commit ed77d40c63f88f654873b95309ddbc684a91a598 Author: PSp..om Date: Tue Jan 7 05:44:17 2014 +0000 First pass at copying the source files with history into the new gradle-based structure. git-svn-id: https://jmonkeyengine.googlecode.com/svn/branches/gradle-restructure@10964 75d07b2b-3a1a-0410-a2c5-0572b91ccdca diff --git a/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java b/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java new file mode 100644 index 000000000..0e108b3c2 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java @@ -0,0 +1,640 @@ +package com.jme3.app; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.pm.ActivityInfo; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.os.Bundle; +import android.util.Log; +import android.view.*; +import android.view.ViewGroup.LayoutParams; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; +import com.jme3.audio.AudioRenderer; +import com.jme3.audio.android.AndroidAudioRenderer; +import com.jme3.input.JoyInput; +import com.jme3.input.TouchInput; +import com.jme3.input.android.AndroidSensorJoyInput; +import com.jme3.input.controls.TouchListener; +import com.jme3.input.controls.TouchTrigger; +import com.jme3.input.event.TouchEvent; +import com.jme3.renderer.android.AndroidGLSurfaceView; +import com.jme3.system.AppSettings; +import com.jme3.system.SystemListener; +import com.jme3.system.android.AndroidConfigChooser.ConfigType; +import com.jme3.system.android.JmeAndroidSystem; +import com.jme3.system.android.OGLESContext; +import com.jme3.util.AndroidLogHandler; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +/** + * AndroidHarness wraps a jme application object and runs it on + * Android + * + * @author Kirill + * @author larynx + */ +public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener, SystemListener { + + protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName()); + /** + * The application class to start + */ + protected String appClass = "jme3test.android.Test"; + /** + * The jme3 application object + */ + protected Application app = null; + + /** + * ConfigType.FASTEST is RGB565, GLSurfaceView default ConfigType.BEST is + * RGBA8888 or better if supported by the hardware + * @deprecated ConfigType has been deprecated. AppSettings are now used + * to determine the desired configuration to match how LWJGL is implemented. + * Use eglBitsPerPixel, eglAlphaBits, eglDepthBits, eglStencilBits in MainActivity to + * override the default values + * (default values: RGB888, 0 alpha bits, 16 bit depth, 0 stencil bits) + */ + @Deprecated + protected ConfigType eglConfigType = null; + + /** + * Sets the desired RGB size for the surfaceview. 16 = RGB565, 24 = RGB888. + * (default = 24) + */ + protected int eglBitsPerPixel = 24; + + /** + * Sets the desired number of Alpha bits for the surfaceview. This affects + * how the surfaceview is able to display Android views that are located + * under the surfaceview jME uses to render the scenegraph. + * 0 = Opaque surfaceview background (fastest) + * 1->7 = Transparent surfaceview background + * 8 or higher = Translucent surfaceview background + * (default = 0) + */ + protected int eglAlphaBits = 0; + + /** + * The number of depth bits specifies the precision of the depth buffer. + * (default = 16) + */ + protected int eglDepthBits = 16; + + /** + * Sets the number of samples to use for multisampling.
+ * Leave 0 (default) to disable multisampling.
+ * Set to 2 or 4 to enable multisampling. + */ + protected int eglSamples = 0; + + /** + * Set the number of stencil bits. + * (default = 0) + */ + protected int eglStencilBits = 0; + + /** + * If true all valid and not valid egl configs are logged + * @deprecated this has no use + */ + @Deprecated + protected boolean eglConfigVerboseLogging = false; + + /** + * set to 2, 4 to enable multisampling. + * @deprecated Use eglSamples + */ + @Deprecated + protected int antiAliasingSamples = 0; + + /** + * Sets the type of Audio Renderer to be used. + *

+ * Android MediaPlayer / SoundPool is the default and can be used on all + * supported Android platform versions (2.2+)
+ * OpenAL Soft uses an OpenSL backend and is only supported on Android + * versions 2.3+. + *

+ * Only use ANDROID_ static strings found in AppSettings + * + */ + protected String audioRendererType = AppSettings.ANDROID_MEDIAPLAYER; + + /** + * If true Android Sensors are used as simulated Joysticks. Users can use the + * Android sensor feedback through the RawInputListener or by registering + * JoyAxisTriggers. + */ + protected boolean joystickEventsEnabled = false; + /** + * If true MouseEvents are generated from TouchEvents + */ + protected boolean mouseEventsEnabled = true; + /** + * Flip X axis + */ + protected boolean mouseEventsInvertX = false; + /** + * Flip Y axis + */ + protected boolean mouseEventsInvertY = false; + /** + * if true finish this activity when the jme app is stopped + */ + protected boolean finishOnAppStop = true; + /** + * set to false if you don't want the harness to handle the exit hook + */ + protected boolean handleExitHook = true; + /** + * Title of the exit dialog, default is "Do you want to exit?" + */ + protected String exitDialogTitle = "Do you want to exit?"; + /** + * Message of the exit dialog, default is "Use your home key to bring this + * app into the background or exit to terminate it." + */ + protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it."; + /** + * Set the screen window mode. If screenFullSize is true, then the + * notification bar and title bar are removed and the screen covers the + * entire display. If screenFullSize is false, then the notification bar + * remains visible if screenShowTitle is true while screenFullScreen is + * false, then the title bar is also displayed under the notification bar. + */ + protected boolean screenFullScreen = true; + /** + * if screenShowTitle is true while screenFullScreen is false, then the + * title bar is also displayed under the notification bar + */ + protected boolean screenShowTitle = true; + /** + * Splash Screen picture Resource ID. If a Splash Screen is desired, set + * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If + * splashPicID = 0, then no splash screen will be displayed. + */ + protected int splashPicID = 0; + /** + * Set the screen orientation, default is SENSOR + * ActivityInfo.SCREEN_ORIENTATION_* constants package + * android.content.pm.ActivityInfo + * + * SCREEN_ORIENTATION_UNSPECIFIED SCREEN_ORIENTATION_LANDSCAPE + * SCREEN_ORIENTATION_PORTRAIT SCREEN_ORIENTATION_USER + * SCREEN_ORIENTATION_BEHIND SCREEN_ORIENTATION_SENSOR (default) + * SCREEN_ORIENTATION_NOSENSOR + */ + protected int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR; + protected OGLESContext ctx; + protected AndroidGLSurfaceView view = null; + protected boolean isGLThreadPaused = true; + protected ImageView splashImageView = null; + protected FrameLayout frameLayout = null; + final private String ESCAPE_EVENT = "TouchEscape"; + private boolean firstDrawFrame = true; + private boolean inConfigChange = false; + + private class DataObject { + protected Application app = null; + } + + @Override + public Object onRetainNonConfigurationInstance() { + logger.log(Level.FINE, "onRetainNonConfigurationInstance"); + final DataObject data = new DataObject(); + data.app = this.app; + inConfigChange = true; + + return data; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + initializeLogHandler(); + + logger.fine("onCreate"); + super.onCreate(savedInstanceState); + + JmeAndroidSystem.setActivity(this); + if (screenFullScreen) { + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + } else { + if (!screenShowTitle) { + requestWindowFeature(Window.FEATURE_NO_TITLE); + } + } + + setRequestedOrientation(screenOrientation); + + final DataObject data = (DataObject) getLastNonConfigurationInstance(); + if (data != null) { + logger.log(Level.FINE, "Using Retained App"); + this.app = data.app; + } else { + // Discover the screen reolution + //TODO try to find a better way to get a hand on the resolution + WindowManager wind = this.getWindowManager(); + Display disp = wind.getDefaultDisplay(); + Log.d("AndroidHarness", "Resolution from Window, width:" + disp.getWidth() + ", height: " + disp.getHeight()); + + // Create Settings + logger.log(Level.FINE, "Creating settings"); + AppSettings settings = new AppSettings(true); + settings.setEmulateMouse(mouseEventsEnabled); + settings.setEmulateMouseFlipAxis(mouseEventsInvertX, mouseEventsInvertY); + settings.setUseJoysticks(joystickEventsEnabled); + if (eglConfigType == null) { + logger.log(Level.FINE, "using new appsettings for eglConfig"); + settings.setBitsPerPixel(eglBitsPerPixel); + settings.setAlphaBits(eglAlphaBits); + settings.setDepthBits(eglDepthBits); + settings.setSamples(eglSamples); + settings.setStencilBits(eglStencilBits); + } else { + logger.log(Level.FINE, "using old eglConfigType {0} for eglConfig", eglConfigType); + switch (eglConfigType) { + case BEST: + settings.setBitsPerPixel(24); + settings.setAlphaBits(0); + settings.setDepthBits(16); + settings.setStencilBits(0); + break; + case FASTEST: + case LEGACY: + settings.setBitsPerPixel(16); + settings.setAlphaBits(0); + settings.setDepthBits(16); + settings.setStencilBits(0); + break; + case BEST_TRANSLUCENT: + settings.setBitsPerPixel(24); + settings.setAlphaBits(8); + settings.setDepthBits(16); + settings.setStencilBits(0); + break; + default: + throw new IllegalArgumentException("Invalid eglConfigType"); + } + settings.setSamples(antiAliasingSamples); + } + settings.setResolution(disp.getWidth(), disp.getHeight()); + settings.setAudioRenderer(audioRendererType); + + + // Create application instance + try { + if (app == null) { + @SuppressWarnings("unchecked") + Class clazz = (Class) Class.forName(appClass); + app = clazz.newInstance(); + } + + app.setSettings(settings); + app.start(); + } catch (Exception ex) { + handleError("Class " + appClass + " init failed", ex); + setContentView(new TextView(this)); + } + } + + ctx = (OGLESContext) app.getContext(); + view = ctx.createView(); + // AndroidHarness wraps the app as a SystemListener. + ctx.setSystemListener(this); + layoutDisplay(); + } + + @Override + protected void onRestart() { + logger.fine("onRestart"); + super.onRestart(); + if (app != null) { + app.restart(); + } + } + + @Override + protected void onStart() { + logger.fine("onStart"); + super.onStart(); + } + + @Override + protected void onResume() { + logger.fine("onResume"); + super.onResume(); + + gainFocus(); + } + + @Override + protected void onPause() { + logger.fine("onPause"); + loseFocus(); + + super.onPause(); + } + + @Override + protected void onStop() { + logger.fine("onStop"); + super.onStop(); + } + + @Override + protected void onDestroy() { + logger.fine("onDestroy"); + final DataObject data = (DataObject) getLastNonConfigurationInstance(); + if (data != null || inConfigChange) { + logger.fine("In Config Change, not stopping app."); + } else { + if (app != null) { + app.stop(!isGLThreadPaused); + } + } + setContentView(new TextView(this)); + JmeAndroidSystem.setActivity(null); + ctx = null; + app = null; + view = null; + + super.onDestroy(); + } + + public Application getJmeApplication() { + return app; + } + + /** + * Called when an error has occurred. By default, will show an error message + * to the user and print the exception/error to the log. + */ + @Override + public void handleError(final String errorMsg, final Throwable t) { + String stackTrace = ""; + String title = "Error"; + + if (t != null) { + // Convert exception to string + StringWriter sw = new StringWriter(100); + t.printStackTrace(new PrintWriter(sw)); + stackTrace = sw.toString(); + title = t.toString(); + } + + final String finalTitle = title; + final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception") + + "\n" + stackTrace; + + logger.log(Level.SEVERE, finalMsg); + + runOnUiThread(new Runnable() { + @Override + public void run() { + AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon) + .setTitle(finalTitle).setPositiveButton("Kill", AndroidHarness.this).setMessage(finalMsg).create(); + dialog.show(); + } + }); + } + + /** + * Called by the android alert dialog, terminate the activity and OpenGL + * rendering + * + * @param dialog + * @param whichButton + */ + public void onClick(DialogInterface dialog, int whichButton) { + if (whichButton != -2) { + if (app != null) { + app.stop(true); + } + app = null; + this.finish(); + } + } + + /** + * Gets called by the InputManager on all touch/drag/scale events + */ + @Override + public void onTouch(String name, TouchEvent evt, float tpf) { + if (name.equals(ESCAPE_EVENT)) { + switch (evt.getType()) { + case KEY_UP: + runOnUiThread(new Runnable() { + @Override + public void run() { + AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon) + .setTitle(exitDialogTitle).setPositiveButton("Yes", AndroidHarness.this).setNegativeButton("No", AndroidHarness.this).setMessage(exitDialogMessage).create(); + dialog.show(); + } + }); + break; + default: + break; + } + } + } + + public void layoutDisplay() { + logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID); + if (view == null) { + logger.log(Level.FINE, "view is null!"); + } + if (splashPicID != 0) { + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( + LayoutParams.FILL_PARENT, + LayoutParams.FILL_PARENT, + Gravity.CENTER); + + frameLayout = new FrameLayout(this); + splashImageView = new ImageView(this); + + Drawable drawable = this.getResources().getDrawable(splashPicID); + if (drawable instanceof NinePatchDrawable) { + splashImageView.setBackgroundDrawable(drawable); + } else { + splashImageView.setImageResource(splashPicID); + } + + if (view.getParent() != null) { + ((ViewGroup) view.getParent()).removeView(view); + } + frameLayout.addView(view); + + if (splashImageView.getParent() != null) { + ((ViewGroup) splashImageView.getParent()).removeView(splashImageView); + } + frameLayout.addView(splashImageView, lp); + + setContentView(frameLayout); + logger.log(Level.FINE, "Splash Screen Created"); + } else { + logger.log(Level.FINE, "Splash Screen Skipped."); + setContentView(view); + } + } + + public void removeSplashScreen() { + logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID); + if (splashPicID != 0) { + if (frameLayout != null) { + if (splashImageView != null) { + this.runOnUiThread(new Runnable() { + @Override + public void run() { + splashImageView.setVisibility(View.INVISIBLE); + frameLayout.removeView(splashImageView); + } + }); + } else { + logger.log(Level.FINE, "splashImageView is null"); + } + } else { + logger.log(Level.FINE, "frameLayout is null"); + } + } + } + + /** + * Removes the standard Android log handler due to an issue with not logging + * entries lower than INFO level and adds a handler that produces + * JME formatted log messages. + */ + protected void initializeLogHandler() { + Logger log = LogManager.getLogManager().getLogger(""); + for (Handler handler : log.getHandlers()) { + if (log.getLevel() != null && log.getLevel().intValue() <= Level.FINE.intValue()) { + Log.v("AndroidHarness", "Removing Handler class: " + handler.getClass().getName()); + } + log.removeHandler(handler); + } + Handler handler = new AndroidLogHandler(); + log.addHandler(handler); + handler.setLevel(Level.ALL); + } + + public void initialize() { + app.initialize(); + if (handleExitHook) { + // remove existing mapping from SimpleApplication that stops the app + // when the esc key is pressed (esc key = android back key) so that + // AndroidHarness can produce the exit app dialog box. + if (app.getInputManager().hasMapping(SimpleApplication.INPUT_MAPPING_EXIT)) { + app.getInputManager().deleteMapping(SimpleApplication.INPUT_MAPPING_EXIT); + } + + app.getInputManager().addMapping(ESCAPE_EVENT, new TouchTrigger(TouchInput.KEYCODE_BACK)); + app.getInputManager().addListener(this, new String[]{ESCAPE_EVENT}); + } + } + + public void reshape(int width, int height) { + app.reshape(width, height); + } + + public void update() { + app.update(); + // call to remove the splash screen, if present. + // call after app.update() to make sure no gap between + // splash screen going away and app display being shown. + if (firstDrawFrame) { + removeSplashScreen(); + firstDrawFrame = false; + } + } + + public void requestClose(boolean esc) { + app.requestClose(esc); + } + + public void destroy() { + if (app != null) { + app.destroy(); + } + if (finishOnAppStop) { + finish(); + } + } + + public void gainFocus() { + logger.fine("gainFocus"); + if (view != null) { + view.onResume(); + } + + if (app != null) { + //resume the audio + AudioRenderer result = app.getAudioRenderer(); + if (result != null) { + if (result instanceof AndroidAudioRenderer) { + AndroidAudioRenderer renderer = (AndroidAudioRenderer) result; + renderer.resumeAll(); + } + } + //resume the sensors (aka joysticks) + if (app.getContext() != null) { + JoyInput joyInput = app.getContext().getJoyInput(); + if (joyInput != null) { + if (joyInput instanceof AndroidSensorJoyInput) { + AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput; + androidJoyInput.resumeSensors(); + } + } + } + } + + isGLThreadPaused = false; + + if (app != null) { + app.gainFocus(); + } + } + + public void loseFocus() { + logger.fine("loseFocus"); + if (app != null) { + app.loseFocus(); + } + + if (view != null) { + view.onPause(); + } + + if (app != null) { + //pause the audio + AudioRenderer result = app.getAudioRenderer(); + if (result != null) { + logger.log(Level.FINE, "pause: {0}", result.getClass().getSimpleName()); + if (result instanceof AndroidAudioRenderer) { + AndroidAudioRenderer renderer = (AndroidAudioRenderer) result; + renderer.pauseAll(); + } + } + //pause the sensors (aka joysticks) + if (app.getContext() != null) { + JoyInput joyInput = app.getContext().getJoyInput(); + if (joyInput != null) { + if (joyInput instanceof AndroidSensorJoyInput) { + AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput; + androidJoyInput.pauseSensors(); + } + } + } + } + isGLThreadPaused = true; + } +} diff --git a/jme3-android/src/main/java/com/jme3/app/R.java b/jme3-android/src/main/java/com/jme3/app/R.java new file mode 100644 index 000000000..4db44e3dc --- /dev/null +++ b/jme3-android/src/main/java/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/jme3-android/src/main/java/com/jme3/asset/AndroidAssetManager.java b/jme3-android/src/main/java/com/jme3/asset/AndroidAssetManager.java new file mode 100644 index 000000000..5d58dd421 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/asset/AndroidAssetManager.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.asset.plugins.AndroidLocator; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.audio.android.AndroidAudioRenderer; +import com.jme3.audio.plugins.AndroidAudioLoader; +import com.jme3.audio.plugins.WAVLoader; +import com.jme3.system.AppSettings; +import com.jme3.system.android.JmeAndroidSystem; +import com.jme3.texture.plugins.AndroidImageLoader; +import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidAssetManager is an implementation of DesktopAssetManager for Android + * + * @author larynx + */ +public class AndroidAssetManager extends DesktopAssetManager { + + private static final Logger logger = Logger.getLogger(AndroidAssetManager.class.getName()); + + public AndroidAssetManager() { + this(null); + } + + @Deprecated + public AndroidAssetManager(boolean loadDefaults) { + //this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg")); + this(null); + } + + private void registerLoaderSafe(String loaderClass, String ... extensions) { + try { + Class loader = (Class) Class.forName(loaderClass); + registerLoader(loader, extensions); + } catch (Exception e){ + logger.log(Level.WARNING, "Failed to load AssetLoader", e); + } + } + + /** + * AndroidAssetManager constructor + * If URL == null then a default list of locators and loaders for android is set + * @param configFile + */ + public AndroidAssetManager(URL configFile) { + System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver"); + + // Set Default Android config + registerLocator("", AndroidLocator.class); + registerLocator("", ClasspathLocator.class); + + registerLoader(AndroidImageLoader.class, "jpg", "bmp", "gif", "png", "jpeg"); + if (JmeAndroidSystem.getAudioRendererType().equals(AppSettings.ANDROID_MEDIAPLAYER)) { + registerLoader(AndroidAudioLoader.class, "ogg", "mp3", "wav"); + } else if (JmeAndroidSystem.getAudioRendererType().equals(AppSettings.ANDROID_OPENAL_SOFT)) { + registerLoader(WAVLoader.class, "wav"); + // TODO jogg is not in core, need to add some other way to get around compile errors, or not. +// registerLoader(com.jme3.audio.plugins.OGGLoader.class, "ogg"); + registerLoaderSafe("com.jme3.audio.plugins.OGGLoader", "ogg"); + } else { + throw new IllegalStateException("No Audio Renderer Type defined!"); + } + + registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3m"); + registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3md"); + registerLoader(com.jme3.material.plugins.ShaderNodeDefinitionLoader.class, "j3sn"); + registerLoader(com.jme3.shader.plugins.GLSLLoader.class, "vert", "frag", "glsl", "glsllib"); + registerLoader(com.jme3.export.binary.BinaryImporter.class, "j3o"); + registerLoader(com.jme3.font.plugins.BitmapFontLoader.class, "fnt"); + + // Less common loaders (especially on Android) + registerLoaderSafe("com.jme3.texture.plugins.DDSLoader", "dds"); + registerLoaderSafe("com.jme3.texture.plugins.PFMLoader", "pfm"); + registerLoaderSafe("com.jme3.texture.plugins.HDRLoader", "hdr"); + registerLoaderSafe("com.jme3.texture.plugins.TGALoader", "tga"); + registerLoaderSafe("com.jme3.scene.plugins.OBJLoader", "obj"); + registerLoaderSafe("com.jme3.scene.plugins.MTLLoader", "mtl"); + registerLoaderSafe("com.jme3.scene.plugins.ogre.MeshLoader", "mesh.xml"); + registerLoaderSafe("com.jme3.scene.plugins.ogre.SkeletonLoader", "skeleton.xml"); + registerLoaderSafe("com.jme3.scene.plugins.ogre.MaterialLoader", "material"); + registerLoaderSafe("com.jme3.scene.plugins.ogre.SceneLoader", "scene"); + + + logger.fine("AndroidAssetManager created."); + } + +} diff --git a/jme3-android/src/main/java/com/jme3/asset/AndroidImageInfo.java b/jme3-android/src/main/java/com/jme3/asset/AndroidImageInfo.java new file mode 100644 index 000000000..9bf2105a9 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/asset/AndroidImageInfo.java @@ -0,0 +1,138 @@ +package com.jme3.asset; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import com.jme3.math.ColorRGBA; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.image.ImageRaster; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidImageInfo is set in a jME3 image via the {@link Image#setEfficentData(java.lang.Object) } + * method to retrieve a {@link Bitmap} when it is needed by the renderer. + * User code may extend AndroidImageInfo and provide their own implementation of the + * {@link AndroidImageInfo#loadBitmap()} method to acquire a bitmap by their own means. + * + * @author Kirill Vainer + */ +public class AndroidImageInfo extends ImageRaster { + + private static final Logger logger = Logger.getLogger(AndroidImageInfo.class.getName()); + + protected AssetInfo assetInfo; + protected Bitmap bitmap; + protected Format format; + + public AndroidImageInfo(AssetInfo assetInfo) { + this.assetInfo = assetInfo; + } + + public Bitmap getBitmap(){ + if (bitmap == null || bitmap.isRecycled()){ + try { + loadBitmap(); + } catch (IOException ex) { + // If called first inside AssetManager, the error will propagate + // correctly. Assuming that if the first calls succeeds + // then subsequent calls will as well. + throw new AssetLoadException("Failed to load image " + assetInfo.getKey(), ex); + } + } + return bitmap; + } + + public void notifyBitmapUploaded() { + // Default function is to recycle the bitmap. + if (bitmap != null && !bitmap.isRecycled()) { + bitmap.recycle(); + bitmap = null; + logger.log(Level.FINE, "Bitmap was deleted. "); + } + } + + public Format getFormat(){ + return format; + } + + @Override + public int getWidth() { + return getBitmap().getWidth(); + } + + @Override + public int getHeight() { + return getBitmap().getHeight(); + } + + @Override + public void setPixel(int x, int y, ColorRGBA color) { + getBitmap().setPixel(x, y, color.asIntARGB()); + } + + @Override + public ColorRGBA getPixel(int x, int y, ColorRGBA store) { + if (store == null) { + store = new ColorRGBA(); + } + store.fromIntARGB(getBitmap().getPixel(x, y)); + return store; + } + + /** + * Loads the bitmap directly from the asset info, possibly updating + * or creating the image object. + */ + protected void loadBitmap() throws IOException{ + InputStream in = null; + try { + in = assetInfo.openStream(); + bitmap = BitmapFactory.decodeStream(in); + if (bitmap == null) { + throw new IOException("Failed to load image: " + assetInfo.getKey().getName()); + } + } finally { + if (in != null) { + in.close(); + } + } + + switch (bitmap.getConfig()) { + case ALPHA_8: + format = Image.Format.Alpha8; + break; + case ARGB_4444: + format = Image.Format.ARGB4444; + break; + case ARGB_8888: + format = Image.Format.RGBA8; + break; + case RGB_565: + format = Image.Format.RGB565; + break; + default: + // This should still work as long + // as renderer doesn't check format + // but just loads bitmap directly. + format = null; + } + + TextureKey texKey = (TextureKey) assetInfo.getKey(); + if (texKey.isFlipY()) { + // Flip the image, then delete the old one. + Matrix flipMat = new Matrix(); + flipMat.preScale(1.0f, -1.0f); + Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), flipMat, false); + bitmap.recycle(); + bitmap = newBitmap; + + if (bitmap == null) { + throw new IOException("Failed to flip image: " + texKey); + } + } + } +} diff --git a/jme3-android/src/main/java/com/jme3/asset/plugins/AndroidLocator.java b/jme3-android/src/main/java/com/jme3/asset/plugins/AndroidLocator.java new file mode 100644 index 000000000..16bf7c9c1 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/asset/plugins/AndroidLocator.java @@ -0,0 +1,87 @@ +package com.jme3.asset.plugins; + +import com.jme3.asset.*; +import com.jme3.system.android.JmeAndroidSystem; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; + +public class AndroidLocator implements AssetLocator { + + private static final Logger logger = Logger.getLogger(AndroidLocator.class.getName()); + + private android.content.res.AssetManager androidManager; + private String rootPath = ""; + + private class AndroidAssetInfo extends AssetInfo { + + private InputStream in; + private final String assetPath; + + public AndroidAssetInfo(com.jme3.asset.AssetManager assetManager, AssetKey key, String assetPath, InputStream in) { + super(assetManager, key); + this.assetPath = assetPath; + this.in = in; + } + + @Override + public InputStream openStream() { + if (in != null){ + // Reuse the already existing stream (only once) + InputStream in2 = in; + in = null; + return in2; + }else{ + // Create a new stream for subsequent invocations. + try { + return androidManager.open(assetPath); + } catch (IOException ex) { + throw new AssetLoadException("Failed to open asset " + assetPath, ex); + } + } + } + } + + private AndroidAssetInfo create(AssetManager assetManager, AssetKey key, String assetPath) throws IOException { + try { + InputStream in = androidManager.open(assetPath); + if (in == null){ + return null; + }else{ + return new AndroidAssetInfo(assetManager, key, assetPath, in); + } + } catch (IOException ex) { + // XXX: Prefer to show warning here? + // Should only surpress exceptions for "file missing" type errors. + return null; + } + } + + public AndroidLocator() { + androidManager = JmeAndroidSystem.getActivity().getAssets(); + } + + public void setRootPath(String rootPath) { + this.rootPath = rootPath; + } + + @SuppressWarnings("rawtypes") + @Override + public AssetInfo locate(com.jme3.asset.AssetManager manager, AssetKey key) { + String assetPath = rootPath + key.getName(); + // Fix path issues + if (assetPath.startsWith("/")) { + // Remove leading / + assetPath = assetPath.substring(1); + } + assetPath = assetPath.replace("//", "/"); + try { + return create(manager, key, assetPath); + } catch (IOException ex) { + // This is different handling than URL locator + // since classpath locating would return null at the getResource() + // call, otherwise there's a more critical error... + throw new AssetLoadException("Failed to open asset " + assetPath, ex); + } + } +} diff --git a/jme3-android/src/main/java/com/jme3/audio/android/AL.java b/jme3-android/src/main/java/com/jme3/audio/android/AL.java new file mode 100644 index 000000000..d8fea3933 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/audio/android/AL.java @@ -0,0 +1,1054 @@ +package com.jme3.audio.android; + +/** + * + * @author iwgeric + */ +public class AL { + + + + /* ********** */ + /* FROM ALC.h */ + /* ********** */ + +// typedef struct ALCdevice_struct ALCdevice; +// typedef struct ALCcontext_struct ALCcontext; + + + /** + * No error + */ + static final int ALC_NO_ERROR = 0; + + /** + * No device + */ + static final int ALC_INVALID_DEVICE = 0xA001; + + /** + * invalid context ID + */ + static final int ALC_INVALID_CONTEXT = 0xA002; + + /** + * bad enum + */ + static final int ALC_INVALID_ENUM = 0xA003; + + /** + * bad value + */ + static final int ALC_INVALID_VALUE = 0xA004; + + /** + * Out of memory. + */ + static final int ALC_OUT_OF_MEMORY = 0xA005; + + + /** + * The Specifier string for default device + */ + static final int ALC_DEFAULT_DEVICE_SPECIFIER = 0x1004; + static final int ALC_DEVICE_SPECIFIER = 0x1005; + static final int ALC_EXTENSIONS = 0x1006; + + static final int ALC_MAJOR_VERSION = 0x1000; + static final int ALC_MINOR_VERSION = 0x1001; + + static final int ALC_ATTRIBUTES_SIZE = 0x1002; + static final int ALC_ALL_ATTRIBUTES = 0x1003; + + + /** + * Capture extension + */ + static final int ALC_EXT_CAPTURE = 1; + static final int ALC_CAPTURE_DEVICE_SPECIFIER = 0x310; + static final int ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER = 0x311; + static final int ALC_CAPTURE_SAMPLES = 0x312; + + + /** + * ALC_ENUMERATE_ALL_EXT enums + */ + static final int ALC_ENUMERATE_ALL_EXT = 1; + static final int ALC_DEFAULT_ALL_DEVICES_SPECIFIER = 0x1012; + static final int ALC_ALL_DEVICES_SPECIFIER = 0x1013; + + + /* ********** */ + /* FROM AL.h */ + /* ********** */ + +/** Boolean False. */ + static final int AL_FALSE = 0; + +/** Boolean True. */ + static final int AL_TRUE = 1; + +/* "no distance model" or "no buffer" */ + static final int AL_NONE = 0; + +/** Indicate Source has relative coordinates. */ + static final int AL_SOURCE_RELATIVE = 0x202; + + + +/** + * Directional source, inner cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ + static final int AL_CONE_INNER_ANGLE = 0x1001; + +/** + * Directional source, outer cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ + static final int AL_CONE_OUTER_ANGLE = 0x1002; + +/** + * Specify the pitch to be applied at source. + * Range: [0.5-2.0] + * Default: 1.0 + */ + static final int AL_PITCH = 0x1003; + +/** + * Specify the current location in three dimensional space. + * OpenAL, like OpenGL, uses a right handed coordinate system, + * where in a frontal default view X (thumb) points right, + * Y points up (index finger), and Z points towards the + * viewer/camera (middle finger). + * To switch from a left handed coordinate system, flip the + * sign on the Z coordinate. + * Listener position is always in the world coordinate system. + */ + static final int AL_POSITION = 0x1004; + +/** Specify the current direction. */ + static final int AL_DIRECTION = 0x1005; + +/** Specify the current velocity in three dimensional space. */ + static final int AL_VELOCITY = 0x1006; + +/** + * Indicate whether source is looping. + * Type: ALboolean? + * Range: [AL_TRUE, AL_FALSE] + * Default: FALSE. + */ + static final int AL_LOOPING = 0x1007; + +/** + * Indicate the buffer to provide sound samples. + * Type: ALuint. + * Range: any valid Buffer id. + */ + static final int AL_BUFFER = 0x1009; + +/** + * Indicate the gain (volume amplification) applied. + * Type: ALfloat. + * Range: ]0.0- ] + * A value of 1.0 means un-attenuated/unchanged. + * Each division by 2 equals an attenuation of -6dB. + * Each multiplicaton with 2 equals an amplification of +6dB. + * A value of 0.0 is meaningless with respect to a logarithmic + * scale; it is interpreted as zero volume - the channel + * is effectively disabled. + */ + static final int AL_GAIN = 0x100A; + +/* + * Indicate minimum source attenuation + * Type: ALfloat + * Range: [0.0 - 1.0] + * + * Logarthmic + */ + static final int AL_MIN_GAIN = 0x100D; + +/** + * Indicate maximum source attenuation + * Type: ALfloat + * Range: [0.0 - 1.0] + * + * Logarthmic + */ + static final int AL_MAX_GAIN = 0x100E; + +/** + * Indicate listener orientation. + * + * at/up + */ + static final int AL_ORIENTATION = 0x100F; + +/** + * Source state information. + */ + static final int AL_SOURCE_STATE = 0x1010; + static final int AL_INITIAL = 0x1011; + static final int AL_PLAYING = 0x1012; + static final int AL_PAUSED = 0x1013; + static final int AL_STOPPED = 0x1014; + +/** + * Buffer Queue params + */ + static final int AL_BUFFERS_QUEUED = 0x1015; + static final int AL_BUFFERS_PROCESSED = 0x1016; + +/** + * Source buffer position information + */ + static final int AL_SEC_OFFSET = 0x1024; + static final int AL_SAMPLE_OFFSET = 0x1025; + static final int AL_BYTE_OFFSET = 0x1026; + +/* + * Source type (Static, Streaming or undetermined) + * Source is Static if a Buffer has been attached using AL_BUFFER + * Source is Streaming if one or more Buffers have been attached using alSourceQueueBuffers + * Source is undetermined when it has the NULL buffer attached + */ + static final int AL_SOURCE_TYPE = 0x1027; + static final int AL_STATIC = 0x1028; + static final int AL_STREAMING = 0x1029; + static final int AL_UNDETERMINED = 0x1030; + +/** Sound samples: format specifier. */ + static final int AL_FORMAT_MONO8 = 0x1100; + static final int AL_FORMAT_MONO16 = 0x1101; + static final int AL_FORMAT_STEREO8 = 0x1102; + static final int AL_FORMAT_STEREO16 = 0x1103; + +/** + * source specific reference distance + * Type: ALfloat + * Range: 0.0 - +inf + * + * At 0.0, no distance attenuation occurs. Default is + * 1.0. + */ + static final int AL_REFERENCE_DISTANCE = 0x1020; + +/** + * source specific rolloff factor + * Type: ALfloat + * Range: 0.0 - +inf + * + */ + static final int AL_ROLLOFF_FACTOR = 0x1021; + +/** + * Directional source, outer cone gain. + * + * Default: 0.0 + * Range: [0.0 - 1.0] + * Logarithmic + */ + static final int AL_CONE_OUTER_GAIN = 0x1022; + +/** + * Indicate distance above which sources are not + * attenuated using the inverse clamped distance model. + * + * Default: +inf + * Type: ALfloat + * Range: 0.0 - +inf + */ + static final int AL_MAX_DISTANCE = 0x1023; + +/** + * Sound samples: frequency, in units of Hertz [Hz]. + * This is the number of samples per second. Half of the + * sample frequency marks the maximum significant + * frequency component. + */ + static final int AL_FREQUENCY = 0x2001; + static final int AL_BITS = 0x2002; + static final int AL_CHANNELS = 0x2003; + static final int AL_SIZE = 0x2004; + +/** + * Buffer state. + * + * Not supported for public use (yet). + */ + static final int AL_UNUSED = 0x2010; + static final int AL_PENDING = 0x2011; + static final int AL_PROCESSED = 0x2012; + + +/** Errors: No Error. */ + static final int AL_NO_ERROR = 0; + +/** + * Invalid Name paramater passed to AL call. + */ + static final int AL_INVALID_NAME = 0xA001; + +/** + * Invalid parameter passed to AL call. + */ + static final int AL_INVALID_ENUM = 0xA002; + +/** + * Invalid enum parameter value. + */ + static final int AL_INVALID_VALUE = 0xA003; + +/** + * Illegal call. + */ + static final int AL_INVALID_OPERATION = 0xA004; + + +/** + * No mojo. + */ + static final int AL_OUT_OF_MEMORY = 0xA005; + + +/** Context strings: Vendor Name. */ + static final int AL_VENDOR = 0xB001; + static final int AL_VERSION = 0xB002; + static final int AL_RENDERER = 0xB003; + static final int AL_EXTENSIONS = 0xB004; + +/** Global tweakage. */ + +/** + * Doppler scale. Default 1.0 + */ + static final int AL_DOPPLER_FACTOR = 0xC000; + +/** + * Tweaks speed of propagation. + */ + static final int AL_DOPPLER_VELOCITY = 0xC001; + +/** + * Speed of Sound in units per second + */ + static final int AL_SPEED_OF_SOUND = 0xC003; + +/** + * Distance models + * + * used in conjunction with DistanceModel + * + * implicit: NONE, which disances distance attenuation. + */ + static final int AL_DISTANCE_MODEL = 0xD000; + static final int AL_INVERSE_DISTANCE = 0xD001; + static final int AL_INVERSE_DISTANCE_CLAMPED = 0xD002; + static final int AL_LINEAR_DISTANCE = 0xD003; + static final int AL_LINEAR_DISTANCE_CLAMPED = 0xD004; + static final int AL_EXPONENT_DISTANCE = 0xD005; + static final int AL_EXPONENT_DISTANCE_CLAMPED = 0xD006; + + /* ********** */ + /* FROM efx.h */ + /* ********** */ + + static final String ALC_EXT_EFX_NAME = "ALC_EXT_EFX"; + + static final int ALC_EFX_MAJOR_VERSION = 0x20001; + static final int ALC_EFX_MINOR_VERSION = 0x20002; + static final int ALC_MAX_AUXILIARY_SENDS = 0x20003; + + +///* Listener properties. */ +//#define AL_METERS_PER_UNIT 0x20004 +// +///* Source properties. */ + static final int AL_DIRECT_FILTER = 0x20005; + static final int AL_AUXILIARY_SEND_FILTER = 0x20006; +//#define AL_AIR_ABSORPTION_FACTOR 0x20007 +//#define AL_ROOM_ROLLOFF_FACTOR 0x20008 +//#define AL_CONE_OUTER_GAINHF 0x20009 + static final int AL_DIRECT_FILTER_GAINHF_AUTO = 0x2000A; +//#define AL_AUXILIARY_SEND_FILTER_GAIN_AUTO 0x2000B +//#define AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO 0x2000C +// +// +///* Effect properties. */ +// +///* Reverb effect parameters */ + static final int AL_REVERB_DENSITY = 0x0001; + static final int AL_REVERB_DIFFUSION = 0x0002; + static final int AL_REVERB_GAIN = 0x0003; + static final int AL_REVERB_GAINHF = 0x0004; + static final int AL_REVERB_DECAY_TIME = 0x0005; + static final int AL_REVERB_DECAY_HFRATIO = 0x0006; + static final int AL_REVERB_REFLECTIONS_GAIN = 0x0007; + static final int AL_REVERB_REFLECTIONS_DELAY = 0x0008; + static final int AL_REVERB_LATE_REVERB_GAIN = 0x0009; + static final int AL_REVERB_LATE_REVERB_DELAY = 0x000A; + static final int AL_REVERB_AIR_ABSORPTION_GAINHF = 0x000B; + static final int AL_REVERB_ROOM_ROLLOFF_FACTOR = 0x000C; + static final int AL_REVERB_DECAY_HFLIMIT = 0x000D; + +///* EAX Reverb effect parameters */ +//#define AL_EAXREVERB_DENSITY 0x0001 +//#define AL_EAXREVERB_DIFFUSION 0x0002 +//#define AL_EAXREVERB_GAIN 0x0003 +//#define AL_EAXREVERB_GAINHF 0x0004 +//#define AL_EAXREVERB_GAINLF 0x0005 +//#define AL_EAXREVERB_DECAY_TIME 0x0006 +//#define AL_EAXREVERB_DECAY_HFRATIO 0x0007 +//#define AL_EAXREVERB_DECAY_LFRATIO 0x0008 +//#define AL_EAXREVERB_REFLECTIONS_GAIN 0x0009 +//#define AL_EAXREVERB_REFLECTIONS_DELAY 0x000A +//#define AL_EAXREVERB_REFLECTIONS_PAN 0x000B +//#define AL_EAXREVERB_LATE_REVERB_GAIN 0x000C +//#define AL_EAXREVERB_LATE_REVERB_DELAY 0x000D +//#define AL_EAXREVERB_LATE_REVERB_PAN 0x000E +//#define AL_EAXREVERB_ECHO_TIME 0x000F +//#define AL_EAXREVERB_ECHO_DEPTH 0x0010 +//#define AL_EAXREVERB_MODULATION_TIME 0x0011 +//#define AL_EAXREVERB_MODULATION_DEPTH 0x0012 +//#define AL_EAXREVERB_AIR_ABSORPTION_GAINHF 0x0013 +//#define AL_EAXREVERB_HFREFERENCE 0x0014 +//#define AL_EAXREVERB_LFREFERENCE 0x0015 +//#define AL_EAXREVERB_ROOM_ROLLOFF_FACTOR 0x0016 +//#define AL_EAXREVERB_DECAY_HFLIMIT 0x0017 +// +///* Chorus effect parameters */ +//#define AL_CHORUS_WAVEFORM 0x0001 +//#define AL_CHORUS_PHASE 0x0002 +//#define AL_CHORUS_RATE 0x0003 +//#define AL_CHORUS_DEPTH 0x0004 +//#define AL_CHORUS_FEEDBACK 0x0005 +//#define AL_CHORUS_DELAY 0x0006 +// +///* Distortion effect parameters */ +//#define AL_DISTORTION_EDGE 0x0001 +//#define AL_DISTORTION_GAIN 0x0002 +//#define AL_DISTORTION_LOWPASS_CUTOFF 0x0003 +//#define AL_DISTORTION_EQCENTER 0x0004 +//#define AL_DISTORTION_EQBANDWIDTH 0x0005 +// +///* Echo effect parameters */ +//#define AL_ECHO_DELAY 0x0001 +//#define AL_ECHO_LRDELAY 0x0002 +//#define AL_ECHO_DAMPING 0x0003 +//#define AL_ECHO_FEEDBACK 0x0004 +//#define AL_ECHO_SPREAD 0x0005 +// +///* Flanger effect parameters */ +//#define AL_FLANGER_WAVEFORM 0x0001 +//#define AL_FLANGER_PHASE 0x0002 +//#define AL_FLANGER_RATE 0x0003 +//#define AL_FLANGER_DEPTH 0x0004 +//#define AL_FLANGER_FEEDBACK 0x0005 +//#define AL_FLANGER_DELAY 0x0006 +// +///* Frequency shifter effect parameters */ +//#define AL_FREQUENCY_SHIFTER_FREQUENCY 0x0001 +//#define AL_FREQUENCY_SHIFTER_LEFT_DIRECTION 0x0002 +//#define AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION 0x0003 +// +///* Vocal morpher effect parameters */ +//#define AL_VOCAL_MORPHER_PHONEMEA 0x0001 +//#define AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING 0x0002 +//#define AL_VOCAL_MORPHER_PHONEMEB 0x0003 +//#define AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING 0x0004 +//#define AL_VOCAL_MORPHER_WAVEFORM 0x0005 +//#define AL_VOCAL_MORPHER_RATE 0x0006 +// +///* Pitchshifter effect parameters */ +//#define AL_PITCH_SHIFTER_COARSE_TUNE 0x0001 +//#define AL_PITCH_SHIFTER_FINE_TUNE 0x0002 +// +///* Ringmodulator effect parameters */ +//#define AL_RING_MODULATOR_FREQUENCY 0x0001 +//#define AL_RING_MODULATOR_HIGHPASS_CUTOFF 0x0002 +//#define AL_RING_MODULATOR_WAVEFORM 0x0003 +// +///* Autowah effect parameters */ +//#define AL_AUTOWAH_ATTACK_TIME 0x0001 +//#define AL_AUTOWAH_RELEASE_TIME 0x0002 +//#define AL_AUTOWAH_RESONANCE 0x0003 +//#define AL_AUTOWAH_PEAK_GAIN 0x0004 +// +///* Compressor effect parameters */ +//#define AL_COMPRESSOR_ONOFF 0x0001 +// +///* Equalizer effect parameters */ +//#define AL_EQUALIZER_LOW_GAIN 0x0001 +//#define AL_EQUALIZER_LOW_CUTOFF 0x0002 +//#define AL_EQUALIZER_MID1_GAIN 0x0003 +//#define AL_EQUALIZER_MID1_CENTER 0x0004 +//#define AL_EQUALIZER_MID1_WIDTH 0x0005 +//#define AL_EQUALIZER_MID2_GAIN 0x0006 +//#define AL_EQUALIZER_MID2_CENTER 0x0007 +//#define AL_EQUALIZER_MID2_WIDTH 0x0008 +//#define AL_EQUALIZER_HIGH_GAIN 0x0009 +//#define AL_EQUALIZER_HIGH_CUTOFF 0x000A +// +///* Effect type */ +//#define AL_EFFECT_FIRST_PARAMETER 0x0000 +//#define AL_EFFECT_LAST_PARAMETER 0x8000 + static final int AL_EFFECT_TYPE = 0x8001; +// +///* Effect types, used with the AL_EFFECT_TYPE property */ +//#define AL_EFFECT_NULL 0x0000 + static final int AL_EFFECT_REVERB = 0x0001; +//#define AL_EFFECT_CHORUS 0x0002 +//#define AL_EFFECT_DISTORTION 0x0003 +//#define AL_EFFECT_ECHO 0x0004 +//#define AL_EFFECT_FLANGER 0x0005 +//#define AL_EFFECT_FREQUENCY_SHIFTER 0x0006 +//#define AL_EFFECT_VOCAL_MORPHER 0x0007 +//#define AL_EFFECT_PITCH_SHIFTER 0x0008 +//#define AL_EFFECT_RING_MODULATOR 0x0009 +//#define AL_EFFECT_AUTOWAH 0x000A +//#define AL_EFFECT_COMPRESSOR 0x000B +//#define AL_EFFECT_EQUALIZER 0x000C +//#define AL_EFFECT_EAXREVERB 0x8000 +// +///* Auxiliary Effect Slot properties. */ + static final int AL_EFFECTSLOT_EFFECT = 0x0001; +//#define AL_EFFECTSLOT_GAIN 0x0002 +//#define AL_EFFECTSLOT_AUXILIARY_SEND_AUTO 0x0003 +// +///* NULL Auxiliary Slot ID to disable a source send. */ +//#define AL_EFFECTSLOT_NULL 0x0000 +// +// +///* Filter properties. */ +// +///* Lowpass filter parameters */ + static final int AL_LOWPASS_GAIN = 0x0001; + static final int AL_LOWPASS_GAINHF = 0x0002; +// +///* Highpass filter parameters */ +//#define AL_HIGHPASS_GAIN 0x0001 +//#define AL_HIGHPASS_GAINLF 0x0002 +// +///* Bandpass filter parameters */ +//#define AL_BANDPASS_GAIN 0x0001 +//#define AL_BANDPASS_GAINLF 0x0002 +//#define AL_BANDPASS_GAINHF 0x0003 +// +///* Filter type */ +//#define AL_FILTER_FIRST_PARAMETER 0x0000 +//#define AL_FILTER_LAST_PARAMETER 0x8000 + static final int AL_FILTER_TYPE = 0x8001; +// +///* Filter types, used with the AL_FILTER_TYPE property */ + static final int AL_FILTER_NULL = 0x0000; + static final int AL_FILTER_LOWPASS = 0x0001; + static final int AL_FILTER_HIGHPASS = 0x0002; +//#define AL_FILTER_BANDPASS 0x0003 +// +///* Filter ranges and defaults. */ +// +///* Lowpass filter */ +//#define AL_LOWPASS_MIN_GAIN (0.0f) +//#define AL_LOWPASS_MAX_GAIN (1.0f) +//#define AL_LOWPASS_DEFAULT_GAIN (1.0f) +// +//#define AL_LOWPASS_MIN_GAINHF (0.0f) +//#define AL_LOWPASS_MAX_GAINHF (1.0f) +//#define AL_LOWPASS_DEFAULT_GAINHF (1.0f) +// +///* Highpass filter */ +//#define AL_HIGHPASS_MIN_GAIN (0.0f) +//#define AL_HIGHPASS_MAX_GAIN (1.0f) +//#define AL_HIGHPASS_DEFAULT_GAIN (1.0f) +// +//#define AL_HIGHPASS_MIN_GAINLF (0.0f) +//#define AL_HIGHPASS_MAX_GAINLF (1.0f) +//#define AL_HIGHPASS_DEFAULT_GAINLF (1.0f) +// +///* Bandpass filter */ +//#define AL_BANDPASS_MIN_GAIN (0.0f) +//#define AL_BANDPASS_MAX_GAIN (1.0f) +//#define AL_BANDPASS_DEFAULT_GAIN (1.0f) +// +//#define AL_BANDPASS_MIN_GAINHF (0.0f) +//#define AL_BANDPASS_MAX_GAINHF (1.0f) +//#define AL_BANDPASS_DEFAULT_GAINHF (1.0f) +// +//#define AL_BANDPASS_MIN_GAINLF (0.0f) +//#define AL_BANDPASS_MAX_GAINLF (1.0f) +//#define AL_BANDPASS_DEFAULT_GAINLF (1.0f) +// +// +///* Effect parameter ranges and defaults. */ +// +///* Standard reverb effect */ +//#define AL_REVERB_MIN_DENSITY (0.0f) +//#define AL_REVERB_MAX_DENSITY (1.0f) +//#define AL_REVERB_DEFAULT_DENSITY (1.0f) +// +//#define AL_REVERB_MIN_DIFFUSION (0.0f) +//#define AL_REVERB_MAX_DIFFUSION (1.0f) +//#define AL_REVERB_DEFAULT_DIFFUSION (1.0f) +// +//#define AL_REVERB_MIN_GAIN (0.0f) +//#define AL_REVERB_MAX_GAIN (1.0f) +//#define AL_REVERB_DEFAULT_GAIN (0.32f) +// +//#define AL_REVERB_MIN_GAINHF (0.0f) +//#define AL_REVERB_MAX_GAINHF (1.0f) +//#define AL_REVERB_DEFAULT_GAINHF (0.89f) +// +//#define AL_REVERB_MIN_DECAY_TIME (0.1f) +//#define AL_REVERB_MAX_DECAY_TIME (20.0f) +//#define AL_REVERB_DEFAULT_DECAY_TIME (1.49f) +// +//#define AL_REVERB_MIN_DECAY_HFRATIO (0.1f) +//#define AL_REVERB_MAX_DECAY_HFRATIO (2.0f) +//#define AL_REVERB_DEFAULT_DECAY_HFRATIO (0.83f) +// +//#define AL_REVERB_MIN_REFLECTIONS_GAIN (0.0f) +//#define AL_REVERB_MAX_REFLECTIONS_GAIN (3.16f) +//#define AL_REVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) +// +//#define AL_REVERB_MIN_REFLECTIONS_DELAY (0.0f) +//#define AL_REVERB_MAX_REFLECTIONS_DELAY (0.3f) +//#define AL_REVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) +// +//#define AL_REVERB_MIN_LATE_REVERB_GAIN (0.0f) +//#define AL_REVERB_MAX_LATE_REVERB_GAIN (10.0f) +//#define AL_REVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) +// +//#define AL_REVERB_MIN_LATE_REVERB_DELAY (0.0f) +//#define AL_REVERB_MAX_LATE_REVERB_DELAY (0.1f) +//#define AL_REVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) +// +//#define AL_REVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) +//#define AL_REVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) +//#define AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) +// +//#define AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +//#define AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +//#define AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) +// +//#define AL_REVERB_MIN_DECAY_HFLIMIT AL_FALSE +//#define AL_REVERB_MAX_DECAY_HFLIMIT AL_TRUE +//#define AL_REVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE +// +///* EAX reverb effect */ +//#define AL_EAXREVERB_MIN_DENSITY (0.0f) +//#define AL_EAXREVERB_MAX_DENSITY (1.0f) +//#define AL_EAXREVERB_DEFAULT_DENSITY (1.0f) +// +//#define AL_EAXREVERB_MIN_DIFFUSION (0.0f) +//#define AL_EAXREVERB_MAX_DIFFUSION (1.0f) +//#define AL_EAXREVERB_DEFAULT_DIFFUSION (1.0f) +// +//#define AL_EAXREVERB_MIN_GAIN (0.0f) +//#define AL_EAXREVERB_MAX_GAIN (1.0f) +//#define AL_EAXREVERB_DEFAULT_GAIN (0.32f) +// +//#define AL_EAXREVERB_MIN_GAINHF (0.0f) +//#define AL_EAXREVERB_MAX_GAINHF (1.0f) +//#define AL_EAXREVERB_DEFAULT_GAINHF (0.89f) +// +//#define AL_EAXREVERB_MIN_GAINLF (0.0f) +//#define AL_EAXREVERB_MAX_GAINLF (1.0f) +//#define AL_EAXREVERB_DEFAULT_GAINLF (1.0f) +// +//#define AL_EAXREVERB_MIN_DECAY_TIME (0.1f) +//#define AL_EAXREVERB_MAX_DECAY_TIME (20.0f) +//#define AL_EAXREVERB_DEFAULT_DECAY_TIME (1.49f) +// +//#define AL_EAXREVERB_MIN_DECAY_HFRATIO (0.1f) +//#define AL_EAXREVERB_MAX_DECAY_HFRATIO (2.0f) +//#define AL_EAXREVERB_DEFAULT_DECAY_HFRATIO (0.83f) +// +//#define AL_EAXREVERB_MIN_DECAY_LFRATIO (0.1f) +//#define AL_EAXREVERB_MAX_DECAY_LFRATIO (2.0f) +//#define AL_EAXREVERB_DEFAULT_DECAY_LFRATIO (1.0f) +// +//#define AL_EAXREVERB_MIN_REFLECTIONS_GAIN (0.0f) +//#define AL_EAXREVERB_MAX_REFLECTIONS_GAIN (3.16f) +//#define AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) +// +//#define AL_EAXREVERB_MIN_REFLECTIONS_DELAY (0.0f) +//#define AL_EAXREVERB_MAX_REFLECTIONS_DELAY (0.3f) +//#define AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) +// +//#define AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ (0.0f) +// +//#define AL_EAXREVERB_MIN_LATE_REVERB_GAIN (0.0f) +//#define AL_EAXREVERB_MAX_LATE_REVERB_GAIN (10.0f) +//#define AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) +// +//#define AL_EAXREVERB_MIN_LATE_REVERB_DELAY (0.0f) +//#define AL_EAXREVERB_MAX_LATE_REVERB_DELAY (0.1f) +//#define AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) +// +//#define AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ (0.0f) +// +//#define AL_EAXREVERB_MIN_ECHO_TIME (0.075f) +//#define AL_EAXREVERB_MAX_ECHO_TIME (0.25f) +//#define AL_EAXREVERB_DEFAULT_ECHO_TIME (0.25f) +// +//#define AL_EAXREVERB_MIN_ECHO_DEPTH (0.0f) +//#define AL_EAXREVERB_MAX_ECHO_DEPTH (1.0f) +//#define AL_EAXREVERB_DEFAULT_ECHO_DEPTH (0.0f) +// +//#define AL_EAXREVERB_MIN_MODULATION_TIME (0.04f) +//#define AL_EAXREVERB_MAX_MODULATION_TIME (4.0f) +//#define AL_EAXREVERB_DEFAULT_MODULATION_TIME (0.25f) +// +//#define AL_EAXREVERB_MIN_MODULATION_DEPTH (0.0f) +//#define AL_EAXREVERB_MAX_MODULATION_DEPTH (1.0f) +//#define AL_EAXREVERB_DEFAULT_MODULATION_DEPTH (0.0f) +// +//#define AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) +//#define AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) +//#define AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) +// +//#define AL_EAXREVERB_MIN_HFREFERENCE (1000.0f) +//#define AL_EAXREVERB_MAX_HFREFERENCE (20000.0f) +//#define AL_EAXREVERB_DEFAULT_HFREFERENCE (5000.0f) +// +//#define AL_EAXREVERB_MIN_LFREFERENCE (20.0f) +//#define AL_EAXREVERB_MAX_LFREFERENCE (1000.0f) +//#define AL_EAXREVERB_DEFAULT_LFREFERENCE (250.0f) +// +//#define AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +//#define AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +//#define AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) +// +//#define AL_EAXREVERB_MIN_DECAY_HFLIMIT AL_FALSE +//#define AL_EAXREVERB_MAX_DECAY_HFLIMIT AL_TRUE +//#define AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE +// +///* Chorus effect */ +//#define AL_CHORUS_WAVEFORM_SINUSOID (0) +//#define AL_CHORUS_WAVEFORM_TRIANGLE (1) +// +//#define AL_CHORUS_MIN_WAVEFORM (0) +//#define AL_CHORUS_MAX_WAVEFORM (1) +//#define AL_CHORUS_DEFAULT_WAVEFORM (1) +// +//#define AL_CHORUS_MIN_PHASE (-180) +//#define AL_CHORUS_MAX_PHASE (180) +//#define AL_CHORUS_DEFAULT_PHASE (90) +// +//#define AL_CHORUS_MIN_RATE (0.0f) +//#define AL_CHORUS_MAX_RATE (10.0f) +//#define AL_CHORUS_DEFAULT_RATE (1.1f) +// +//#define AL_CHORUS_MIN_DEPTH (0.0f) +//#define AL_CHORUS_MAX_DEPTH (1.0f) +//#define AL_CHORUS_DEFAULT_DEPTH (0.1f) +// +//#define AL_CHORUS_MIN_FEEDBACK (-1.0f) +//#define AL_CHORUS_MAX_FEEDBACK (1.0f) +//#define AL_CHORUS_DEFAULT_FEEDBACK (0.25f) +// +//#define AL_CHORUS_MIN_DELAY (0.0f) +//#define AL_CHORUS_MAX_DELAY (0.016f) +//#define AL_CHORUS_DEFAULT_DELAY (0.016f) +// +///* Distortion effect */ +//#define AL_DISTORTION_MIN_EDGE (0.0f) +//#define AL_DISTORTION_MAX_EDGE (1.0f) +//#define AL_DISTORTION_DEFAULT_EDGE (0.2f) +// +//#define AL_DISTORTION_MIN_GAIN (0.01f) +//#define AL_DISTORTION_MAX_GAIN (1.0f) +//#define AL_DISTORTION_DEFAULT_GAIN (0.05f) +// +//#define AL_DISTORTION_MIN_LOWPASS_CUTOFF (80.0f) +//#define AL_DISTORTION_MAX_LOWPASS_CUTOFF (24000.0f) +//#define AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF (8000.0f) +// +//#define AL_DISTORTION_MIN_EQCENTER (80.0f) +//#define AL_DISTORTION_MAX_EQCENTER (24000.0f) +//#define AL_DISTORTION_DEFAULT_EQCENTER (3600.0f) +// +//#define AL_DISTORTION_MIN_EQBANDWIDTH (80.0f) +//#define AL_DISTORTION_MAX_EQBANDWIDTH (24000.0f) +//#define AL_DISTORTION_DEFAULT_EQBANDWIDTH (3600.0f) +// +///* Echo effect */ +//#define AL_ECHO_MIN_DELAY (0.0f) +//#define AL_ECHO_MAX_DELAY (0.207f) +//#define AL_ECHO_DEFAULT_DELAY (0.1f) +// +//#define AL_ECHO_MIN_LRDELAY (0.0f) +//#define AL_ECHO_MAX_LRDELAY (0.404f) +//#define AL_ECHO_DEFAULT_LRDELAY (0.1f) +// +//#define AL_ECHO_MIN_DAMPING (0.0f) +//#define AL_ECHO_MAX_DAMPING (0.99f) +//#define AL_ECHO_DEFAULT_DAMPING (0.5f) +// +//#define AL_ECHO_MIN_FEEDBACK (0.0f) +//#define AL_ECHO_MAX_FEEDBACK (1.0f) +//#define AL_ECHO_DEFAULT_FEEDBACK (0.5f) +// +//#define AL_ECHO_MIN_SPREAD (-1.0f) +//#define AL_ECHO_MAX_SPREAD (1.0f) +//#define AL_ECHO_DEFAULT_SPREAD (-1.0f) +// +///* Flanger effect */ +//#define AL_FLANGER_WAVEFORM_SINUSOID (0) +//#define AL_FLANGER_WAVEFORM_TRIANGLE (1) +// +//#define AL_FLANGER_MIN_WAVEFORM (0) +//#define AL_FLANGER_MAX_WAVEFORM (1) +//#define AL_FLANGER_DEFAULT_WAVEFORM (1) +// +//#define AL_FLANGER_MIN_PHASE (-180) +//#define AL_FLANGER_MAX_PHASE (180) +//#define AL_FLANGER_DEFAULT_PHASE (0) +// +//#define AL_FLANGER_MIN_RATE (0.0f) +//#define AL_FLANGER_MAX_RATE (10.0f) +//#define AL_FLANGER_DEFAULT_RATE (0.27f) +// +//#define AL_FLANGER_MIN_DEPTH (0.0f) +//#define AL_FLANGER_MAX_DEPTH (1.0f) +//#define AL_FLANGER_DEFAULT_DEPTH (1.0f) +// +//#define AL_FLANGER_MIN_FEEDBACK (-1.0f) +//#define AL_FLANGER_MAX_FEEDBACK (1.0f) +//#define AL_FLANGER_DEFAULT_FEEDBACK (-0.5f) +// +//#define AL_FLANGER_MIN_DELAY (0.0f) +//#define AL_FLANGER_MAX_DELAY (0.004f) +//#define AL_FLANGER_DEFAULT_DELAY (0.002f) +// +///* Frequency shifter effect */ +//#define AL_FREQUENCY_SHIFTER_MIN_FREQUENCY (0.0f) +//#define AL_FREQUENCY_SHIFTER_MAX_FREQUENCY (24000.0f) +//#define AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY (0.0f) +// +//#define AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION (0) +//#define AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION (2) +//#define AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION (0) +// +//#define AL_FREQUENCY_SHIFTER_DIRECTION_DOWN (0) +//#define AL_FREQUENCY_SHIFTER_DIRECTION_UP (1) +//#define AL_FREQUENCY_SHIFTER_DIRECTION_OFF (2) +// +//#define AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION (0) +//#define AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION (2) +//#define AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION (0) +// +///* Vocal morpher effect */ +//#define AL_VOCAL_MORPHER_MIN_PHONEMEA (0) +//#define AL_VOCAL_MORPHER_MAX_PHONEMEA (29) +//#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA (0) +// +//#define AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING (-24) +//#define AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING (24) +//#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING (0) +// +//#define AL_VOCAL_MORPHER_MIN_PHONEMEB (0) +//#define AL_VOCAL_MORPHER_MAX_PHONEMEB (29) +//#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB (10) +// +//#define AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING (-24) +//#define AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING (24) +//#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING (0) +// +//#define AL_VOCAL_MORPHER_PHONEME_A (0) +//#define AL_VOCAL_MORPHER_PHONEME_E (1) +//#define AL_VOCAL_MORPHER_PHONEME_I (2) +//#define AL_VOCAL_MORPHER_PHONEME_O (3) +//#define AL_VOCAL_MORPHER_PHONEME_U (4) +//#define AL_VOCAL_MORPHER_PHONEME_AA (5) +//#define AL_VOCAL_MORPHER_PHONEME_AE (6) +//#define AL_VOCAL_MORPHER_PHONEME_AH (7) +//#define AL_VOCAL_MORPHER_PHONEME_AO (8) +//#define AL_VOCAL_MORPHER_PHONEME_EH (9) +//#define AL_VOCAL_MORPHER_PHONEME_ER (10) +//#define AL_VOCAL_MORPHER_PHONEME_IH (11) +//#define AL_VOCAL_MORPHER_PHONEME_IY (12) +//#define AL_VOCAL_MORPHER_PHONEME_UH (13) +//#define AL_VOCAL_MORPHER_PHONEME_UW (14) +//#define AL_VOCAL_MORPHER_PHONEME_B (15) +//#define AL_VOCAL_MORPHER_PHONEME_D (16) +//#define AL_VOCAL_MORPHER_PHONEME_F (17) +//#define AL_VOCAL_MORPHER_PHONEME_G (18) +//#define AL_VOCAL_MORPHER_PHONEME_J (19) +//#define AL_VOCAL_MORPHER_PHONEME_K (20) +//#define AL_VOCAL_MORPHER_PHONEME_L (21) +//#define AL_VOCAL_MORPHER_PHONEME_M (22) +//#define AL_VOCAL_MORPHER_PHONEME_N (23) +//#define AL_VOCAL_MORPHER_PHONEME_P (24) +//#define AL_VOCAL_MORPHER_PHONEME_R (25) +//#define AL_VOCAL_MORPHER_PHONEME_S (26) +//#define AL_VOCAL_MORPHER_PHONEME_T (27) +//#define AL_VOCAL_MORPHER_PHONEME_V (28) +//#define AL_VOCAL_MORPHER_PHONEME_Z (29) +// +//#define AL_VOCAL_MORPHER_WAVEFORM_SINUSOID (0) +//#define AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE (1) +//#define AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH (2) +// +//#define AL_VOCAL_MORPHER_MIN_WAVEFORM (0) +//#define AL_VOCAL_MORPHER_MAX_WAVEFORM (2) +//#define AL_VOCAL_MORPHER_DEFAULT_WAVEFORM (0) +// +//#define AL_VOCAL_MORPHER_MIN_RATE (0.0f) +//#define AL_VOCAL_MORPHER_MAX_RATE (10.0f) +//#define AL_VOCAL_MORPHER_DEFAULT_RATE (1.41f) +// +///* Pitch shifter effect */ +//#define AL_PITCH_SHIFTER_MIN_COARSE_TUNE (-12) +//#define AL_PITCH_SHIFTER_MAX_COARSE_TUNE (12) +//#define AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE (12) +// +//#define AL_PITCH_SHIFTER_MIN_FINE_TUNE (-50) +//#define AL_PITCH_SHIFTER_MAX_FINE_TUNE (50) +//#define AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE (0) +// +///* Ring modulator effect */ +//#define AL_RING_MODULATOR_MIN_FREQUENCY (0.0f) +//#define AL_RING_MODULATOR_MAX_FREQUENCY (8000.0f) +//#define AL_RING_MODULATOR_DEFAULT_FREQUENCY (440.0f) +// +//#define AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF (0.0f) +//#define AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF (24000.0f) +//#define AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF (800.0f) +// +//#define AL_RING_MODULATOR_SINUSOID (0) +//#define AL_RING_MODULATOR_SAWTOOTH (1) +//#define AL_RING_MODULATOR_SQUARE (2) +// +//#define AL_RING_MODULATOR_MIN_WAVEFORM (0) +//#define AL_RING_MODULATOR_MAX_WAVEFORM (2) +//#define AL_RING_MODULATOR_DEFAULT_WAVEFORM (0) +// +///* Autowah effect */ +//#define AL_AUTOWAH_MIN_ATTACK_TIME (0.0001f) +//#define AL_AUTOWAH_MAX_ATTACK_TIME (1.0f) +//#define AL_AUTOWAH_DEFAULT_ATTACK_TIME (0.06f) +// +//#define AL_AUTOWAH_MIN_RELEASE_TIME (0.0001f) +//#define AL_AUTOWAH_MAX_RELEASE_TIME (1.0f) +//#define AL_AUTOWAH_DEFAULT_RELEASE_TIME (0.06f) +// +//#define AL_AUTOWAH_MIN_RESONANCE (2.0f) +//#define AL_AUTOWAH_MAX_RESONANCE (1000.0f) +//#define AL_AUTOWAH_DEFAULT_RESONANCE (1000.0f) +// +//#define AL_AUTOWAH_MIN_PEAK_GAIN (0.00003f) +//#define AL_AUTOWAH_MAX_PEAK_GAIN (31621.0f) +//#define AL_AUTOWAH_DEFAULT_PEAK_GAIN (11.22f) +// +///* Compressor effect */ +//#define AL_COMPRESSOR_MIN_ONOFF (0) +//#define AL_COMPRESSOR_MAX_ONOFF (1) +//#define AL_COMPRESSOR_DEFAULT_ONOFF (1) +// +///* Equalizer effect */ +//#define AL_EQUALIZER_MIN_LOW_GAIN (0.126f) +//#define AL_EQUALIZER_MAX_LOW_GAIN (7.943f) +//#define AL_EQUALIZER_DEFAULT_LOW_GAIN (1.0f) +// +//#define AL_EQUALIZER_MIN_LOW_CUTOFF (50.0f) +//#define AL_EQUALIZER_MAX_LOW_CUTOFF (800.0f) +//#define AL_EQUALIZER_DEFAULT_LOW_CUTOFF (200.0f) +// +//#define AL_EQUALIZER_MIN_MID1_GAIN (0.126f) +//#define AL_EQUALIZER_MAX_MID1_GAIN (7.943f) +//#define AL_EQUALIZER_DEFAULT_MID1_GAIN (1.0f) +// +//#define AL_EQUALIZER_MIN_MID1_CENTER (200.0f) +//#define AL_EQUALIZER_MAX_MID1_CENTER (3000.0f) +//#define AL_EQUALIZER_DEFAULT_MID1_CENTER (500.0f) +// +//#define AL_EQUALIZER_MIN_MID1_WIDTH (0.01f) +//#define AL_EQUALIZER_MAX_MID1_WIDTH (1.0f) +//#define AL_EQUALIZER_DEFAULT_MID1_WIDTH (1.0f) +// +//#define AL_EQUALIZER_MIN_MID2_GAIN (0.126f) +//#define AL_EQUALIZER_MAX_MID2_GAIN (7.943f) +//#define AL_EQUALIZER_DEFAULT_MID2_GAIN (1.0f) +// +//#define AL_EQUALIZER_MIN_MID2_CENTER (1000.0f) +//#define AL_EQUALIZER_MAX_MID2_CENTER (8000.0f) +//#define AL_EQUALIZER_DEFAULT_MID2_CENTER (3000.0f) +// +//#define AL_EQUALIZER_MIN_MID2_WIDTH (0.01f) +//#define AL_EQUALIZER_MAX_MID2_WIDTH (1.0f) +//#define AL_EQUALIZER_DEFAULT_MID2_WIDTH (1.0f) +// +//#define AL_EQUALIZER_MIN_HIGH_GAIN (0.126f) +//#define AL_EQUALIZER_MAX_HIGH_GAIN (7.943f) +//#define AL_EQUALIZER_DEFAULT_HIGH_GAIN (1.0f) +// +//#define AL_EQUALIZER_MIN_HIGH_CUTOFF (4000.0f) +//#define AL_EQUALIZER_MAX_HIGH_CUTOFF (16000.0f) +//#define AL_EQUALIZER_DEFAULT_HIGH_CUTOFF (6000.0f) +// +// +///* Source parameter value ranges and defaults. */ +//#define AL_MIN_AIR_ABSORPTION_FACTOR (0.0f) +//#define AL_MAX_AIR_ABSORPTION_FACTOR (10.0f) +//#define AL_DEFAULT_AIR_ABSORPTION_FACTOR (0.0f) +// +//#define AL_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +//#define AL_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +//#define AL_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) +// +//#define AL_MIN_CONE_OUTER_GAINHF (0.0f) +//#define AL_MAX_CONE_OUTER_GAINHF (1.0f) +//#define AL_DEFAULT_CONE_OUTER_GAINHF (1.0f) +// +//#define AL_MIN_DIRECT_FILTER_GAINHF_AUTO AL_FALSE +//#define AL_MAX_DIRECT_FILTER_GAINHF_AUTO AL_TRUE +//#define AL_DEFAULT_DIRECT_FILTER_GAINHF_AUTO AL_TRUE +// +//#define AL_MIN_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_FALSE +//#define AL_MAX_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE +//#define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE +// +//#define AL_MIN_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_FALSE +//#define AL_MAX_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE +//#define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE +// +// +///* Listener parameter value ranges and defaults. */ +//#define AL_MIN_METERS_PER_UNIT FLT_MIN +//#define AL_MAX_METERS_PER_UNIT FLT_MAX +//#define AL_DEFAULT_METERS_PER_UNIT (1.0f) + + + public static String GetALErrorMsg(int errorCode) { + String errorText; + switch (errorCode) { + case AL_NO_ERROR: + errorText = "No Error"; + break; + case AL_INVALID_NAME: + errorText = "Invalid Name"; + break; + case AL_INVALID_ENUM: + errorText = "Invalid Enum"; + break; + case AL_INVALID_VALUE: + errorText = "Invalid Value"; + break; + case AL_INVALID_OPERATION: + errorText = "Invalid Operation"; + break; + case AL_OUT_OF_MEMORY: + errorText = "Out of Memory"; + break; + default: + errorText = "Unknown Error Code: " + String.valueOf(errorCode); + } + return errorText; + } +} + diff --git a/jme3-android/src/main/java/com/jme3/audio/android/AndroidAudioData.java b/jme3-android/src/main/java/com/jme3/audio/android/AndroidAudioData.java new file mode 100644 index 000000000..e7f4a0f98 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/audio/android/AndroidAudioData.java @@ -0,0 +1,67 @@ +package com.jme3.audio.android; + +import com.jme3.asset.AssetKey; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioRenderer; +import com.jme3.util.NativeObject; + +public class AndroidAudioData extends AudioData { + + protected AssetKey assetKey; + protected float currentVolume = 0f; + + public AndroidAudioData(){ + super(); + } + + protected AndroidAudioData(int id){ + super(id); + } + + public AssetKey getAssetKey() { + return assetKey; + } + + public void setAssetKey(AssetKey assetKey) { + this.assetKey = assetKey; + } + + @Override + public DataType getDataType() { + return DataType.Buffer; + } + + @Override + public float getDuration() { + return 0; // TODO: ??? + } + + @Override + public void resetObject() { + this.id = -1; + setUpdateNeeded(); + } + + @Override + public void deleteObject(Object rendererObject) { + ((AudioRenderer)rendererObject).deleteAudioData(this); + } + + public float getCurrentVolume() { + return currentVolume; + } + + public void setCurrentVolume(float currentVolume) { + this.currentVolume = currentVolume; + } + + @Override + public NativeObject createDestructableClone() { + return new AndroidAudioData(id); + } + + @Override + public long getUniqueId() { + return ((long)OBJTYPE_AUDIOBUFFER << 32) | ((long)id); + } +} diff --git a/jme3-android/src/main/java/com/jme3/audio/android/AndroidAudioRenderer.java b/jme3-android/src/main/java/com/jme3/audio/android/AndroidAudioRenderer.java new file mode 100644 index 000000000..0cde16aa5 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/audio/android/AndroidAudioRenderer.java @@ -0,0 +1,24 @@ +package com.jme3.audio.android; + +import com.jme3.audio.AudioRenderer; + +/** + * Android specific AudioRenderer interface that supports pausing and resuming + * audio files when the app is minimized or placed in the background + * + * @author iwgeric + */ +public interface AndroidAudioRenderer extends AudioRenderer { + + /** + * Pauses all Playing audio. To be used when the app is placed in the + * background. + */ + public void pauseAll(); + + /** + * Resumes all Paused audio. To be used when the app is brought back to + * the foreground. + */ + public void resumeAll(); +} diff --git a/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java b/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java new file mode 100644 index 000000000..df1bea3b6 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java @@ -0,0 +1,523 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio.android; + +import android.app.Activity; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.SoundPool; +import com.jme3.asset.AssetKey; +import com.jme3.audio.*; +import com.jme3.audio.AudioSource.Status; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class is the android implementation for {@link AudioRenderer} + * + * @author larynx + * @author plan_rich + */ +public class AndroidMediaPlayerAudioRenderer implements AndroidAudioRenderer, + SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener { + + private static final Logger logger = Logger.getLogger(AndroidMediaPlayerAudioRenderer.class.getName()); + private final static int MAX_NUM_CHANNELS = 16; + private final HashMap musicPlaying = new HashMap(); + private SoundPool soundPool = null; + private final Vector3f listenerPosition = new Vector3f(); + // For temp use + private final Vector3f distanceVector = new Vector3f(); + private final AssetManager assetManager; + private HashMap soundpoolStillLoading = new HashMap(); + private Listener listener; + private boolean audioDisabled = false; + private final AudioManager manager; + + public AndroidMediaPlayerAudioRenderer(Activity context) { + manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + context.setVolumeControlStream(AudioManager.STREAM_MUSIC); + assetManager = context.getAssets(); + } + + @Override + public void initialize() { + soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC, + 0); + soundPool.setOnLoadCompleteListener(this); + } + + @Override + public void updateSourceParam(AudioSource src, AudioParam param) { + if (audioDisabled) { + return; + } + + if (src.getChannel() < 0) { + return; + } + + switch (param) { + case Position: + if (!src.isPositional()) { + return; + } + + Vector3f pos = src.getPosition(); + break; + case Velocity: + if (!src.isPositional()) { + return; + } + + Vector3f vel = src.getVelocity(); + break; + case MaxDistance: + if (!src.isPositional()) { + return; + } + break; + case RefDistance: + if (!src.isPositional()) { + return; + } + break; + case ReverbFilter: + if (!src.isPositional() || !src.isReverbEnabled()) { + return; + } + break; + case ReverbEnabled: + if (!src.isPositional()) { + return; + } + + if (src.isReverbEnabled()) { + updateSourceParam(src, AudioParam.ReverbFilter); + } + break; + case IsPositional: + break; + case Direction: + if (!src.isDirectional()) { + return; + } + + Vector3f dir = src.getDirection(); + break; + case InnerAngle: + if (!src.isDirectional()) { + return; + } + break; + case OuterAngle: + if (!src.isDirectional()) { + return; + } + break; + case IsDirectional: + if (src.isDirectional()) { + updateSourceParam(src, AudioParam.Direction); + updateSourceParam(src, AudioParam.InnerAngle); + updateSourceParam(src, AudioParam.OuterAngle); + } else { + } + break; + case DryFilter: + if (src.getDryFilter() != null) { + Filter f = src.getDryFilter(); + if (f.isUpdateNeeded()) { + // updateFilter(f); + } + } + break; + case Looping: + if (src.isLooping()) { + } + break; + case Volume: + MediaPlayer mp = musicPlaying.get(src); + if (mp != null) { + mp.setVolume(src.getVolume(), src.getVolume()); + } else { + soundPool.setVolume(src.getChannel(), src.getVolume(), + src.getVolume()); + } + + break; + case Pitch: + + break; + } + + } + + @Override + public void updateListenerParam(Listener listener, ListenerParam param) { + if (audioDisabled) { + return; + } + + switch (param) { + case Position: + listenerPosition.set(listener.getLocation()); + break; + case Rotation: + Vector3f dir = listener.getDirection(); + Vector3f up = listener.getUp(); + + break; + case Velocity: + Vector3f vel = listener.getVelocity(); + + break; + case Volume: + // alListenerf(AL_GAIN, listener.getVolume()); + break; + } + + } + + @Override + public void update(float tpf) { + float distance; + float volume; + + // Loop over all mediaplayers + for (AudioSource src : musicPlaying.keySet()) { + + MediaPlayer mp = musicPlaying.get(src); + + // Calc the distance to the listener + distanceVector.set(listenerPosition); + distanceVector.subtractLocal(src.getPosition()); + distance = FastMath.abs(distanceVector.length()); + + if (distance < src.getRefDistance()) { + distance = src.getRefDistance(); + } + if (distance > src.getMaxDistance()) { + distance = src.getMaxDistance(); + } + volume = src.getRefDistance() / distance; + + AndroidAudioData audioData = (AndroidAudioData) src.getAudioData(); + + if (FastMath.abs(audioData.getCurrentVolume() - volume) > FastMath.FLT_EPSILON) { + // Left / Right channel get the same volume by now, only + // positional + mp.setVolume(volume, volume); + + audioData.setCurrentVolume(volume); + } + + } + } + + public void setListener(Listener listener) { + if (audioDisabled) { + return; + } + + if (this.listener != null) { + // previous listener no longer associated with current + // renderer + this.listener.setRenderer(null); + } + + this.listener = listener; + this.listener.setRenderer(this); + + } + + @Override + public void cleanup() { + // Cleanup sound pool + if (soundPool != null) { + soundPool.release(); + soundPool = null; + } + + // Cleanup media player + for (AudioSource src : musicPlaying.keySet()) { + MediaPlayer mp = musicPlaying.get(src); + { + mp.stop(); + mp.release(); + src.setStatus(Status.Stopped); + } + } + musicPlaying.clear(); + } + + @Override + public void onCompletion(MediaPlayer mp) { + if (mp.isPlaying()) { + mp.seekTo(0); + mp.stop(); + } + // XXX: This has bad performance -> maybe change overall structure of + // mediaplayer in this audiorenderer? + for (AudioSource src : musicPlaying.keySet()) { + if (musicPlaying.get(src) == mp) { + src.setStatus(Status.Stopped); + break; + } + } + + } + + /** + * Plays using the {@link SoundPool} of Android. Due to hard limitation of + * the SoundPool: After playing more instances of the sound you only have + * the channel of the last played instance. + * + * It is not possible to get information about the state of the soundpool of + * a specific streamid, so removing is not possilbe -> noone knows when + * sound finished. + */ + public void playSourceInstance(AudioSource src) { + if (audioDisabled) { + return; + } + + AndroidAudioData audioData = (AndroidAudioData) src.getAudioData(); + + if (!(audioData.getAssetKey() instanceof AudioKey)) { + throw new IllegalArgumentException("Asset is not a AudioKey"); + } + + AudioKey assetKey = (AudioKey) audioData.getAssetKey(); + + try { + + if (audioData.getId() < 0) { // found something to load + int soundId = soundPool.load( + assetManager.openFd(assetKey.getName()), 1); + audioData.setId(soundId); + } + + int channel = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f); + + if (channel == 0) { + soundpoolStillLoading.put(audioData.getId(), src); + } else { + if (src.getStatus() != Status.Stopped) { + soundPool.stop(channel); + src.setStatus(Status.Stopped); + } + src.setChannel(channel); // receive a channel at the last + setSourceParams(src); + // playing at least + + + } + } catch (IOException e) { + logger.log(Level.SEVERE, + "Failed to load sound " + assetKey.getName(), e); + audioData.setId(-1); + } + } + + @Override + public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { + AudioSource src = soundpoolStillLoading.remove(sampleId); + + if (src == null) { + logger.warning("Something went terribly wrong! onLoadComplete" + + " had sampleId which was not in the HashMap of loading items"); + return; + } + + AudioData audioData = src.getAudioData(); + + // load was successfull + if (status == 0) { + int channelIndex; + channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f); + src.setChannel(channelIndex); + setSourceParams(src); + } + } + + public void playSource(AudioSource src) { + if (audioDisabled) { + return; + } + + AndroidAudioData audioData = (AndroidAudioData) src.getAudioData(); + + MediaPlayer mp = musicPlaying.get(src); + if (mp == null) { + mp = new MediaPlayer(); + mp.setOnCompletionListener(this); + mp.setAudioStreamType(AudioManager.STREAM_MUSIC); + } + + try { + if (src.getStatus() == Status.Stopped) { + mp.reset(); + AssetKey key = audioData.getAssetKey(); + + AssetFileDescriptor afd = assetManager.openFd(key.getName()); // assetKey.getName() + mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), + afd.getLength()); + mp.prepare(); + setSourceParams(src, mp); + src.setChannel(0); + src.setStatus(Status.Playing); + musicPlaying.put(src, mp); + mp.start(); + } else { + mp.start(); + } + } catch (IllegalStateException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void setSourceParams(AudioSource src, MediaPlayer mp) { + mp.setLooping(src.isLooping()); + mp.setVolume(src.getVolume(), src.getVolume()); + //src.getDryFilter(); + } + + private void setSourceParams(AudioSource src) { + soundPool.setLoop(src.getChannel(), src.isLooping() ? -1 : 0); + soundPool.setVolume(src.getChannel(), src.getVolume(), src.getVolume()); + } + + /** + * Pause the current playing sounds. Both from the {@link SoundPool} and the + * active {@link MediaPlayer}s + */ + public void pauseAll() { + if (soundPool != null) { + soundPool.autoPause(); + for (MediaPlayer mp : musicPlaying.values()) { + if(mp.isPlaying()){ + mp.pause(); + } + } + } + } + + /** + * Resume all paused sounds. + */ + public void resumeAll() { + if (soundPool != null) { + soundPool.autoResume(); + for (MediaPlayer mp : musicPlaying.values()) { + mp.start(); //no resume -> api says call start to resume + } + } + } + + public void pauseSource(AudioSource src) { + if (audioDisabled) { + return; + } + + MediaPlayer mp = musicPlaying.get(src); + if (mp != null) { + mp.pause(); + src.setStatus(Status.Paused); + } else { + int channel = src.getChannel(); + if (channel != -1) { + soundPool.pause(channel); // is not very likley to make + } // something useful :) + } + } + + public void stopSource(AudioSource src) { + if (audioDisabled) { + return; + } + + // can be stream or buffer -> so try to get mediaplayer + // if there is non try to stop soundpool + MediaPlayer mp = musicPlaying.get(src); + if (mp != null) { + mp.stop(); + mp.reset(); + src.setStatus(Status.Stopped); + } else { + int channel = src.getChannel(); + if (channel != -1) { + soundPool.pause(channel); // is not very likley to make + // something useful :) + } + } + + } + + @Override + public void deleteAudioData(AudioData ad) { + + for (AudioSource src : musicPlaying.keySet()) { + if (src.getAudioData() == ad) { + MediaPlayer mp = musicPlaying.remove(src); + mp.stop(); + mp.release(); + src.setStatus(Status.Stopped); + src.setChannel(-1); + ad.setId(-1); + break; + } + } + + if (ad.getId() > 0) { + soundPool.unload(ad.getId()); + ad.setId(-1); + } + } + + @Override + public void setEnvironment(Environment env) { + // not yet supported + } + + @Override + public void deleteFilter(Filter filter) { + } +} diff --git a/jme3-android/src/main/java/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.java b/jme3-android/src/main/java/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.java new file mode 100644 index 000000000..e5c98ebec --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.java @@ -0,0 +1,1423 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio.android; + +import com.jme3.audio.*; +import com.jme3.audio.AudioSource.Status; +import com.jme3.math.Vector3f; +import com.jme3.util.BufferUtils; +import com.jme3.util.NativeObjectManager; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class AndroidOpenALSoftAudioRenderer implements AndroidAudioRenderer, Runnable { + + private static final Logger logger = Logger.getLogger(AndroidOpenALSoftAudioRenderer.class.getName()); + private final NativeObjectManager objManager = new NativeObjectManager(); + // When multiplied by STREAMING_BUFFER_COUNT, will equal 44100 * 2 * 2 + // which is exactly 1 second of audio. + private static final int BUFFER_SIZE = 35280; + private static final int STREAMING_BUFFER_COUNT = 5; + private final static int MAX_NUM_CHANNELS = 64; + private IntBuffer ib = BufferUtils.createIntBuffer(1); + private final FloatBuffer fb = BufferUtils.createVector3Buffer(2); + private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE); + private final byte[] arrayBuf = new byte[BUFFER_SIZE]; + private int[] channels; + private AudioSource[] 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; + // Update audio 20 times per second + private static final float UPDATE_RATE = 0.05f; + private final Thread audioThread = new Thread(this, "jME3 Audio Thread"); + private final AtomicBoolean threadLock = new AtomicBoolean(false); + private boolean initialized = false; + + public AndroidOpenALSoftAudioRenderer() { + } + + 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(); + } + + initialized = true; + + 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; + } + } + } + } + + initialized = false; + + synchronized (threadLock) { + cleanupInThread(); + } + } + + public void initInThread() { + try { + if (!alIsCreated()) { + //AL.create(); + alCreate(); + checkError(false); + } +// } catch (OpenALException ex) { +// logger.log(Level.SEVERE, "Failed to load audio library", ex); +// audioDisabled = true; +// return; +// } catch (LWJGLException ex) { +// logger.log(Level.SEVERE, "Failed to load audio library", ex); +// audioDisabled = true; +// return; + } catch (UnsatisfiedLinkError ex) { + logger.log(Level.SEVERE, "Failed to load audio library", ex); + audioDisabled = true; + return; + } + + //ALCdevice device = AL.getDevice(); /* device maintained in jni */ + //String deviceName = ALC10.alcGetString(device, ALC10.ALC_DEVICE_SPECIFIER); + String deviceName = alcGetString(AL.ALC_DEVICE_SPECIFIER); + + logger.log(Level.INFO, "Audio Device: {0}", deviceName); + //logger.log(Level.INFO, "Audio Vendor: {0}", alGetString(AL_VENDOR)); + //logger.log(Level.INFO, "Audio Renderer: {0}", alGetString(AL_RENDERER)); + //logger.log(Level.INFO, "Audio Version: {0}", alGetString(AL_VERSION)); + logger.log(Level.INFO, "Audio Vendor: {0}", alGetString(AL.AL_VENDOR)); + logger.log(Level.INFO, "Audio Renderer: {0}", alGetString(AL.AL_RENDERER)); + logger.log(Level.INFO, "Audio Version: {0}", alGetString(AL.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) { + if (checkError(false) != 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 AudioSource[channels.length]; + + logger.log(Level.INFO, "AudioRenderer supports {0} channels", channels.length); + + //supportEfx = alcIsExtensionPresent(device, "ALC_EXT_EFX"); + supportEfx = alcIsExtensionPresent(AL.ALC_EXT_EFX_NAME); + + if (supportEfx) { + ib.position(0).limit(1); + //ALC10.alcGetInteger(device, EFX10.ALC_EFX_MAJOR_VERSION, ib); + alcGetInteger(AL.ALC_EFX_MAJOR_VERSION, ib, 1); + int major = ib.get(0); + ib.position(0).limit(1); + //ALC10.alcGetInteger(device, EFX10.ALC_EFX_MINOR_VERSION, ib); + alcGetInteger(AL.ALC_EFX_MINOR_VERSION, ib, 1); + 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); + alcGetInteger(AL.ALC_MAX_AUXILIARY_SENDS, ib, 1); + auxSends = ib.get(0); + logger.log(Level.INFO, "Audio max auxilary sends: {0}", auxSends); + + // create slot + ib.position(0).limit(1); + //EFX10.alGenAuxiliaryEffectSlots(ib); + alGenAuxiliaryEffectSlots(1, ib); + reverbFxSlot = ib.get(0); + + // create effect + ib.position(0).limit(1); + //EFX10.alGenEffects(ib); + alGenEffects(1, ib); + reverbFx = ib.get(0); + //EFX10.alEffecti(reverbFx, EFX10.AL_EFFECT_TYPE, EFX10.AL_EFFECT_REVERB); + alEffecti(reverbFx, AL.AL_EFFECT_TYPE, AL.AL_EFFECT_REVERB); + + // attach reverb effect to effect slot + //EFX10.alAuxiliaryEffectSloti(reverbFxSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbFx); + alAuxiliaryEffectSloti(reverbFxSlot, AL.AL_EFFECTSLOT_EFFECT, reverbFx); + } else { + logger.log(Level.WARNING, "OpenAL EFX not available! Audio effects won't work."); + } + } + + public void cleanupInThread() { + if (audioDisabled) { + //AL.destroy(); + alDestroy(); + checkError(true); + return; + } + + // stop any playing channels + for (int i = 0; i < chanSrcs.length; i++) { + if (chanSrcs[i] != null) { + clearChannel(i); + } + } + + // delete channel-based sources + ib.clear(); + ib.put(channels); + ib.flip(); + //alDeleteSources(ib); + alDeleteSources(channels.length, ib); + checkError(true); + + // delete audio buffers and filters + objManager.deleteAllObjects(this); + + if (supportEfx) { + ib.position(0).limit(1); + ib.put(0, reverbFx); + //EFX10.alDeleteEffects(ib); + alDeleteEffects(1, ib); + + // If this is not allocated, why is it deleted? + // Commented out to fix native crash in OpenAL. + ib.position(0).limit(1); + ib.put(0, reverbFxSlot); + //EFX10.alDeleteAuxiliaryEffectSlots(ib); + alDeleteAuxiliaryEffectSlots(1, ib); + } + + //AL.destroy(); + logger.log(Level.INFO, "Destroying OpenAL Soft Renderer"); + alDestroy(); + } + + 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); + alGenFilters(1, ib); + id = ib.get(0); + f.setId(id); + + objManager.registerObject(f); + } + + if (f instanceof LowPassFilter) { + LowPassFilter lpf = (LowPassFilter) f; + //EFX10.alFilteri(id, EFX10.AL_FILTER_TYPE, EFX10.AL_FILTER_LOWPASS); + alFilteri(id, AL.AL_FILTER_TYPE, AL.AL_FILTER_LOWPASS); + //EFX10.alFilterf(id, EFX10.AL_LOWPASS_GAIN, lpf.getVolume()); + alFilterf(id, AL.AL_LOWPASS_GAIN, lpf.getVolume()); + //EFX10.alFilterf(id, EFX10.AL_LOWPASS_GAINHF, lpf.getHighFreqVolume()); + alFilterf(id, AL.AL_LOWPASS_GAINHF, lpf.getHighFreqVolume()); + } else { + throw new UnsupportedOperationException("Filter type unsupported: " + + f.getClass().getName()); + } + + f.clearUpdateNeeded(); + } + + public void updateSourceParam(AudioSource src, AudioParam param) { + checkDead(); + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled) { + return; + } + + // There is a race condition in AudioSource that can + // cause this to be called for a node that has been + // detached from its channel. For example, setVolume() + // called from the render thread may see that that AudioSource + // still has a channel value but the audio thread may + // clear that channel before setVolume() gets to call + // updateSourceParam() (because the audio stopped playing + // on its own right as the volume was set). In this case, + // it should be safe to just ignore the update + if (src.getChannel() < 0) { + return; + } + + assert src.getChannel() >= 0; + + int id = channels[src.getChannel()]; + switch (param) { + case Position: + if (!src.isPositional()) { + return; + } + + Vector3f pos = src.getPosition(); + //alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z); + alSource3f(id, AL.AL_POSITION, pos.x, pos.y, pos.z); + checkError(true); + break; + case Velocity: + if (!src.isPositional()) { + return; + } + + Vector3f vel = src.getVelocity(); + //alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z); + alSource3f(id, AL.AL_VELOCITY, vel.x, vel.y, vel.z); + checkError(true); + break; + case MaxDistance: + if (!src.isPositional()) { + return; + } + + //alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance()); + alSourcef(id, AL.AL_MAX_DISTANCE, src.getMaxDistance()); + checkError(true); + break; + case RefDistance: + if (!src.isPositional()) { + return; + } + + //alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance()); + alSourcef(id, AL.AL_REFERENCE_DISTANCE, src.getRefDistance()); + checkError(true); + break; + case ReverbFilter: + if (!supportEfx || !src.isPositional() || !src.isReverbEnabled()) { + return; + } + + int filter = AL.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); + alSource3i(id, AL.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); + break; + case ReverbEnabled: + if (!supportEfx || !src.isPositional()) { + return; + } + + if (src.isReverbEnabled()) { + updateSourceParam(src, AudioParam.ReverbFilter); + } else { + //AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL); + alSource3i(id, AL.AL_AUXILIARY_SEND_FILTER, 0, 0, AL.AL_FILTER_NULL); + } + break; + case IsPositional: + if (!src.isPositional()) { + // Play in headspace + //alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(id, AL.AL_SOURCE_RELATIVE, AL.AL_TRUE); + checkError(true); + //alSource3f(id, AL_POSITION, 0, 0, 0); + alSource3f(id, AL.AL_POSITION, 0, 0, 0); + checkError(true); + //alSource3f(id, AL_VELOCITY, 0, 0, 0); + alSource3f(id, AL.AL_VELOCITY, 0, 0, 0); + checkError(true); + + // Disable reverb + //AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL); + alSource3i(id, AL.AL_AUXILIARY_SEND_FILTER, 0, 0, AL.AL_FILTER_NULL); + } else { + //alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE); + alSourcei(id, AL.AL_SOURCE_RELATIVE, AL.AL_FALSE); + checkError(true); + 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); + alSource3f(id, AL.AL_DIRECTION, dir.x, dir.y, dir.z); + checkError(true); + break; + case InnerAngle: + if (!src.isDirectional()) { + return; + } + + //alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle()); + alSourcef(id, AL.AL_CONE_INNER_ANGLE, src.getInnerAngle()); + checkError(true); + break; + case OuterAngle: + if (!src.isDirectional()) { + return; + } + + //alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); + alSourcef(id, AL.AL_CONE_OUTER_ANGLE, src.getOuterAngle()); + checkError(true); + 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); + alSourcef(id, AL.AL_CONE_OUTER_GAIN, 0); + checkError(true); + } else { + //alSourcef(id, AL_CONE_INNER_ANGLE, 360); + alSourcef(id, AL.AL_CONE_INNER_ANGLE, 360); + checkError(true); + //alSourcef(id, AL_CONE_OUTER_ANGLE, 360); + alSourcef(id, AL.AL_CONE_OUTER_ANGLE, 360); + checkError(true); + //alSourcef(id, AL_CONE_OUTER_GAIN, 1f); + alSourcef(id, AL.AL_CONE_OUTER_GAIN, 1f); + checkError(true); + } + break; + case DryFilter: + if (!supportEfx) { + return; + } + + if (src.getDryFilter() != null) { + Filter f = src.getDryFilter(); + if (f.isUpdateNeeded()) { + updateFilter(f); + + // NOTE: must re-attach filter for changes to apply. + //alSourcei(id, EFX10.AL_DIRECT_FILTER, f.getId()); + alSourcei(id, AL.AL_DIRECT_FILTER, f.getId()); + } + } else { + //alSourcei(id, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL); + alSourcei(id, AL.AL_DIRECT_FILTER, AL.AL_FILTER_NULL); + } + break; + case Looping: + if (src.isLooping()) { + if (!(src.getAudioData() instanceof AudioStream)) { + //alSourcei(id, AL_LOOPING, AL_TRUE); + alSourcei(id, AL.AL_LOOPING, AL.AL_TRUE); + checkError(true); + } + } else { + //alSourcei(id, AL_LOOPING, AL_FALSE); + alSourcei(id, AL.AL_LOOPING, AL.AL_FALSE); + checkError(true); + } + break; + case Volume: + //alSourcef(id, AL_GAIN, src.getVolume()); + alSourcef(id, AL.AL_GAIN, src.getVolume()); + checkError(true); + break; + case Pitch: + //alSourcef(id, AL_PITCH, src.getPitch()); + alSourcef(id, AL.AL_PITCH, src.getPitch()); + checkError(true); + break; + } + } + } + + private void setSourceParams(int id, AudioSource src, boolean forceNonLoop) { + if (src.isPositional()) { + Vector3f pos = src.getPosition(); + Vector3f vel = src.getVelocity(); + //alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z); + alSource3f(id, AL.AL_POSITION, pos.x, pos.y, pos.z); + checkError(true); + //alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z); + alSource3f(id, AL.AL_VELOCITY, vel.x, vel.y, vel.z); + checkError(true); + //alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance()); + alSourcef(id, AL.AL_MAX_DISTANCE, src.getMaxDistance()); + checkError(true); + //alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance()); + alSourcef(id, AL.AL_REFERENCE_DISTANCE, src.getRefDistance()); + checkError(true); + //alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE); + alSourcei(id, AL.AL_SOURCE_RELATIVE, AL.AL_FALSE); + checkError(true); + + if (src.isReverbEnabled() && supportEfx) { + //int filter = EFX10.AL_FILTER_NULL; + int filter = AL.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); + alSource3i(id, AL.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); + } + } else { + // play in headspace + //alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(id, AL.AL_SOURCE_RELATIVE, AL.AL_TRUE); + checkError(true); + //alSource3f(id, AL_POSITION, 0, 0, 0); + alSource3f(id, AL.AL_POSITION, 0, 0, 0); + checkError(true); + //alSource3f(id, AL_VELOCITY, 0, 0, 0); + alSource3f(id, AL.AL_VELOCITY, 0, 0, 0); + checkError(true); + } + + if (src.getDryFilter() != null && supportEfx) { + Filter f = src.getDryFilter(); + if (f.isUpdateNeeded()) { + updateFilter(f); + + // NOTE: must re-attach filter for changes to apply. + //alSourcei(id, EFX10.AL_DIRECT_FILTER, f.getId()); + alSourcei(id, AL.AL_DIRECT_FILTER, f.getId()); + } + } + + if (forceNonLoop) { + //alSourcei(id, AL_LOOPING, AL_FALSE); + alSourcei(id, AL.AL_LOOPING, AL.AL_FALSE); + checkError(true); + } else { + //alSourcei(id, AL_LOOPING, src.isLooping() ? AL_TRUE : AL_FALSE); + alSourcei(id, AL.AL_LOOPING, src.isLooping() ? AL.AL_TRUE : AL.AL_FALSE); + checkError(true); + } + //alSourcef(id, AL_GAIN, src.getVolume()); + alSourcef(id, AL.AL_GAIN, src.getVolume()); + checkError(true); + //alSourcef(id, AL_PITCH, src.getPitch()); + alSourcef(id, AL.AL_PITCH, src.getPitch()); + checkError(true); + //alSourcef(id, AL11.AL_SEC_OFFSET, src.getTimeOffset()); + alSourcef(id, AL.AL_SEC_OFFSET, src.getTimeOffset()); + checkError(true); + + if (src.isDirectional()) { + Vector3f dir = src.getDirection(); + //alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z); + alSource3f(id, AL.AL_DIRECTION, dir.x, dir.y, dir.z); + checkError(true); + //alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle()); + alSourcef(id, AL.AL_CONE_INNER_ANGLE, src.getInnerAngle()); + checkError(true); + //alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); + alSourcef(id, AL.AL_CONE_OUTER_ANGLE, src.getOuterAngle()); + checkError(true); + //alSourcef(id, AL_CONE_OUTER_GAIN, 0); + alSourcef(id, AL.AL_CONE_OUTER_GAIN, 0); + checkError(true); + } else { + //alSourcef(id, AL_CONE_INNER_ANGLE, 360); + alSourcef(id, AL.AL_CONE_INNER_ANGLE, 360); + checkError(true); + //alSourcef(id, AL_CONE_OUTER_ANGLE, 360); + alSourcef(id, AL.AL_CONE_OUTER_ANGLE, 360); + checkError(true); + //alSourcef(id, AL_CONE_OUTER_GAIN, 1f); + alSourcef(id, AL.AL_CONE_OUTER_GAIN, 1f); + checkError(true); + } + } + + 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); + alListener3f(AL.AL_POSITION, pos.x, pos.y, pos.z); + checkError(true); + 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); + alListener(AL.AL_ORIENTATION, fb); + checkError(true); + break; + case Velocity: + Vector3f vel = listener.getVelocity(); + //alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); + alListener3f(AL.AL_VELOCITY, vel.x, vel.y, vel.z); + checkError(true); + break; + case Volume: + //alListenerf(AL_GAIN, listener.getVolume()); + alListenerf(AL.AL_GAIN, listener.getVolume()); + checkError(true); + 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.AL_POSITION, pos.x, pos.y, pos.z); + checkError(true); + //alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); + alListener3f(AL.AL_VELOCITY, vel.x, vel.y, vel.z); + checkError(true); + 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); + alListener(AL.AL_ORIENTATION, fb); + checkError(true); + //alListenerf(AL_GAIN, listener.getVolume()); + alListenerf(AL.AL_GAIN, listener.getVolume()); + checkError(true); + } + + private int newChannel() { + if (freeChans.size() > 0) { + return freeChans.remove(0); + } else if (nextChan < channels.length) { + return nextChan++; + } else { + return -1; + } + } + + private void freeChannel(int index) { + if (index == nextChan - 1) { + nextChan--; + } else { + freeChans.add(index); + } + } + + public void setEnvironment(Environment env) { + checkDead(); + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled || !supportEfx) { + return; + } + + //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DENSITY, env.getDensity()); + alEffectf(reverbFx, AL.AL_REVERB_DENSITY, env.getDensity()); + //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DIFFUSION, env.getDiffusion()); + alEffectf(reverbFx, AL.AL_REVERB_DIFFUSION, env.getDiffusion()); + //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_GAIN, env.getGain()); + alEffectf(reverbFx, AL.AL_REVERB_GAIN, env.getGain()); + //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_GAINHF, env.getGainHf()); + alEffectf(reverbFx, AL.AL_REVERB_GAINHF, env.getGainHf()); + //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DECAY_TIME, env.getDecayTime()); + alEffectf(reverbFx, AL.AL_REVERB_DECAY_TIME, env.getDecayTime()); + //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DECAY_HFRATIO, env.getDecayHFRatio()); + alEffectf(reverbFx, AL.AL_REVERB_DECAY_HFRATIO, env.getDecayHFRatio()); + //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_REFLECTIONS_GAIN, env.getReflectGain()); + alEffectf(reverbFx, AL.AL_REVERB_REFLECTIONS_GAIN, env.getReflectGain()); + //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_REFLECTIONS_DELAY, env.getReflectDelay()); + alEffectf(reverbFx, AL.AL_REVERB_REFLECTIONS_DELAY, env.getReflectDelay()); + //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_LATE_REVERB_GAIN, env.getLateReverbGain()); + alEffectf(reverbFx, AL.AL_REVERB_LATE_REVERB_GAIN, env.getLateReverbGain()); + //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_LATE_REVERB_DELAY, env.getLateReverbDelay()); + alEffectf(reverbFx, AL.AL_REVERB_LATE_REVERB_DELAY, env.getLateReverbDelay()); + //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_AIR_ABSORPTION_GAINHF, env.getAirAbsorbGainHf()); + alEffectf(reverbFx, AL.AL_REVERB_AIR_ABSORPTION_GAINHF, env.getAirAbsorbGainHf()); + //EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_ROOM_ROLLOFF_FACTOR, env.getRoomRolloffFactor()); + alEffectf(reverbFx, AL.AL_REVERB_ROOM_ROLLOFF_FACTOR, env.getRoomRolloffFactor()); + + // attach effect to slot + //EFX10.alAuxiliaryEffectSloti(reverbFxSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbFx); + alAuxiliaryEffectSloti(reverbFxSlot, AL.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()); + alBufferData(id, convertFormat(stream), nativeBuf, size, stream.getSampleRate()); + checkError(true); + + return true; + } + + private boolean fillStreamingSource(int sourceId, AudioStream stream) { + if (!stream.isOpen()) { + return false; + } + + boolean active = true; + //int processed = alGetSourcei(sourceId, AL_BUFFERS_PROCESSED); + int processed = alGetSourcei(sourceId, AL.AL_BUFFERS_PROCESSED); + checkError(true); + + //while((processed--) != 0){ + if (processed > 0) { + int buffer; + + ib.position(0).limit(1); + //alSourceUnqueueBuffers(sourceId, ib); + alSourceUnqueueBuffers(sourceId, 1, ib); + checkError(true); + buffer = ib.get(0); + + active = fillBuffer(stream, buffer); + + ib.position(0).limit(1); + ib.put(0, buffer); + //alSourceQueueBuffers(sourceId, ib); + alSourceQueueBuffers(sourceId, 1, ib); + checkError(true); + } + + if (!active && stream.isOpen()) { + stream.close(); + } + + return active; + } + + private boolean attachStreamToSource(int sourceId, AudioStream stream) { + boolean active = true; + int activeBufferCount = 0; + for (int id : stream.getIds()) { + active = fillBuffer(stream, id); + ib.position(0).limit(1); + ib.put(id).flip(); + //alSourceQueueBuffers(sourceId, ib); + // OpenAL Soft does not like 0 size buffer data in alSourceQueueBuffers + // Produces error code 40964 (0xA004) = AL_INVALID_OPERATION and + // does not return (crashes) so that the error code can be checked. + // active is FALSE when the data size is 0 + if (active) { + alSourceQueueBuffers(sourceId, 1, ib); + checkError(true); + activeBufferCount++; + } + } + // adjust the steam id array if the audio data is smaller than STREAMING_BUFFER_COUNT + // this is to avoid an error with OpenAL Soft when alSourceUnenqueueBuffers + // is called with more buffers than were originally used with alSourceQueueBuffers + if (activeBufferCount < STREAMING_BUFFER_COUNT) { + int[] newIds = new int[activeBufferCount]; + for (int i=0; iAndroidAudioLoader will create an + * {@link AndroidAudioData} object with the specified asset key. + */ +public class AndroidAudioLoader implements AssetLoader { + + @Override + public Object load(AssetInfo assetInfo) throws IOException { + AndroidAudioData result = new AndroidAudioData(); + result.setAssetKey(assetInfo.getKey()); + return result; + } +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureHandler.java new file mode 100644 index 000000000..d233836a5 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureHandler.java @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.android; + +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import com.jme3.input.event.InputEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidGestureHandler uses Gesture type listeners to create jME TouchEvents + * for gestures. This class is designed to handle the gestures supported + * on Android rev 9 (Android 2.3). Extend this class to add functionality + * added by Android after rev 9. + * + * @author iwgeric + */ +public class AndroidGestureHandler implements + GestureDetector.OnGestureListener, + GestureDetector.OnDoubleTapListener, + ScaleGestureDetector.OnScaleGestureListener { + private static final Logger logger = Logger.getLogger(AndroidGestureHandler.class.getName()); + private AndroidInputHandler androidInput; + private GestureDetector gestureDetector; + private ScaleGestureDetector scaleDetector; + float gestureDownX = -1f; + float gestureDownY = -1f; + float scaleStartX = -1f; + float scaleStartY = -1f; + + public AndroidGestureHandler(AndroidInputHandler androidInput) { + this.androidInput = androidInput; + } + + public void initialize() { + } + + public void destroy() { + setView(null); + } + + public void setView(View view) { + if (view != null) { + gestureDetector = new GestureDetector(view.getContext(), this); + scaleDetector = new ScaleGestureDetector(view.getContext(), this); + } else { + gestureDetector = null; + scaleDetector = null; + } + } + + public void detectGesture(MotionEvent event) { + if (gestureDetector != null && scaleDetector != null) { + gestureDetector.onTouchEvent(event); + scaleDetector.onTouchEvent(event); + } + } + + private int getPointerIndex(MotionEvent event) { + return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + } + + private int getPointerId(MotionEvent event) { + return event.getPointerId(getPointerIndex(event)); + } + + private void processEvent(TouchEvent event) { + // Add the touch event + androidInput.addEvent(event); + if (androidInput.isSimulateMouse()) { + InputEvent mouseEvent = generateMouseEvent(event); + if (mouseEvent != null) { + // Add the mouse event + androidInput.addEvent(mouseEvent); + } + } + } + + // TODO: Ring Buffer for mouse events? + private InputEvent generateMouseEvent(TouchEvent event) { + InputEvent inputEvent = null; + int newX; + int newY; + int newDX; + int newDY; + + if (androidInput.isMouseEventsInvertX()) { + newX = (int) (androidInput.invertX(event.getX())); + newDX = (int)event.getDeltaX() * -1; + } else { + newX = (int) event.getX(); + newDX = (int)event.getDeltaX(); + } + int wheel = (int) (event.getScaleSpan()); // might need to scale to match mouse wheel + int dWheel = (int) (event.getDeltaScaleSpan()); // might need to scale to match mouse wheel + + if (androidInput.isMouseEventsInvertY()) { + newY = (int) (androidInput.invertY(event.getY())); + newDY = (int)event.getDeltaY() * -1; + } else { + newY = (int) event.getY(); + newDY = (int)event.getDeltaY(); + } + + switch (event.getType()) { + case SCALE_MOVE: + inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, wheel, dWheel); + inputEvent.setTime(event.getTime()); + break; + } + + return inputEvent; + } + + /* Events from onGestureListener */ + + public boolean onDown(MotionEvent event) { + // start of all GestureListeners. Not really a gesture by itself + // so we don't create an event. + // However, reset the scaleInProgress here since this is the beginning + // of a series of gesture events. +// logger.log(Level.INFO, "onDown pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); + gestureDownX = androidInput.getJmeX(event.getX()); + gestureDownY = androidInput.invertY(androidInput.getJmeY(event.getY())); + return true; + } + + public boolean onSingleTapUp(MotionEvent event) { + // Up of single tap. May be followed by a double tap later. + // use onSingleTapConfirmed instead. +// logger.log(Level.INFO, "onSingleTapUp pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); + return true; + } + + public void onShowPress(MotionEvent event) { +// logger.log(Level.INFO, "onShowPress pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); + float jmeX = androidInput.getJmeX(event.getX()); + float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY())); + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.SHOWPRESS, jmeX, jmeY, 0, 0); + touchEvent.setPointerId(getPointerId(event)); + touchEvent.setTime(event.getEventTime()); + touchEvent.setPressure(event.getPressure()); + processEvent(touchEvent); + } + + public void onLongPress(MotionEvent event) { +// logger.log(Level.INFO, "onLongPress pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); + float jmeX = androidInput.getJmeX(event.getX()); + float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY())); + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.LONGPRESSED, jmeX, jmeY, 0, 0); + touchEvent.setPointerId(getPointerId(event)); + touchEvent.setTime(event.getEventTime()); + touchEvent.setPressure(event.getPressure()); + processEvent(touchEvent); + } + + public boolean onScroll(MotionEvent startEvent, MotionEvent endEvent, float distX, float distY) { + // if not scaleInProgess, send scroll events. This is to avoid sending + // scroll events when one of the fingers is lifted just before the other one. + // Avoids sending the scroll for that brief period of time. + // Return true so that the next event doesn't accumulate the distX and distY values. + // Apparantly, both distX and distY are negative. + // Negate distX to get the real value, but leave distY negative to compensate + // for the fact that jME has y=0 at bottom where Android has y=0 at top. +// if (!scaleInProgress) { + if (!scaleDetector.isInProgress()) { +// logger.log(Level.INFO, "onScroll pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, dx: {7}, dy: {8}", +// new Object[]{getPointerId(startEvent), getAction(startEvent), startEvent.getX(), startEvent.getY(), getAction(endEvent), endEvent.getX(), endEvent.getY(), distX, distY}); + + float jmeX = androidInput.getJmeX(endEvent.getX()); + float jmeY = androidInput.invertY(androidInput.getJmeY(endEvent.getY())); + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.SCROLL, jmeX, jmeY, androidInput.getJmeX(-distX), androidInput.getJmeY(distY)); + touchEvent.setPointerId(getPointerId(endEvent)); + touchEvent.setTime(endEvent.getEventTime()); + touchEvent.setPressure(endEvent.getPressure()); + processEvent(touchEvent); + } + return true; + } + + public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float velocityX, float velocityY) { + // Fling happens only once at the end of the gesture (all fingers up). + // Fling returns the velocity of the finger movement in pixels/sec. + // Therefore, the dX and dY values are actually velocity instead of distance values + // Since this does not track the movement, use the start position and velocity values. + +// logger.log(Level.INFO, "onFling pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, velocityX: {7}, velocityY: {8}", +// new Object[]{getPointerId(startEvent), getAction(startEvent), startEvent.getX(), startEvent.getY(), getAction(endEvent), endEvent.getX(), endEvent.getY(), velocityX, velocityY}); + + float jmeX = androidInput.getJmeX(startEvent.getX()); + float jmeY = androidInput.invertY(androidInput.getJmeY(startEvent.getY())); + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.FLING, jmeX, jmeY, velocityX, velocityY); + touchEvent.setPointerId(getPointerId(endEvent)); + touchEvent.setTime(endEvent.getEventTime()); + touchEvent.setPressure(endEvent.getPressure()); + processEvent(touchEvent); + return true; + } + + /* Events from onDoubleTapListener */ + + public boolean onSingleTapConfirmed(MotionEvent event) { + // Up of single tap when no double tap followed. +// logger.log(Level.INFO, "onSingleTapConfirmed pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); + float jmeX = androidInput.getJmeX(event.getX()); + float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY())); + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.TAP, jmeX, jmeY, 0, 0); + touchEvent.setPointerId(getPointerId(event)); + touchEvent.setTime(event.getEventTime()); + touchEvent.setPressure(event.getPressure()); + processEvent(touchEvent); + return true; + } + + public boolean onDoubleTap(MotionEvent event) { + //The down motion event of the first tap of the double-tap + // We could use this event to fire off a double tap event, or use + // DoubleTapEvent with a check for the UP action +// logger.log(Level.INFO, "onDoubleTap pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); + float jmeX = androidInput.getJmeX(event.getX()); + float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY())); + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.DOUBLETAP, jmeX, jmeY, 0, 0); + touchEvent.setPointerId(getPointerId(event)); + touchEvent.setTime(event.getEventTime()); + touchEvent.setPressure(event.getPressure()); + processEvent(touchEvent); + return true; + } + + public boolean onDoubleTapEvent(MotionEvent event) { + //Notified when an event within a double-tap gesture occurs, including the down, move(s), and up events. + // this means it will get called multiple times for a single double tap +// logger.log(Level.INFO, "onDoubleTapEvent pointerId: {0}, action: {1}, x: {2}, y: {3}", +// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); +// if (getAction(event) == MotionEvent.ACTION_UP) { +// TouchEvent touchEvent = touchEventPool.getNextFreeEvent(); +// touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), androidInput.invertY(event.getY()), 0, 0); +// touchEvent.setPointerId(getPointerId(event)); +// touchEvent.setTime(event.getEventTime()); +// touchEvent.setPressure(event.getPressure()); +// processEvent(touchEvent); +// } + return true; + } + + /* Events from ScaleGestureDetector */ + + public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { + // Scale uses a focusX and focusY instead of x and y. Focus is the middle + // of the fingers. Therefore, use the x and y values from the Down event + // so that the x and y values don't jump to the middle position. + // return true or all gestures for this beginning event will be discarded + logger.log(Level.INFO, "onScaleBegin"); + scaleStartX = gestureDownX; + scaleStartY = gestureDownY; + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.SCALE_START, scaleStartX, scaleStartY, 0f, 0f); + touchEvent.setPointerId(0); + touchEvent.setTime(scaleGestureDetector.getEventTime()); + touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touchEvent.setDeltaScaleSpan(0f); + touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor()); + touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress()); + processEvent(touchEvent); + + return true; + } + + public boolean onScale(ScaleGestureDetector scaleGestureDetector) { + // return true or all gestures for this event will be accumulated + logger.log(Level.INFO, "onScale"); + scaleStartX = gestureDownX; + scaleStartY = gestureDownY; + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.SCALE_MOVE, scaleStartX, scaleStartY, 0f, 0f); + touchEvent.setPointerId(0); + touchEvent.setTime(scaleGestureDetector.getEventTime()); + touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan()); + touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor()); + touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress()); + processEvent(touchEvent); + return true; + } + + public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) { + logger.log(Level.INFO, "onScaleEnd"); + scaleStartX = gestureDownX; + scaleStartY = gestureDownY; + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.SCALE_END, scaleStartX, scaleStartY, 0f, 0f); + touchEvent.setPointerId(0); + touchEvent.setTime(scaleGestureDetector.getEventTime()); + touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan()); + touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor()); + touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress()); + processEvent(touchEvent); + } +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInput.java new file mode 100644 index 000000000..1cec2b8d4 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInput.java @@ -0,0 +1,686 @@ +package com.jme3.input.android; + +import android.view.*; +import com.jme3.input.KeyInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.TouchInput; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; +import com.jme3.input.event.TouchEvent.Type; +import com.jme3.math.Vector2f; +import com.jme3.system.AppSettings; +import com.jme3.util.RingBuffer; +import java.util.HashMap; +import java.util.logging.Logger; + +/** + * AndroidInput is one of the main components that connect jme with android. Is derived from GLSurfaceView and handles all Inputs + * @author larynx + * + */ +public class AndroidInput implements + TouchInput, + View.OnTouchListener, + View.OnKeyListener, + GestureDetector.OnGestureListener, + GestureDetector.OnDoubleTapListener, + ScaleGestureDetector.OnScaleGestureListener { + + final private static int MAX_EVENTS = 1024; + // Custom settings + public boolean mouseEventsEnabled = true; + public boolean mouseEventsInvertX = false; + public boolean mouseEventsInvertY = false; + public boolean keyboardEventsEnabled = false; + public boolean dontSendHistory = false; + // Used to transfer events from android thread to GLThread + final private RingBuffer eventQueue = new RingBuffer(MAX_EVENTS); + final private RingBuffer eventPoolUnConsumed = new RingBuffer(MAX_EVENTS); + final private RingBuffer eventPool = new RingBuffer(MAX_EVENTS); + final private HashMap lastPositions = new HashMap(); + // Internal + private View view; + private ScaleGestureDetector scaledetector; + private boolean scaleInProgress = false; + private GestureDetector detector; + private int lastX; + private int lastY; + private final static Logger logger = Logger.getLogger(AndroidInput.class.getName()); + private boolean isInitialized = false; + private RawInputListener listener = null; + private static final int[] ANDROID_TO_JME = { + 0x0, // unknown + 0x0, // key code soft left + 0x0, // key code soft right + KeyInput.KEY_HOME, + KeyInput.KEY_ESCAPE, // key back + 0x0, // key call + 0x0, // key endcall + KeyInput.KEY_0, + KeyInput.KEY_1, + KeyInput.KEY_2, + KeyInput.KEY_3, + KeyInput.KEY_4, + KeyInput.KEY_5, + KeyInput.KEY_6, + KeyInput.KEY_7, + KeyInput.KEY_8, + KeyInput.KEY_9, + KeyInput.KEY_MULTIPLY, + 0x0, // key pound + KeyInput.KEY_UP, + KeyInput.KEY_DOWN, + KeyInput.KEY_LEFT, + KeyInput.KEY_RIGHT, + KeyInput.KEY_RETURN, // dpad center + 0x0, // volume up + 0x0, // volume down + KeyInput.KEY_POWER, // power (?) + 0x0, // camera + 0x0, // clear + KeyInput.KEY_A, + KeyInput.KEY_B, + KeyInput.KEY_C, + KeyInput.KEY_D, + KeyInput.KEY_E, + KeyInput.KEY_F, + KeyInput.KEY_G, + KeyInput.KEY_H, + KeyInput.KEY_I, + KeyInput.KEY_J, + KeyInput.KEY_K, + KeyInput.KEY_L, + KeyInput.KEY_M, + KeyInput.KEY_N, + KeyInput.KEY_O, + KeyInput.KEY_P, + KeyInput.KEY_Q, + KeyInput.KEY_R, + KeyInput.KEY_S, + KeyInput.KEY_T, + KeyInput.KEY_U, + KeyInput.KEY_V, + KeyInput.KEY_W, + KeyInput.KEY_X, + KeyInput.KEY_Y, + KeyInput.KEY_Z, + KeyInput.KEY_COMMA, + KeyInput.KEY_PERIOD, + KeyInput.KEY_LMENU, + KeyInput.KEY_RMENU, + KeyInput.KEY_LSHIFT, + KeyInput.KEY_RSHIFT, + // 0x0, // fn + // 0x0, // cap (?) + + KeyInput.KEY_TAB, + KeyInput.KEY_SPACE, + 0x0, // sym (?) symbol + 0x0, // explorer + 0x0, // envelope + KeyInput.KEY_RETURN, // newline/enter + KeyInput.KEY_DELETE, + KeyInput.KEY_GRAVE, + KeyInput.KEY_MINUS, + KeyInput.KEY_EQUALS, + KeyInput.KEY_LBRACKET, + KeyInput.KEY_RBRACKET, + KeyInput.KEY_BACKSLASH, + KeyInput.KEY_SEMICOLON, + KeyInput.KEY_APOSTROPHE, + KeyInput.KEY_SLASH, + KeyInput.KEY_AT, // at (@) + KeyInput.KEY_NUMLOCK, //0x0, // num + 0x0, //headset hook + 0x0, //focus + KeyInput.KEY_ADD, + KeyInput.KEY_LMETA, //menu + 0x0,//notification + 0x0,//search + 0x0,//media play/pause + 0x0,//media stop + 0x0,//media next + 0x0,//media previous + 0x0,//media rewind + 0x0,//media fastforward + 0x0,//mute + }; + + public AndroidInput() { + } + + public void setView(View view) { + this.view = view; + if (view != null) { + detector = new GestureDetector(null, this, null, false); + scaledetector = new ScaleGestureDetector(view.getContext(), this); + view.setOnTouchListener(this); + view.setOnKeyListener(this); + } + } + + private TouchEvent getNextFreeTouchEvent() { + return getNextFreeTouchEvent(false); + } + + /** + * Fetches a touch event from the reuse pool + * @param wait if true waits for a reusable event to get available/released + * by an other thread, if false returns a new one if needed. + * + * @return a usable TouchEvent + */ + private TouchEvent getNextFreeTouchEvent(boolean wait) { + TouchEvent evt = null; + synchronized (eventPoolUnConsumed) { + int size = eventPoolUnConsumed.size(); + while (size > 0) { + evt = eventPoolUnConsumed.pop(); + if (!evt.isConsumed()) { + eventPoolUnConsumed.push(evt); + evt = null; + } else { + break; + } + size--; + } + } + + if (evt == null) { + if (eventPool.isEmpty() && wait) { + logger.warning("eventPool buffer underrun"); + boolean isEmpty; + do { + synchronized (eventPool) { + isEmpty = eventPool.isEmpty(); + } + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + } while (isEmpty); + synchronized (eventPool) { + evt = eventPool.pop(); + } + } else if (eventPool.isEmpty()) { + evt = new TouchEvent(); + logger.warning("eventPool buffer underrun"); + } else { + synchronized (eventPool) { + evt = eventPool.pop(); + } + } + } + return evt; + } + + /** + * onTouch gets called from android thread on touchpad events + */ + public boolean onTouch(View view, MotionEvent event) { + if (view != this.view) { + return false; + } + boolean bWasHandled = false; + TouchEvent touch; + // System.out.println("native : " + event.getAction()); + int action = event.getAction() & MotionEvent.ACTION_MASK; + int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + int pointerId = event.getPointerId(pointerIndex); + Vector2f lastPos = lastPositions.get(pointerId); + + // final int historySize = event.getHistorySize(); + //final int pointerCount = event.getPointerCount(); + switch (action) { + case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_DOWN: + touch = getNextFreeTouchEvent(); + touch.set(Type.DOWN, event.getX(pointerIndex), view.getHeight() - event.getY(pointerIndex), 0, 0); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + processEvent(touch); + + lastPos = new Vector2f(event.getX(pointerIndex), view.getHeight() - event.getY(pointerIndex)); + lastPositions.put(pointerId, lastPos); + + bWasHandled = true; + break; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + touch = getNextFreeTouchEvent(); + touch.set(Type.UP, event.getX(pointerIndex), view.getHeight() - event.getY(pointerIndex), 0, 0); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + processEvent(touch); + lastPositions.remove(pointerId); + + bWasHandled = true; + break; + case MotionEvent.ACTION_MOVE: + // Convert all pointers into events + for (int p = 0; p < event.getPointerCount(); p++) { + lastPos = lastPositions.get(event.getPointerId(p)); + if (lastPos == null) { + lastPos = new Vector2f(event.getX(p), view.getHeight() - event.getY(p)); + lastPositions.put(event.getPointerId(p), lastPos); + } + + float dX = event.getX(p) - lastPos.x; + float dY = view.getHeight() - event.getY(p) - lastPos.y; + if (dX != 0 || dY != 0) { + touch = getNextFreeTouchEvent(); + touch.set(Type.MOVE, event.getX(p), view.getHeight() - event.getY(p), dX, dY); + touch.setPointerId(event.getPointerId(p)); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(p)); + touch.setScaleSpanInProgress(scaleInProgress); + processEvent(touch); + lastPos.set(event.getX(p), view.getHeight() - event.getY(p)); + } + } + bWasHandled = true; + break; + case MotionEvent.ACTION_OUTSIDE: + break; + + } + + // Try to detect gestures + this.detector.onTouchEvent(event); + this.scaledetector.onTouchEvent(event); + + return bWasHandled; + } + + /** + * onKey gets called from android thread on key events + */ + public boolean onKey(View view, int keyCode, KeyEvent event) { + if (view != this.view) { + return false; + } + + if (event.getAction() == KeyEvent.ACTION_DOWN) { + TouchEvent evt; + evt = getNextFreeTouchEvent(); + evt.set(TouchEvent.Type.KEY_DOWN); + evt.setKeyCode(keyCode); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + processEvent(evt); + + // Handle all keys ourself except Volume Up/Down + if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) { + return false; + } else { + return true; + } + } else if (event.getAction() == KeyEvent.ACTION_UP) { + TouchEvent evt; + evt = getNextFreeTouchEvent(); + evt.set(TouchEvent.Type.KEY_UP); + evt.setKeyCode(keyCode); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + processEvent(evt); + + // Handle all keys ourself except Volume Up/Down + if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) { + return false; + } else { + return true; + } + } else { + return false; + } + } + + public void loadSettings(AppSettings settings) { + mouseEventsEnabled = settings.isEmulateMouse(); + mouseEventsInvertX = settings.isEmulateMouseFlipX(); + mouseEventsInvertY = settings.isEmulateMouseFlipY(); + } + + // ----------------------------------------- + // JME3 Input interface + @Override + public void initialize() { + TouchEvent item; + for (int i = 0; i < MAX_EVENTS; i++) { + item = new TouchEvent(); + eventPool.push(item); + } + isInitialized = true; + } + + @Override + public void destroy() { + isInitialized = false; + + // Clean up queues + while (!eventPool.isEmpty()) { + eventPool.pop(); + } + while (!eventQueue.isEmpty()) { + eventQueue.pop(); + } + + + this.view = null; + } + + @Override + public boolean isInitialized() { + return isInitialized; + } + + @Override + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return System.nanoTime(); + } + // ----------------------------------------- + + private void processEvent(TouchEvent event) { + synchronized (eventQueue) { + //Discarding events when the ring buffer is full to avoid buffer overflow. + if(eventQueue.size()< MAX_EVENTS){ + eventQueue.push(event); + } + + } + } + + // --------------- INSIDE GLThread --------------- + @Override + public void update() { + generateEvents(); + } + + private void generateEvents() { + if (listener != null) { + TouchEvent event; + MouseButtonEvent btn; + MouseMotionEvent mot; + int newX; + int newY; + + while (!eventQueue.isEmpty()) { + synchronized (eventQueue) { + event = eventQueue.pop(); + } + if (event != null) { + listener.onTouchEvent(event); + + if (mouseEventsEnabled) { + if (mouseEventsInvertX) { + newX = view.getWidth() - (int) event.getX(); + } else { + newX = (int) event.getX(); + } + + if (mouseEventsInvertY) { + newY = view.getHeight() - (int) event.getY(); + } else { + newY = (int) event.getY(); + } + + switch (event.getType()) { + case DOWN: + // Handle mouse down event + btn = new MouseButtonEvent(0, true, newX, newY); + btn.setTime(event.getTime()); + listener.onMouseButtonEvent(btn); + // Store current pos + lastX = -1; + lastY = -1; + break; + + case UP: + // Handle mouse up event + btn = new MouseButtonEvent(0, false, newX, newY); + btn.setTime(event.getTime()); + listener.onMouseButtonEvent(btn); + // Store current pos + lastX = -1; + lastY = -1; + break; + + case SCALE_MOVE: + if (lastX != -1 && lastY != -1) { + newX = lastX; + newY = lastY; + } + int wheel = (int) (event.getScaleSpan() / 4f); // scale to match mouse wheel + int dwheel = (int) (event.getDeltaScaleSpan() / 4f); // scale to match mouse wheel + mot = new MouseMotionEvent(newX, newX, 0, 0, wheel, dwheel); + mot.setTime(event.getTime()); + listener.onMouseMotionEvent(mot); + lastX = newX; + lastY = newY; + + break; + + case MOVE: + if (event.isScaleSpanInProgress()) { + break; + } + + int dx; + int dy; + if (lastX != -1) { + dx = newX - lastX; + dy = newY - lastY; + } else { + dx = 0; + dy = 0; + } + + mot = new MouseMotionEvent(newX, newY, dx, dy, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan()); + mot.setTime(event.getTime()); + listener.onMouseMotionEvent(mot); + lastX = newX; + lastY = newY; + + break; + } + } + } + + if (event.isConsumed() == false) { + synchronized (eventPoolUnConsumed) { + eventPoolUnConsumed.push(event); + } + + } else { + synchronized (eventPool) { + eventPool.push(event); + } + } + } + + } + } + // --------------- ENDOF INSIDE GLThread --------------- + + // --------------- Gesture detected callback events --------------- + public boolean onDown(MotionEvent event) { + return false; + } + + public void onLongPress(MotionEvent event) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.LONGPRESSED, event.getX(), view.getHeight() - event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + } + + public boolean onFling(MotionEvent event, MotionEvent event2, float vx, float vy) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.FLING, event.getX(), view.getHeight() - event.getY(), vx, vy); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + + return true; + } + + public boolean onSingleTapConfirmed(MotionEvent event) { + //Nothing to do here the tap has already been detected. + return false; + } + + public boolean onDoubleTap(MotionEvent event) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.DOUBLETAP, event.getX(), view.getHeight() - event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + return true; + } + + public boolean onDoubleTapEvent(MotionEvent event) { + return false; + } + + public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { + scaleInProgress = true; + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCALE_START, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(scaleGestureDetector.getEventTime()); + touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touch.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan()); + touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); + touch.setScaleSpanInProgress(scaleInProgress); + processEvent(touch); + // System.out.println("scaleBegin"); + + return true; + } + + public boolean onScale(ScaleGestureDetector scaleGestureDetector) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCALE_MOVE, scaleGestureDetector.getFocusX(), view.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(scaleGestureDetector.getEventTime()); + touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touch.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan()); + touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); + touch.setScaleSpanInProgress(scaleInProgress); + processEvent(touch); + // System.out.println("scale"); + + return false; + } + + public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) { + scaleInProgress = false; + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCALE_END, scaleGestureDetector.getFocusX(), view.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(scaleGestureDetector.getEventTime()); + touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touch.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan()); + touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); + touch.setScaleSpanInProgress(scaleInProgress); + processEvent(touch); + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCROLL, e1.getX(), view.getHeight() - e1.getY(), distanceX, distanceY * (-1)); + touch.setPointerId(0); + touch.setTime(e1.getEventTime()); + processEvent(touch); + //System.out.println("scroll " + e1.getPointerCount()); + return false; + } + + public void onShowPress(MotionEvent event) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SHOWPRESS, event.getX(), view.getHeight() - event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + } + + public boolean onSingleTapUp(MotionEvent event) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.TAP, event.getX(), view.getHeight() - event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + return true; + } + + @Override + public void setSimulateKeyboard(boolean simulate) { + keyboardEventsEnabled = simulate; + } + + @Override + public void setOmitHistoricEvents(boolean dontSendHistory) { + this.dontSendHistory = dontSendHistory; + } + + /** + * @deprecated Use {@link #getSimulateMouse()}; + */ + @Deprecated + public boolean isMouseEventsEnabled() { + return mouseEventsEnabled; + } + + @Deprecated + public void setMouseEventsEnabled(boolean mouseEventsEnabled) { + this.mouseEventsEnabled = mouseEventsEnabled; + } + + public boolean isMouseEventsInvertY() { + return mouseEventsInvertY; + } + + public void setMouseEventsInvertY(boolean mouseEventsInvertY) { + this.mouseEventsInvertY = mouseEventsInvertY; + } + + public boolean isMouseEventsInvertX() { + return mouseEventsInvertX; + } + + public void setMouseEventsInvertX(boolean mouseEventsInvertX) { + this.mouseEventsInvertX = mouseEventsInvertX; + } + + public void setSimulateMouse(boolean simulate) { + mouseEventsEnabled = simulate; + } + + public boolean getSimulateMouse() { + return isSimulateMouse(); + } + + public boolean isSimulateMouse() { + return mouseEventsEnabled; + } + + public void showVirtualKeyboard(boolean visible) { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java new file mode 100644 index 000000000..f2ea5a4b4 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.android; + +import android.os.Build; +import android.view.View; +import com.jme3.input.RawInputListener; +import com.jme3.input.TouchInput; +import com.jme3.input.event.InputEvent; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; +import com.jme3.renderer.android.AndroidGLSurfaceView; +import com.jme3.system.AppSettings; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidInput is the main class that connects the Android system + * inputs to jME. It serves as the manager that gathers inputs from the various + * Android input methods and provides them to jME's InputManager. + * + * @author iwgeric + */ +public class AndroidInputHandler implements TouchInput { + private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName()); + + // Custom settings + private boolean mouseEventsEnabled = true; + private boolean mouseEventsInvertX = false; + private boolean mouseEventsInvertY = false; + private boolean keyboardEventsEnabled = false; + private boolean joystickEventsEnabled = false; + private boolean dontSendHistory = false; + + + // Internal + private AndroidGLSurfaceView view; + private AndroidTouchHandler touchHandler; + private AndroidKeyHandler keyHandler; + private AndroidGestureHandler gestureHandler; + private boolean initialized = false; + private RawInputListener listener = null; + private ConcurrentLinkedQueue inputEventQueue = new ConcurrentLinkedQueue(); + private final static int MAX_TOUCH_EVENTS = 1024; + private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS); + private float scaleX = 1f; + private float scaleY = 1f; + + + public AndroidInputHandler() { + int buildVersion = Build.VERSION.SDK_INT; + logger.log(Level.INFO, "Android Build Version: {0}", buildVersion); + if (buildVersion >= 14) { + // add support for onHover and GenericMotionEvent (ie. gamepads) + gestureHandler = new AndroidGestureHandler(this); + touchHandler = new AndroidTouchHandler14(this, gestureHandler); + keyHandler = new AndroidKeyHandler(this); + } else if (buildVersion >= 8){ + gestureHandler = new AndroidGestureHandler(this); + touchHandler = new AndroidTouchHandler(this, gestureHandler); + keyHandler = new AndroidKeyHandler(this); + } + } + + public AndroidInputHandler(AndroidTouchHandler touchInput, + AndroidKeyHandler keyInput, AndroidGestureHandler gestureHandler) { + this.touchHandler = touchInput; + this.keyHandler = keyInput; + this.gestureHandler = gestureHandler; + } + + public void setView(View view) { + if (touchHandler != null) { + touchHandler.setView(view); + } + if (keyHandler != null) { + keyHandler.setView(view); + } + if (gestureHandler != null) { + gestureHandler.setView(view); + } + this.view = (AndroidGLSurfaceView)view; + } + + public View getView() { + return view; + } + + public float invertX(float origX) { + return getJmeX(view.getWidth()) - origX; + } + + public float invertY(float origY) { + return getJmeY(view.getHeight()) - origY; + } + + public float getJmeX(float origX) { + return origX * scaleX; + } + + public float getJmeY(float origY) { + return origY * scaleY; + } + + public void loadSettings(AppSettings settings) { + // TODO: add simulate keyboard to settings +// keyboardEventsEnabled = true; + mouseEventsEnabled = settings.isEmulateMouse(); + mouseEventsInvertX = settings.isEmulateMouseFlipX(); + mouseEventsInvertY = settings.isEmulateMouseFlipY(); + joystickEventsEnabled = settings.useJoysticks(); + + // view width and height are 0 until the view is displayed on the screen + if (view.getWidth() != 0 && view.getHeight() != 0) { + scaleX = (float)settings.getWidth() / (float)view.getWidth(); + scaleY = (float)settings.getHeight() / (float)view.getHeight(); + } + logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}", + new Object[]{scaleX, scaleY}); + + } + + // ----------------------------------------- + // JME3 Input interface + @Override + public void initialize() { + touchEventPool.initialize(); + if (touchHandler != null) { + touchHandler.initialize(); + } + if (keyHandler != null) { + keyHandler.initialize(); + } + if (gestureHandler != null) { + gestureHandler.initialize(); + } + + initialized = true; + } + + @Override + public void destroy() { + initialized = false; + + touchEventPool.destroy(); + if (touchHandler != null) { + touchHandler.destroy(); + } + if (keyHandler != null) { + keyHandler.destroy(); + } + if (gestureHandler != null) { + gestureHandler.destroy(); + } + + setView(null); + } + + @Override + public boolean isInitialized() { + return initialized; + } + + @Override + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return System.nanoTime(); + } + + public void update() { + if (listener != null) { + InputEvent inputEvent; + + while ((inputEvent = inputEventQueue.poll()) != null) { + if (inputEvent instanceof TouchEvent) { + listener.onTouchEvent((TouchEvent)inputEvent); + } else if (inputEvent instanceof MouseButtonEvent) { + listener.onMouseButtonEvent((MouseButtonEvent)inputEvent); + } else if (inputEvent instanceof MouseMotionEvent) { + listener.onMouseMotionEvent((MouseMotionEvent)inputEvent); + } else if (inputEvent instanceof KeyInputEvent) { + listener.onKeyEvent((KeyInputEvent)inputEvent); + } + } + } + } + + // ----------------------------------------- + + public TouchEvent getFreeTouchEvent() { + return touchEventPool.getNextFreeEvent(); + } + + public void addEvent(InputEvent event) { + inputEventQueue.add(event); + if (event instanceof TouchEvent) { + touchEventPool.storeEvent((TouchEvent)event); + } + } + + public void setSimulateMouse(boolean simulate) { + this.mouseEventsEnabled = simulate; + } + + public boolean isSimulateMouse() { + return mouseEventsEnabled; + } + + public boolean getSimulateMouse() { + return mouseEventsEnabled; + } + + public boolean isMouseEventsInvertX() { + return mouseEventsInvertX; + } + + public boolean isMouseEventsInvertY() { + return mouseEventsInvertY; + } + + public void setSimulateKeyboard(boolean simulate) { + this.keyboardEventsEnabled = simulate; + } + + public void setOmitHistoricEvents(boolean dontSendHistory) { + this.dontSendHistory = dontSendHistory; + } + + public void showVirtualKeyboard(boolean visible) { + if (keyHandler != null) { + keyHandler.showVirtualKeyboard(visible); + } + } + + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java new file mode 100644 index 000000000..ffd299d60 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.android; + +import android.content.Context; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.TouchEvent; +import java.util.logging.Logger; + +/** + * AndroidKeyHandler recieves onKey events from the Android system and creates + * the jME KeyEvents. onKey is used by Android to receive keys from the keyboard + * or device buttons. All key events are consumed by jME except for the Volume + * buttons and menu button. + * + * This class also provides the functionality to display or hide the soft keyboard + * for inputing single key events. Use OGLESContext to display an dialog to type + * in complete strings. + * + * @author iwgeric + */ +public class AndroidKeyHandler implements View.OnKeyListener { + private static final Logger logger = Logger.getLogger(AndroidKeyHandler.class.getName()); + + private AndroidInputHandler androidInput; + private boolean sendKeyEvents = true; + + public AndroidKeyHandler(AndroidInputHandler androidInput) { + this.androidInput = androidInput; + } + + public void initialize() { + } + + public void destroy() { + } + + public void setView(View view) { + if (view != null) { + view.setOnKeyListener(this); + } else { + androidInput.getView().setOnKeyListener(null); + } + } + + /** + * onKey gets called from android thread on key events + */ + public boolean onKey(View view, int keyCode, KeyEvent event) { + if (androidInput.isInitialized() && view != androidInput.getView()) { + return false; + } + + TouchEvent evt; + // TODO: get touch event from pool + if (event.getAction() == KeyEvent.ACTION_DOWN) { + evt = new TouchEvent(); + evt.set(TouchEvent.Type.KEY_DOWN); + evt.setKeyCode(keyCode); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + androidInput.addEvent(evt); + + } else if (event.getAction() == KeyEvent.ACTION_UP) { + evt = new TouchEvent(); + evt.set(TouchEvent.Type.KEY_UP); + evt.setKeyCode(keyCode); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + androidInput.addEvent(evt); + + } + + + KeyInputEvent kie; + char unicodeChar = (char)event.getUnicodeChar(); + int jmeKeyCode = AndroidKeyMapping.getJmeKey(keyCode); + + boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN; + boolean repeating = pressed && event.getRepeatCount() > 0; + + kie = new KeyInputEvent(jmeKeyCode, unicodeChar, pressed, repeating); + kie.setTime(event.getEventTime()); + androidInput.addEvent(kie); +// logger.log(Level.FINE, "onKey keyCode: {0}, jmeKeyCode: {1}, pressed: {2}, repeating: {3}", +// new Object[]{keyCode, jmeKeyCode, pressed, repeating}); +// logger.log(Level.FINE, "creating KeyInputEvent: {0}", kie); + + // consume all keys ourself except Volume Up/Down and Menu + // Don't do Menu so that typical Android Menus can be created and used + // by the user in MainActivity + if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || + (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) || + (keyCode == KeyEvent.KEYCODE_MENU)) { + return false; + } else { + return true; + } + } + + public void showVirtualKeyboard (final boolean visible) { + androidInput.getView().getHandler().post(new Runnable() { + + public void run() { + InputMethodManager manager = + (InputMethodManager)androidInput.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + + if (visible) { + manager.showSoftInput(androidInput.getView(), 0); + sendKeyEvents = true; + } else { + manager.hideSoftInputFromWindow(androidInput.getView().getWindowToken(), 0); + sendKeyEvents = false; + } + } + }); + } + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java new file mode 100644 index 000000000..e99d8e9fc --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.android; + +import com.jme3.input.KeyInput; +import java.util.logging.Logger; + +/** + * AndroidKeyMapping is just a utility to convert the Android keyCodes into + * jME KeyCodes received in jME's KeyEvent will match between Desktop and Android. + * + * @author iwgeric + */ +public class AndroidKeyMapping { + private static final Logger logger = Logger.getLogger(AndroidKeyMapping.class.getName()); + + 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_BACK, //used to be KeyInput.KEY_DELETE, + KeyInput.KEY_GRAVE, + KeyInput.KEY_MINUS, + KeyInput.KEY_EQUALS, + KeyInput.KEY_LBRACKET, + KeyInput.KEY_RBRACKET, + KeyInput.KEY_BACKSLASH, + KeyInput.KEY_SEMICOLON, + KeyInput.KEY_APOSTROPHE, + KeyInput.KEY_SLASH, + KeyInput.KEY_AT, // at (@) + KeyInput.KEY_NUMLOCK, //0x0, // num + 0x0, //headset hook + 0x0, //focus + KeyInput.KEY_ADD, + KeyInput.KEY_LMETA, //menu + 0x0,//notification + 0x0,//search + 0x0,//media play/pause + 0x0,//media stop + 0x0,//media next + 0x0,//media previous + 0x0,//media rewind + 0x0,//media fastforward + 0x0,//mute + }; + + public static int getJmeKey(int androidKey) { + return ANDROID_TO_JME[androidKey]; + } + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java new file mode 100644 index 000000000..6ae7fca1d --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java @@ -0,0 +1,795 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.android; + +import android.app.Activity; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Vibrator; +import android.view.Surface; +import android.view.View; +import com.jme3.input.AbstractJoystick; +import com.jme3.input.DefaultJoystickAxis; +import com.jme3.input.InputManager; +import com.jme3.input.JoyInput; +import com.jme3.input.Joystick; +import com.jme3.input.JoystickAxis; +import com.jme3.input.SensorJoystickAxis; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.JoyAxisEvent; +import com.jme3.math.FastMath; +import com.jme3.system.android.JmeAndroidSystem; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidSensorJoyInput converts the Android Sensor system into Joystick events. + * A single joystick is configured and includes data for all configured sensors + * as seperate axes of the joystick. + * + * Each axis is named accounting to the static strings in SensorJoystickAxis. + * Refer to the strings defined in SensorJoystickAxis for a list of supported + * sensors and their axis data. Each sensor type defined in SensorJoystickAxis + * will be attempted to be configured. If the device does not support a particular + * sensor, the axis will return null if joystick.getAxis(String name) is called. + * + * The joystick.getXAxis and getYAxis methods of the joystick are configured to + * return the device orientation values in the device's X and Y directions. + * + * This joystick also supports the joystick.rumble(rumbleAmount) method. In this + * case, when joystick.rumble(rumbleAmount) is called, the Android device will vibrate + * if the device has a built in vibrate motor. + * + * Because Andorid does not allow for the user to define the intensity of the + * vibration, the rumble amount (ie strength) is converted into vibration pulses + * The stronger the strength amount, the shorter the delay between pulses. If + * amount is 1, then the vibration stays on the whole time. If amount is 0.5, + * the vibration will a pulse of equal parts vibration and delay. + * To turn off vibration, set rumble amount to 0. + * + * MainActivity needs the following line to enable Joysticks on Android platforms + * joystickEventsEnabled = true; + * This is done to allow for battery conservation when sensor data is not required + * by the application. + * + * To use the joystick rumble feature, the following line needs to be + * added to the Android Manifest File + * + * + * @author iwgeric + */ +public class AndroidSensorJoyInput implements JoyInput, SensorEventListener { + private final static Logger logger = Logger.getLogger(AndroidSensorJoyInput.class.getName()); + + private Activity activity = null; + private InputManager inputManager = null; + private SensorManager sensorManager = null; + private Vibrator vibrator = null; + private boolean vibratorActive = false; + private long maxRumbleTime = 250; // 250ms + private RawInputListener listener = null; + private IntMap sensors = new IntMap(); + private AndroidJoystick[] joysticks; + private int lastRotation = 0; + private boolean initialized = false; + private boolean loaded = false; + + private final ArrayList eventQueue = new ArrayList(); + + /** + * Internal class to enclose data for each sensor. + */ + private class SensorData { + int androidSensorType = -1; + int androidSensorSpeed = SensorManager.SENSOR_DELAY_GAME; + Sensor sensor = null; + int sensorAccuracy = 0; + float[] lastValues; + final Object valuesLock = new Object(); + ArrayList axes = new ArrayList(); + boolean enabled = false; + boolean haveData = false; + + public SensorData(int androidSensorType, Sensor sensor) { + this.androidSensorType = androidSensorType; + this.sensor = sensor; + } + + } + + private void initSensorManager() { + this.activity = JmeAndroidSystem.getActivity(); + // Get instance of the SensorManager from the current Context + sensorManager = (SensorManager) activity.getSystemService(Context.SENSOR_SERVICE); + // Get instance of Vibrator from current Context + vibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE); + if (vibrator == null) { + logger.log(Level.FINE, "Vibrator Service not found."); + } + } + + private SensorData initSensor(int sensorType) { + boolean success = false; + + SensorData sensorData = sensors.get(sensorType); + if (sensorData != null) { + unRegisterListener(sensorType); + } else { + sensorData = new SensorData(sensorType, null); + sensors.put(sensorType, sensorData); + } + + sensorData.androidSensorType = sensorType; + sensorData.sensor = sensorManager.getDefaultSensor(sensorType); + + if (sensorData.sensor != null) { + logger.log(Level.FINE, "Sensor Type {0} found.", sensorType); + success = registerListener(sensorType); + } else { + logger.log(Level.FINE, "Sensor Type {0} not found.", sensorType); + } + + if (success) { + return sensorData; + } else { + return null; + } + } + + private boolean registerListener(int sensorType) { + SensorData sensorData = sensors.get(sensorType); + if (sensorData != null) { + if (sensorData.enabled) { + logger.log(Level.FINE, "Sensor Already Active: SensorType: {0}, active: {1}", + new Object[]{sensorType, sensorData.enabled}); + return true; + } + sensorData.haveData = false; + if (sensorData.sensor != null) { + if (sensorManager.registerListener(this, sensorData.sensor, sensorData.androidSensorSpeed)) { + sensorData.enabled = true; + logger.log(Level.FINE, "SensorType: {0}, actived: {1}", + new Object[]{sensorType, sensorData.enabled}); + return true; + } else { + sensorData.enabled = false; + logger.log(Level.FINE, "Sensor Type {0} activation failed.", sensorType); + } + } + } + return false; + } + + private void unRegisterListener(int sensorType) { + SensorData sensorData = sensors.get(sensorType); + if (sensorData != null) { + if (sensorData.sensor != null) { + sensorManager.unregisterListener(this, sensorData.sensor); + } + sensorData.enabled = false; + sensorData.haveData = false; + logger.log(Level.FINE, "SensorType: {0} deactivated, active: {1}", + new Object[]{sensorType, sensorData.enabled}); + } + } + + /** + * Pauses the sensors to save battery life if the sensors are not needed. + * Used to pause sensors when the activity pauses + */ + public void pauseSensors() { + for (Entry entry: sensors) { + if (entry.getKey() != Sensor.TYPE_ORIENTATION) { + unRegisterListener(entry.getKey()); + } + } + if (vibrator != null && vibratorActive) { + vibrator.cancel(); + } + } + + /** + * Resumes the sensors. + * Used to resume sensors when the activity comes to the top of the stack + */ + public void resumeSensors() { + for (Entry entry: sensors) { + if (entry.getKey() != Sensor.TYPE_ORIENTATION) { + registerListener(entry.getKey()); + } + } + } + + /* + * Allows the orientation data to be rotated based on the current device + * rotation. This keeps the data aligned with the game when the user + * rotates the device during game play. + * + * Android remapCoordinateSystem from the Android docs + * remapCoordinateSystem(float[] inR, int X, int Y, float[] outR) + * + * @param inR the rotation matrix to be transformed. Usually it is the matrix + * returned by getRotationMatrix(float[], float[], float[], float[]). + * + * @param outR the transformed rotation matrix. inR and outR can be the same + * array, but it is not recommended for performance reason. + * + * X defines on which world (Earth) axis and direction the X axis of the device is mapped. + * Y defines on which world (Earth) axis and direction the Y axis of the device is mapped. + * + * @return True if successful + */ + private boolean remapCoordinates(float[] inR, float[] outR) { + int xDir = SensorManager.AXIS_X; + int yDir = SensorManager.AXIS_Y; + int curRotation = getScreenRotation(); + if (lastRotation != curRotation) { + logger.log(Level.FINE, "Device Rotation changed to: {0}", curRotation); + } + lastRotation = curRotation; + +// logger.log(Level.FINE, "Screen Rotation: {0}", getScreenRotation()); + switch (getScreenRotation()) { + // device natural position + case Surface.ROTATION_0: + xDir = SensorManager.AXIS_X; + yDir = SensorManager.AXIS_Y; + break; + // device rotated 90 deg counterclockwise + case Surface.ROTATION_90: + xDir = SensorManager.AXIS_Y; + yDir = SensorManager.AXIS_MINUS_X; + break; + // device rotated 180 deg counterclockwise + case Surface.ROTATION_180: + xDir = SensorManager.AXIS_MINUS_X; + yDir = SensorManager.AXIS_MINUS_Y; + break; + // device rotated 270 deg counterclockwise + case Surface.ROTATION_270: + xDir = SensorManager.AXIS_MINUS_Y; + yDir = SensorManager.AXIS_X; + break; + default: + break; + } + return SensorManager.remapCoordinateSystem(inR, xDir, yDir, outR); + } + + /** + * Returns the current device rotation. + * Surface.ROTATION_0 = device in natural default rotation + * Surface.ROTATION_90 = device in rotated 90deg counterclockwise + * Surface.ROTATION_180 = device in rotated 180deg counterclockwise + * Surface.ROTATION_270 = device in rotated 270deg counterclockwise + * + * When the Manifest locks the orientation, this value will not change during + * gametime, but if the orientation of the screen is based off the sensor, + * this value will change as the device is rotated. + * @return Current device rotation amount + */ + private int getScreenRotation() { + return activity.getWindowManager().getDefaultDisplay().getRotation(); + } + + /** + * Calculates the device orientation based off the data recieved from the + * Acceleration Sensor and Mangetic Field sensor + * Values are returned relative to the Earth. + * + * From the Android Doc + * + * Computes the device's orientation based on the rotation matrix. When it returns, the array values is filled with the result: + * values[0]: azimuth, rotation around the Z axis. + * values[1]: pitch, rotation around the X axis. + * values[2]: roll, rotation around the Y axis. + * + * The reference coordinate-system used is different from the world + * coordinate-system defined for the rotation matrix: + * X is defined as the vector product Y.Z (It is tangential to the ground at the device's current location and roughly points West). + * Y is tangential to the ground at the device's current location and points towards the magnetic North Pole. + * Z points towards the center of the Earth and is perpendicular to the ground. + * + * @return True if Orientation was calculated + */ + private boolean updateOrientation() { + SensorData sensorData; + AndroidJoystickAxis axis; + final float[] curInclinationMat = new float[16]; + final float[] curRotationMat = new float[16]; + final float[] rotatedRotationMat = new float[16]; + final float[] accValues = new float[3]; + final float[] magValues = new float[3]; + final float[] orderedOrientation = new float[3]; + + // if the Gravity Sensor is available, use it for orientation, if not + // use the accelerometer + // NOTE: Seemed to work worse, so just using accelerometer +// sensorData = sensors.get(Sensor.TYPE_GRAVITY); +// if (sensorData == null) { + sensorData = sensors.get(Sensor.TYPE_ACCELEROMETER); +// } + + if (sensorData == null || !sensorData.enabled || !sensorData.haveData) { + return false; + } + + if (sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) { + return false; + } + + synchronized(sensorData.valuesLock) { + accValues[0] = sensorData.lastValues[0]; + accValues[1] = sensorData.lastValues[1]; + accValues[2] = sensorData.lastValues[2]; + } + + sensorData = sensors.get(Sensor.TYPE_MAGNETIC_FIELD); + if (sensorData == null || !sensorData.enabled || !sensorData.haveData) { + return false; + } + + if (sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) { + return false; + } + + synchronized(sensorData.valuesLock) { + magValues[0] = sensorData.lastValues[0]; + magValues[1] = sensorData.lastValues[1]; + magValues[2] = sensorData.lastValues[2]; + } + + if (SensorManager.getRotationMatrix(curRotationMat, curInclinationMat, accValues, magValues)) { + final float [] orientValues = new float[3]; + if (remapCoordinates(curRotationMat, rotatedRotationMat)) { + SensorManager.getOrientation(rotatedRotationMat, orientValues); +// logger.log(Level.FINE, "Orientation Values: {0}, {1}, {2}", +// new Object[]{orientValues[0], orientValues[1], orientValues[2]}); + + + // need to reorder to make it x, y, z order instead of z, x, y order + orderedOrientation[0] = orientValues[1]; + orderedOrientation[1] = orientValues[2]; + orderedOrientation[2] = orientValues[0]; + + sensorData = sensors.get(Sensor.TYPE_ORIENTATION); + if (sensorData != null && sensorData.axes.size() > 0) { + for (int i=0; i 0) { + vibrator.vibrate(rumblePattern, rumbleRepeatFrom); + vibratorActive = true; + } else { + vibrator.cancel(); + vibratorActive = false; + } + } + + } + + public Joystick[] loadJoysticks(InputManager inputManager) { + this.inputManager = inputManager; + + initSensorManager(); + + SensorData sensorData; + List list = new ArrayList(); + AndroidJoystick joystick; + AndroidJoystickAxis axis; + + joystick = new AndroidJoystick(inputManager, + this, + list.size(), + "AndroidSensorsJoystick"); + list.add(joystick); + + List availSensors = sensorManager.getSensorList(Sensor.TYPE_ALL); + for (Sensor sensor: availSensors) { + logger.log(Level.FINE, "{0} Sensor is available, Type: {1}, Vendor: {2}, Version: {3}", + new Object[]{sensor.getName(), sensor.getType(), sensor.getVendor(), sensor.getVersion()}); + } + + // manually create orientation sensor data since orientation is not a physical sensor + sensorData = new SensorData(Sensor.TYPE_ORIENTATION, null); + sensorData.lastValues = new float[3]; + sensors.put(Sensor.TYPE_ORIENTATION, sensorData); + axis = joystick.addAxis(SensorJoystickAxis.ORIENTATION_X, SensorJoystickAxis.ORIENTATION_X, joystick.getAxisCount(), FastMath.HALF_PI); + joystick.setYAxis(axis); // joystick y axis = rotation around device x axis + sensorData.axes.add(axis); + axis = joystick.addAxis(SensorJoystickAxis.ORIENTATION_Y, SensorJoystickAxis.ORIENTATION_Y, joystick.getAxisCount(), FastMath.HALF_PI); + joystick.setXAxis(axis); // joystick x axis = rotation around device y axis + sensorData.axes.add(axis); + axis = joystick.addAxis(SensorJoystickAxis.ORIENTATION_Z, SensorJoystickAxis.ORIENTATION_Z, joystick.getAxisCount(), FastMath.HALF_PI); + sensorData.axes.add(axis); + + // add axes for physical sensors + sensorData = initSensor(Sensor.TYPE_MAGNETIC_FIELD); + if (sensorData != null) { + sensorData.lastValues = new float[3]; + sensors.put(Sensor.TYPE_MAGNETIC_FIELD, sensorData); +// axis = joystick.addAxis(SensorJoystickAxis.MAGNETIC_X, "MagneticField_X", joystick.getAxisCount(), 1f); +// sensorData.axes.add(axis); +// axis = joystick.addAxis(SensorJoystickAxis.MAGNETIC_Y, "MagneticField_Y", joystick.getAxisCount(), 1f); +// sensorData.axes.add(axis); +// axis = joystick.addAxis(SensorJoystickAxis.MAGNETIC_Z, "MagneticField_Z", joystick.getAxisCount(), 1f); +// sensorData.axes.add(axis); + } + + sensorData = initSensor(Sensor.TYPE_ACCELEROMETER); + if (sensorData != null) { + sensorData.lastValues = new float[3]; + sensors.put(Sensor.TYPE_ACCELEROMETER, sensorData); +// axis = joystick.addAxis(SensorJoystickAxis.ACCELEROMETER_X, "Accelerometer_X", joystick.getAxisCount(), 1f); +// sensorData.axes.add(axis); +// axis = joystick.addAxis(SensorJoystickAxis.ACCELEROMETER_Y, "Accelerometer_Y", joystick.getAxisCount(), 1f); +// sensorData.axes.add(axis); +// axis = joystick.addAxis(SensorJoystickAxis.ACCELEROMETER_Z, "Accelerometer_Z", joystick.getAxisCount(), 1f); +// sensorData.axes.add(axis); + } + +// sensorData = initSensor(Sensor.TYPE_GYROSCOPE); +// if (sensorData != null) { +// sensorData.lastValues = new float[3]; +// } +// +// sensorData = initSensor(Sensor.TYPE_GRAVITY); +// if (sensorData != null) { +// sensorData.lastValues = new float[3]; +// } +// +// sensorData = initSensor(Sensor.TYPE_LINEAR_ACCELERATION); +// if (sensorData != null) { +// sensorData.lastValues = new float[3]; +// } +// +// sensorData = initSensor(Sensor.TYPE_ROTATION_VECTOR); +// if (sensorData != null) { +// sensorData.lastValues = new float[4]; +// } +// +// sensorData = initSensor(Sensor.TYPE_PROXIMITY); +// if (sensorData != null) { +// sensorData.lastValues = new float[1]; +// } +// +// sensorData = initSensor(Sensor.TYPE_LIGHT); +// if (sensorData != null) { +// sensorData.lastValues = new float[1]; +// } +// +// sensorData = initSensor(Sensor.TYPE_PRESSURE); +// if (sensorData != null) { +// sensorData.lastValues = new float[1]; +// } +// +// sensorData = initSensor(Sensor.TYPE_TEMPERATURE); +// if (sensorData != null) { +// sensorData.lastValues = new float[1]; +// } + + + joysticks = list.toArray( new AndroidJoystick[list.size()] ); + loaded = true; + return joysticks; + } + + public void initialize() { + initialized = true; + loaded = false; + } + + public void update() { + if (!loaded) { + return; + } + updateOrientation(); + synchronized (eventQueue){ + // flush events to listener + if (listener != null && eventQueue.size() > 0) { + for (int i = 0; i < eventQueue.size(); i++){ + listener.onJoyAxisEvent(eventQueue.get(i)); + } + eventQueue.clear(); + } + } + } + + public void destroy() { + logger.log(Level.FINE, "Doing Destroy."); + pauseSensors(); + if (sensorManager != null) { + sensorManager.unregisterListener(this); + } + sensors.clear(); + eventQueue.clear(); + initialized = false; + loaded = false; + joysticks = null; + sensorManager = null; + vibrator = null; + activity = null; + } + + public boolean isInitialized() { + return initialized; + } + + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + public long getInputTimeNanos() { + return System.nanoTime(); + } + + // End of JoyInput methods + + // Start of Android SensorEventListener methods + + public void onSensorChanged(SensorEvent se) { + if (!initialized || !loaded) { + return; + } + + int sensorType = se.sensor.getType(); + + SensorData sensorData = sensors.get(sensorType); + if (sensorData != null && sensorData.sensor.equals(se.sensor) && sensorData.enabled) { + + if (sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) { + return; + } + synchronized(sensorData.valuesLock) { + for (int i=0; i 0) { + AndroidJoystickAxis axis; + for (int i=0; i getDeadZone()) { + hasChanged = true; + lastRawValue = curRawValue; + } else { + hasChanged = false; + } + } + + protected float getJoystickAxisValue() { + return (lastRawValue-zeroRawValue) / maxRawValue; + } + + protected boolean isChanged() { + return hasChanged; + } + + public void calibrateCenter() { + zeroRawValue = lastRawValue; + logger.log(Level.FINE, "Calibrating axis {0} to {1}", + new Object[]{getName(), zeroRawValue}); + } + + } +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler.java new file mode 100644 index 000000000..8a2679bdc --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler.java @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.android; + +import android.view.MotionEvent; +import android.view.View; +import com.jme3.input.event.InputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; +import static com.jme3.input.event.TouchEvent.Type.DOWN; +import static com.jme3.input.event.TouchEvent.Type.MOVE; +import static com.jme3.input.event.TouchEvent.Type.UP; +import com.jme3.math.Vector2f; +import java.util.HashMap; +import java.util.logging.Logger; + +/** + * AndroidTouchHandler is the base class that receives touch inputs from the + * Android system and creates the TouchEvents for jME. This class is designed + * to handle the base touch events for Android rev 9 (Android 2.3). This is + * extended by other classes to add features that were introducted after + * Android rev 9. + * + * @author iwgeric + */ +public class AndroidTouchHandler implements View.OnTouchListener { + private static final Logger logger = Logger.getLogger(AndroidTouchHandler.class.getName()); + + final private HashMap lastPositions = new HashMap(); + + protected int numPointers = 0; + + protected AndroidInputHandler androidInput; + protected AndroidGestureHandler gestureHandler; + + public AndroidTouchHandler(AndroidInputHandler androidInput, AndroidGestureHandler gestureHandler) { + this.androidInput = androidInput; + this.gestureHandler = gestureHandler; + } + + public void initialize() { + } + + public void destroy() { + setView(null); + } + + public void setView(View view) { + if (view != null) { + view.setOnTouchListener(this); + } else { + androidInput.getView().setOnTouchListener(null); + } + } + + protected int getPointerIndex(MotionEvent event) { + return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + } + + protected int getPointerId(MotionEvent event) { + return event.getPointerId(getPointerIndex(event)); + } + + protected int getAction(MotionEvent event) { + return event.getAction() & MotionEvent.ACTION_MASK; + } + + /** + * onTouch gets called from android thread on touch events + */ + public boolean onTouch(View view, MotionEvent event) { + if (!androidInput.isInitialized() || view != androidInput.getView()) { + return false; + } + + boolean bWasHandled = false; + TouchEvent touch = null; + // System.out.println("native : " + event.getAction()); + int action = getAction(event); + int pointerIndex = getPointerIndex(event); + int pointerId = getPointerId(event); + Vector2f lastPos = lastPositions.get(pointerId); + float jmeX; + float jmeY; + + numPointers = event.getPointerCount(); + + // final int historySize = event.getHistorySize(); + //final int pointerCount = event.getPointerCount(); + switch (getAction(event)) { + case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_DOWN: + jmeX = androidInput.getJmeX(event.getX(pointerIndex)); + jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex))); + touch = androidInput.getFreeTouchEvent(); + touch.set(TouchEvent.Type.DOWN, jmeX, jmeY, 0, 0); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + + lastPos = new Vector2f(jmeX, jmeY); + lastPositions.put(pointerId, lastPos); + + processEvent(touch); + + bWasHandled = true; + break; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + jmeX = androidInput.getJmeX(event.getX(pointerIndex)); + jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex))); + touch = androidInput.getFreeTouchEvent(); + touch.set(TouchEvent.Type.UP, jmeX, jmeY, 0, 0); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + lastPositions.remove(pointerId); + + processEvent(touch); + + bWasHandled = true; + break; + case MotionEvent.ACTION_MOVE: + // Convert all pointers into events + for (int p = 0; p < event.getPointerCount(); p++) { + jmeX = androidInput.getJmeX(event.getX(p)); + jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(p))); + lastPos = lastPositions.get(event.getPointerId(p)); + if (lastPos == null) { + lastPos = new Vector2f(jmeX, jmeY); + lastPositions.put(event.getPointerId(p), lastPos); + } + + float dX = jmeX - lastPos.x; + float dY = jmeY - lastPos.y; + if (dX != 0 || dY != 0) { + touch = androidInput.getFreeTouchEvent(); + touch.set(TouchEvent.Type.MOVE, jmeX, jmeY, dX, dY); + touch.setPointerId(event.getPointerId(p)); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(p)); + lastPos.set(jmeX, jmeY); + + processEvent(touch); + + bWasHandled = true; + } + } + break; + case MotionEvent.ACTION_OUTSIDE: + break; + + } + + // Try to detect gestures + if (gestureHandler != null) { + gestureHandler.detectGesture(event); + } + + return bWasHandled; + } + + protected void processEvent(TouchEvent event) { + // Add the touch event + androidInput.addEvent(event); + // MouseEvents do not support multi-touch, so only evaluate 1 finger pointer events + if (androidInput.isSimulateMouse() && numPointers == 1) { + InputEvent mouseEvent = generateMouseEvent(event); + if (mouseEvent != null) { + // Add the mouse event + androidInput.addEvent(mouseEvent); + } + } + + } + + // TODO: Ring Buffer for mouse events? + protected InputEvent generateMouseEvent(TouchEvent event) { + InputEvent inputEvent = null; + int newX; + int newY; + int newDX; + int newDY; + + if (androidInput.isMouseEventsInvertX()) { + newX = (int) (androidInput.invertX(event.getX())); + newDX = (int)event.getDeltaX() * -1; + } else { + newX = (int) event.getX(); + newDX = (int)event.getDeltaX(); + } + + if (androidInput.isMouseEventsInvertY()) { + newY = (int) (androidInput.invertY(event.getY())); + newDY = (int)event.getDeltaY() * -1; + } else { + newY = (int) event.getY(); + newDY = (int)event.getDeltaY(); + } + + switch (event.getType()) { + case DOWN: + // Handle mouse down event + inputEvent = new MouseButtonEvent(0, true, newX, newY); + inputEvent.setTime(event.getTime()); + break; + + case UP: + // Handle mouse up event + inputEvent = new MouseButtonEvent(0, false, newX, newY); + inputEvent.setTime(event.getTime()); + break; + + case HOVER_MOVE: + case MOVE: + inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan()); + inputEvent.setTime(event.getTime()); + break; + } + + return inputEvent; + } + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler14.java new file mode 100644 index 000000000..1a785a5e9 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler14.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.android; + +import android.view.MotionEvent; +import android.view.View; +import com.jme3.input.event.TouchEvent; +import com.jme3.math.Vector2f; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidTouchHandler14 is an extension of AndroidTouchHander that adds the + * Android touch event functionality between Android rev 9 (Android 2.3) and + * Android rev 14 (Android 4.0). + * + * @author iwgeric + */ +public class AndroidTouchHandler14 extends AndroidTouchHandler implements + View.OnHoverListener { + private static final Logger logger = Logger.getLogger(AndroidTouchHandler14.class.getName()); + final private HashMap lastHoverPositions = new HashMap(); + + public AndroidTouchHandler14(AndroidInputHandler androidInput, AndroidGestureHandler gestureHandler) { + super(androidInput, gestureHandler); + } + + @Override + public void setView(View view) { + if (view != null) { + view.setOnHoverListener(this); + } else { + androidInput.getView().setOnHoverListener(null); + } + super.setView(view); + } + + public boolean onHover(View view, MotionEvent event) { + if (view == null || view != androidInput.getView()) { + return false; + } + + boolean consumed = false; + int action = getAction(event); + int pointerId = getPointerId(event); + int pointerIndex = getPointerIndex(event); + Vector2f lastPos = lastHoverPositions.get(pointerId); + float jmeX; + float jmeY; + + numPointers = event.getPointerCount(); + + logger.log(Level.INFO, "onHover pointerId: {0}, action: {1}, x: {2}, y: {3}, numPointers: {4}", + new Object[]{pointerId, action, event.getX(), event.getY(), event.getPointerCount()}); + + TouchEvent touchEvent; + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: + jmeX = androidInput.getJmeX(event.getX(pointerIndex)); + jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex))); + touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.HOVER_START, jmeX, jmeY, 0, 0); + touchEvent.setPointerId(pointerId); + touchEvent.setTime(event.getEventTime()); + touchEvent.setPressure(event.getPressure(pointerIndex)); + + lastPos = new Vector2f(jmeX, jmeY); + lastHoverPositions.put(pointerId, lastPos); + + processEvent(touchEvent); + consumed = true; + break; + case MotionEvent.ACTION_HOVER_MOVE: + // Convert all pointers into events + for (int p = 0; p < event.getPointerCount(); p++) { + jmeX = androidInput.getJmeX(event.getX(p)); + jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(p))); + lastPos = lastHoverPositions.get(event.getPointerId(p)); + if (lastPos == null) { + lastPos = new Vector2f(jmeX, jmeY); + lastHoverPositions.put(event.getPointerId(p), lastPos); + } + + float dX = jmeX - lastPos.x; + float dY = jmeY - lastPos.y; + if (dX != 0 || dY != 0) { + touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.HOVER_MOVE, jmeX, jmeY, dX, dY); + touchEvent.setPointerId(event.getPointerId(p)); + touchEvent.setTime(event.getEventTime()); + touchEvent.setPressure(event.getPressure(p)); + lastPos.set(jmeX, jmeY); + + processEvent(touchEvent); + + } + } + consumed = true; + break; + case MotionEvent.ACTION_HOVER_EXIT: + jmeX = androidInput.getJmeX(event.getX(pointerIndex)); + jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex))); + touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.HOVER_END, jmeX, jmeY, 0, 0); + touchEvent.setPointerId(pointerId); + touchEvent.setTime(event.getEventTime()); + touchEvent.setPressure(event.getPressure(pointerIndex)); + lastHoverPositions.remove(pointerId); + + processEvent(touchEvent); + consumed = true; + break; + default: + consumed = false; + break; + } + + return consumed; + } + +} diff --git a/jme3-android/src/main/java/com/jme3/input/android/TouchEventPool.java b/jme3-android/src/main/java/com/jme3/input/android/TouchEventPool.java new file mode 100644 index 000000000..400a3bd38 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/TouchEventPool.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.android; + +import com.jme3.input.event.TouchEvent; +import com.jme3.util.RingBuffer; +import java.util.logging.Logger; + +/** + * TouchEventPool provides a RingBuffer of jME TouchEvents to help with garbage + * collection on Android. Each TouchEvent is stored in the RingBuffer and is + * reused if the TouchEvent has been consumed. + * + * If a TouchEvent has not been consumed, it is placed back into the pool at the + * end for later use. If a TouchEvent has been consumed, it is reused to avoid + * creating lots of little objects. + * + * If the pool is full of unconsumed events, then a new event is created and provided. + * + * + * @author iwgeric + */ +public class TouchEventPool { + private static final Logger logger = Logger.getLogger(TouchEventPool.class.getName()); + private final RingBuffer eventPool; + private final int maxEvents; + + public TouchEventPool (int maxEvents) { + eventPool = new RingBuffer(maxEvents); + this.maxEvents = maxEvents; + } + + public void initialize() { + TouchEvent newEvent; + while (!eventPool.isEmpty()) { + eventPool.pop(); + } + for (int i = 0; i < maxEvents; i++) { + newEvent = new TouchEvent(); + newEvent.setConsumed(); + eventPool.push(newEvent); + } + } + + public void destroy() { + // Clean up queues + while (!eventPool.isEmpty()) { + eventPool.pop(); + } + } + + /** + * Fetches a touch event from the reuse pool + * + * @return a usable TouchEvent + */ + public TouchEvent getNextFreeEvent() { + TouchEvent evt = null; + int curSize = eventPool.size(); + while (curSize > 0) { + evt = (TouchEvent)eventPool.pop(); + if (evt.isConsumed()) { + break; + } else { + eventPool.push(evt); + evt = null; + } + curSize--; + } + + if (evt == null) { + logger.warning("eventPool full of unconsumed events"); + evt = new TouchEvent(); + } + return evt; + } + + /** + * Stores the TouchEvent back in the pool for later reuse. It is only reused + * if the TouchEvent has been consumed. + * + * @param event TouchEvent to store for later use if consumed. + */ + public void storeEvent(TouchEvent event) { + if (eventPool.size() < maxEvents) { + eventPool.push(event); + } else { + logger.warning("eventPool full"); + } + } + +} diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/Android22Workaround.java b/jme3-android/src/main/java/com/jme3/renderer/android/Android22Workaround.java new file mode 100644 index 000000000..9c5bf587a --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/renderer/android/Android22Workaround.java @@ -0,0 +1,14 @@ +package com.jme3.renderer.android; + +import android.opengl.GLES20; + +public class Android22Workaround { + public static void glVertexAttribPointer(int location, int components, int format, boolean normalize, int stride, int offset){ + GLES20.glVertexAttribPointer(location, + components, + format, + normalize, + stride, + offset); + } +} diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGLSurfaceView.java b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGLSurfaceView.java new file mode 100644 index 000000000..b328c6b79 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGLSurfaceView.java @@ -0,0 +1,26 @@ +package com.jme3.renderer.android; + +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.util.AttributeSet; +import java.util.logging.Logger; + +/** + * AndroidGLSurfaceView is derived from GLSurfaceView + * @author iwgeric + * + */ +public class AndroidGLSurfaceView extends GLSurfaceView { + + private final static Logger logger = Logger.getLogger(AndroidGLSurfaceView.class.getName()); + + public AndroidGLSurfaceView(Context ctx, AttributeSet attribs) { + super(ctx, attribs); + } + + public AndroidGLSurfaceView(Context ctx) { + super(ctx); + } + + +} \ No newline at end of file diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java b/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java new file mode 100644 index 000000000..5b2b3b7b1 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java @@ -0,0 +1,2530 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.android; + +import android.opengl.GLES20; +import android.os.Build; +import com.jme3.asset.AndroidImageInfo; +import com.jme3.light.LightList; +import com.jme3.material.RenderState; +import com.jme3.math.*; +import com.jme3.renderer.*; +import com.jme3.renderer.android.TextureUtil.AndroidGLImageFormat; +import com.jme3.scene.Mesh; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.shader.Attribute; +import com.jme3.shader.Shader; +import com.jme3.shader.Shader.ShaderSource; +import com.jme3.shader.Shader.ShaderType; +import com.jme3.shader.Uniform; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.FrameBuffer.RenderBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapAxis; +import com.jme3.util.BufferUtils; +import com.jme3.util.ListMap; +import com.jme3.util.NativeObjectManager; +import java.nio.*; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3tools.shader.ShaderDebug; + +public class OGLESShaderRenderer implements Renderer { + + private static final Logger logger = Logger.getLogger(OGLESShaderRenderer.class.getName()); + private static final boolean VALIDATE_SHADER = false; + private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); + private final StringBuilder stringBuf = new StringBuilder(250); + private final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1); + private final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16); + private final RenderContext context = new RenderContext(); + private final NativeObjectManager objManager = new NativeObjectManager(); + private final EnumSet caps = EnumSet.noneOf(Caps.class); + // current state + private Shader boundShader; + // initalDrawBuf and initialReadBuf are not used on ES, + // http://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindFramebuffer.xml + //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 final int maxFBOAttachs = 1; // Only 1 color attachment on ES + private final int maxMRTFBOAttachs = 1; // FIXME for now, not sure if > 1 is needed for ES + private int maxRBSize; + private int maxTexSize; + private int maxCubeTexSize; + private int maxVertCount; + private int maxTriCount; + private boolean tdc; + private FrameBuffer lastFb = null; + private FrameBuffer mainFbOverride = null; + private final Statistics statistics = new Statistics(); + private int vpX, vpY, vpW, vpH; + private int clipX, clipY, clipW, clipH; + //private final GL10 gl; + private boolean powerVr = false; + private boolean useVBO = false; + + public OGLESShaderRenderer() { + } + + 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; + } + + private int extractVersion(String prefixStr, String versionStr) { + if (versionStr != null) { + int spaceIdx = versionStr.indexOf(" ", prefixStr.length()); + if (spaceIdx >= 1) { + versionStr = versionStr.substring(prefixStr.length(), spaceIdx).trim(); + } else { + versionStr = versionStr.substring(prefixStr.length()).trim(); + } + //some device have ":" at the end of the version. + versionStr = versionStr.replaceAll("\\:", ""); + float version = Float.parseFloat(versionStr); + return (int) (version * 100); + } else { + return -1; + } + } + + public void initialize() { + logger.log(Level.FINE, "Vendor: {0}", GLES20.glGetString(GLES20.GL_VENDOR)); + logger.log(Level.FINE, "Renderer: {0}", GLES20.glGetString(GLES20.GL_RENDERER)); + logger.log(Level.FINE, "Version: {0}", GLES20.glGetString(GLES20.GL_VERSION)); + logger.log(Level.FINE, "Shading Language Version: {0}", GLES20.glGetString(GLES20.GL_SHADING_LANGUAGE_VERSION)); + + powerVr = GLES20.glGetString(GLES20.GL_RENDERER).contains("PowerVR"); + + /* + // Fix issue in TestRenderToMemory when GL_FRONT is the main + // buffer being used. + initialDrawBuf = glGetInteger(GL_DRAW_BUFFER); + initialReadBuf = glGetInteger(GL_READ_BUFFER); + + // XXX: This has to be GL_BACK for canvas on Mac + // Since initialDrawBuf is GL_FRONT for pbuffer, gotta + // change this value later on ... +// initialDrawBuf = GL_BACK; +// initialReadBuf = GL_BACK; + */ + + // Check OpenGL version + int openGlVer = extractVersion("OpenGL ES ", GLES20.glGetString(GLES20.GL_VERSION)); + if (openGlVer == -1) { + glslVer = -1; + throw new UnsupportedOperationException("OpenGL ES 2.0+ is required for OGLESShaderRenderer!"); + } + + // Check shader language version + glslVer = extractVersion("OpenGL ES GLSL ES ", GLES20.glGetString(GLES20.GL_SHADING_LANGUAGE_VERSION)); + switch (glslVer) { + // TODO: When new versions of OpenGL ES shader language come out, + // update this. + default: + caps.add(Caps.GLSL100); + break; + } + + GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16); + vertexTextureUnits = intBuf16.get(0); + logger.log(Level.FINE, "VTF Units: {0}", vertexTextureUnits); + if (vertexTextureUnits > 0) { + caps.add(Caps.VertexTextureFetch); + } + + GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16); + fragTextureUnits = intBuf16.get(0); + logger.log(Level.FINE, "Texture Units: {0}", fragTextureUnits); + + // Multiply vector count by 4 to get float count. + GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_UNIFORM_VECTORS, intBuf16); + vertexUniforms = intBuf16.get(0) * 4; + logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms); + + GLES20.glGetIntegerv(GLES20.GL_MAX_FRAGMENT_UNIFORM_VECTORS, intBuf16); + fragUniforms = intBuf16.get(0) * 4; + logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); + + GLES20.glGetIntegerv(GLES20.GL_MAX_VARYING_VECTORS, intBuf16); + int varyingFloats = intBuf16.get(0) * 4; + logger.log(Level.FINER, "Varying Floats: {0}", varyingFloats); + + GLES20.glGetIntegerv(GLES20.GL_MAX_VERTEX_ATTRIBS, intBuf16); + vertexAttribs = intBuf16.get(0); + logger.log(Level.FINE, "Vertex Attributes: {0}", vertexAttribs); + + GLES20.glGetIntegerv(GLES20.GL_SUBPIXEL_BITS, intBuf16); + int subpixelBits = intBuf16.get(0); + logger.log(Level.FINE, "Subpixel Bits: {0}", subpixelBits); + +// GLES10.glGetIntegerv(GLES10.GL_MAX_ELEMENTS_VERTICES, intBuf16); +// maxVertCount = intBuf16.get(0); +// logger.log(Level.FINER, "Preferred Batch Vertex Count: {0}", maxVertCount); +// +// GLES10.glGetIntegerv(GLES10.GL_MAX_ELEMENTS_INDICES, intBuf16); +// maxTriCount = intBuf16.get(0); +// logger.log(Level.FINER, "Preferred Batch Index Count: {0}", maxTriCount); + + GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, intBuf16); + maxTexSize = intBuf16.get(0); + logger.log(Level.FINE, "Maximum Texture Resolution: {0}", maxTexSize); + + GLES20.glGetIntegerv(GLES20.GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16); + maxCubeTexSize = intBuf16.get(0); + logger.log(Level.FINE, "Maximum CubeMap Resolution: {0}", maxCubeTexSize); + + GLES20.glGetIntegerv(GLES20.GL_MAX_RENDERBUFFER_SIZE, intBuf16); + maxRBSize = intBuf16.get(0); + logger.log(Level.FINER, "FBO RB Max Size: {0}", maxRBSize); + + /* + 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_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_EXT_packed_float){ + caps.add(Caps.PackedFloatColorBuffer); + if (ctxCaps.GL_ARB_half_float_pixel){ + // because textures are usually uploaded as RGB16F + // need half-float pixel + caps.add(Caps.PackedFloatTexture); + } + } + + if (ctxCaps.GL_EXT_texture_array) + caps.add(Caps.TextureArray); + + if (ctxCaps.GL_EXT_texture_shared_exponent) + caps.add(Caps.SharedExponentTexture); + + if (ctxCaps.GL_EXT_framebuffer_object){ + caps.add(Caps.FrameBuffer); + + glGetInteger(GL_MAX_RENDERBUFFER_SIZE_EXT, intBuf16); + maxRBSize = intBuf16.get(0); + logger.log(Level.FINER, "FBO RB Max Size: {0}", maxRBSize); + + glGetInteger(GL_MAX_COLOR_ATTACHMENTS_EXT, intBuf16); + maxFBOAttachs = intBuf16.get(0); + logger.log(Level.FINER, "FBO Max renderbuffers: {0}", maxFBOAttachs); + + if (ctxCaps.GL_EXT_framebuffer_multisample){ + caps.add(Caps.FrameBufferMultisample); + + glGetInteger(GL_MAX_SAMPLES_EXT, intBuf16); + maxFBOSamples = intBuf16.get(0); + logger.log(Level.FINER, "FBO Max Samples: {0}", maxFBOSamples); + } + + if (ctxCaps.GL_ARB_draw_buffers){ + caps.add(Caps.FrameBufferMRT); + glGetInteger(ARBDrawBuffers.GL_MAX_DRAW_BUFFERS_ARB, intBuf16); + maxMRTFBOAttachs = intBuf16.get(0); + logger.log(Level.FINER, "FBO Max MRT renderbuffers: {0}", maxMRTFBOAttachs); + } + } + + if (ctxCaps.GL_ARB_multisample){ + glGetInteger(ARBMultisample.GL_SAMPLE_BUFFERS_ARB, intBuf16); + boolean available = intBuf16.get(0) != 0; + glGetInteger(ARBMultisample.GL_SAMPLES_ARB, intBuf16); + int samples = intBuf16.get(0); + logger.log(Level.FINER, "Samples: {0}", samples); + boolean enabled = glIsEnabled(ARBMultisample.GL_MULTISAMPLE_ARB); + if (samples > 0 && available && !enabled){ + glEnable(ARBMultisample.GL_MULTISAMPLE_ARB); + } + } + */ + + String extensions = GLES20.glGetString(GLES20.GL_EXTENSIONS); + logger.log(Level.FINE, "GL_EXTENSIONS: {0}", extensions); + + // Get number of compressed formats available. + GLES20.glGetIntegerv(GLES20.GL_NUM_COMPRESSED_TEXTURE_FORMATS, intBuf16); + int numCompressedFormats = intBuf16.get(0); + + // Allocate buffer for compressed formats. + IntBuffer compressedFormats = BufferUtils.createIntBuffer(numCompressedFormats); + GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, compressedFormats); + + // Check for errors after all glGet calls. + RendererUtil.checkGLError(); + + // Print compressed formats. + for (int i = 0; i < numCompressedFormats; i++) { + logger.log(Level.FINE, "Compressed Texture Formats: {0}", compressedFormats.get(i)); + } + + TextureUtil.loadTextureFeatures(extensions); + + applyRenderState(RenderState.DEFAULT); + GLES20.glDisable(GLES20.GL_DITHER); + RendererUtil.checkGLError(); + + useVBO = false; + + // NOTE: SDK_INT is only available since 1.6, + // but for jME3 it doesn't matter since android versions 1.5 and below + // are not supported. + if (Build.VERSION.SDK_INT >= 9){ + logger.log(Level.FINE, "Force-enabling VBO (Android 2.3 or higher)"); + useVBO = true; + } else { + useVBO = false; + } + + logger.log(Level.FINE, "Caps: {0}", caps); + } + + /** + * resetGLObjects should be called when die GLView gets recreated to reset all GPU objects + */ + public void resetGLObjects() { + objManager.resetObjects(); + statistics.clearMemory(); + boundShader = null; + lastFb = null; + context.reset(); + } + + public void cleanup() { + objManager.deleteAllObjects(this); + statistics.clearMemory(); + } + + private void checkCap(Caps cap) { + if (!caps.contains(cap)) { + throw new UnsupportedOperationException("Required capability missing: " + cap.name()); + } + } + + /*********************************************************************\ + |* Render State *| + \*********************************************************************/ + public void setDepthRange(float start, float end) { + GLES20.glDepthRangef(start, end); + RendererUtil.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) { + GLES20.glClear(bits); + RendererUtil.checkGLError(); + } + } + + public void setBackgroundColor(ColorRGBA color) { + GLES20.glClearColor(color.r, color.g, color.b, color.a); + RendererUtil.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) { + GLES20.glEnable(GLES20.GL_DEPTH_TEST); + GLES20.glDepthFunc(convertTestFunction(context.depthFunc)); + RendererUtil.checkGLError(); + context.depthTestEnabled = true; + } else if (!state.isDepthTest() && context.depthTestEnabled) { + GLES20.glDisable(GLES20.GL_DEPTH_TEST); + RendererUtil.checkGLError(); + context.depthTestEnabled = false; + } + if (state.getDepthFunc() != context.depthFunc) { + GLES20.glDepthFunc(convertTestFunction(state.getDepthFunc())); + context.depthFunc = state.getDepthFunc(); + } + + if (state.isDepthWrite() && !context.depthWriteEnabled) { + GLES20.glDepthMask(true); + RendererUtil.checkGLError(); + context.depthWriteEnabled = true; + } else if (!state.isDepthWrite() && context.depthWriteEnabled) { + GLES20.glDepthMask(false); + RendererUtil.checkGLError(); + context.depthWriteEnabled = false; + } + if (state.isColorWrite() && !context.colorWriteEnabled) { + GLES20.glColorMask(true, true, true, true); + RendererUtil.checkGLError(); + context.colorWriteEnabled = true; + } else if (!state.isColorWrite() && context.colorWriteEnabled) { + GLES20.glColorMask(false, false, false, false); + RendererUtil.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) { + GLES20.glEnable(GLES20.GL_POLYGON_OFFSET_FILL); + GLES20.glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + RendererUtil.checkGLError(); + + context.polyOffsetEnabled = true; + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } else { + if (state.getPolyOffsetFactor() != context.polyOffsetFactor + || state.getPolyOffsetUnits() != context.polyOffsetUnits) { + GLES20.glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + RendererUtil.checkGLError(); + + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } + } + } else { + if (context.polyOffsetEnabled) { + GLES20.glDisable(GLES20.GL_POLYGON_OFFSET_FILL); + RendererUtil.checkGLError(); + + context.polyOffsetEnabled = false; + context.polyOffsetFactor = 0; + context.polyOffsetUnits = 0; + } + } + if (state.getFaceCullMode() != context.cullMode) { + if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) { + GLES20.glDisable(GLES20.GL_CULL_FACE); + RendererUtil.checkGLError(); + } else { + GLES20.glEnable(GLES20.GL_CULL_FACE); + RendererUtil.checkGLError(); + } + + switch (state.getFaceCullMode()) { + case Off: + break; + case Back: + GLES20.glCullFace(GLES20.GL_BACK); + RendererUtil.checkGLError(); + break; + case Front: + GLES20.glCullFace(GLES20.GL_FRONT); + RendererUtil.checkGLError(); + break; + case FrontAndBack: + GLES20.glCullFace(GLES20.GL_FRONT_AND_BACK); + RendererUtil.checkGLError(); + 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) { + GLES20.glDisable(GLES20.GL_BLEND); + RendererUtil.checkGLError(); + } else { + GLES20.glEnable(GLES20.GL_BLEND); + switch (state.getBlendMode()) { + case Off: + break; + case Additive: + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE); + break; + case AlphaAdditive: + GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE); + break; + case Color: + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_COLOR); + break; + case Alpha: + GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); + break; + case PremultAlpha: + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); + break; + case Modulate: + GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_ZERO); + break; + case ModulateX2: + GLES20.glBlendFunc(GLES20.GL_DST_COLOR, GLES20.GL_SRC_COLOR); + break; + default: + throw new UnsupportedOperationException("Unrecognized blend mode: " + + state.getBlendMode()); + } + RendererUtil.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) { + GLES20.glViewport(x, y, w, h); + RendererUtil.checkGLError(); + + vpX = x; + vpY = y; + vpW = w; + vpH = h; + } + } + + public void setClipRect(int x, int y, int width, int height) { + if (!context.clipRectEnabled) { + GLES20.glEnable(GLES20.GL_SCISSOR_TEST); + RendererUtil.checkGLError(); + context.clipRectEnabled = true; + } + if (clipX != x || clipY != y || clipW != width || clipH != height) { + GLES20.glScissor(x, y, width, height); + RendererUtil.checkGLError(); + clipX = x; + clipY = y; + clipW = width; + clipH = height; + } + } + + public void clearClipRect() { + if (context.clipRectEnabled) { + GLES20.glDisable(GLES20.GL_SCISSOR_TEST); + RendererUtil.checkGLError(); + context.clipRectEnabled = false; + + clipX = 0; + clipY = 0; + clipW = 0; + clipH = 0; + } + } + + public void onFrame() { + RendererUtil.checkGLErrorForced(); + + objManager.deleteUnused(this); + } + + 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 = GLES20.glGetUniformLocation(shader.getId(), uniform.getName()); + RendererUtil.checkGLError(); + + if (loc < 0) { + uniform.setLocation(-1); + // uniform is not declared in shader + } else { + uniform.setLocation(loc); + } + } + + protected void bindProgram(Shader shader) { + int shaderId = shader.getId(); + if (context.boundShaderProgram != shaderId) { + GLES20.glUseProgram(shaderId); + RendererUtil.checkGLError(); + + statistics.onShaderUse(shader, true); + boundShader = shader; + context.boundShaderProgram = shaderId; + } else { + statistics.onShaderUse(shader, false); + } + } + + protected void updateUniform(Shader shader, Uniform uniform) { + int shaderId = shader.getId(); + + assert uniform.getName() != null; + assert shader.getId() > 0; + + if (context.boundShaderProgram != shaderId) { + GLES20.glUseProgram(shaderId); + RendererUtil.checkGLError(); + + 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) { + // removed logging the warning to avoid flooding the log + // (LWJGL also doesn't post a warning) + //logger.log(Level.FINEST, "Uniform value is not set yet. Shader: {0}, Uniform: {1}", + // new Object[]{shader.toString(), uniform.toString()}); + return; // value not set yet.. + } + + statistics.onUniformSet(); + + uniform.clearUpdateNeeded(); + FloatBuffer fb; + IntBuffer ib; + switch (uniform.getVarType()) { + case Float: + Float f = (Float) uniform.getValue(); + GLES20.glUniform1f(loc, f.floatValue()); + break; + case Vector2: + Vector2f v2 = (Vector2f) uniform.getValue(); + GLES20.glUniform2f(loc, v2.getX(), v2.getY()); + break; + case Vector3: + Vector3f v3 = (Vector3f) uniform.getValue(); + GLES20.glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ()); + break; + case Vector4: + Object val = uniform.getValue(); + if (val instanceof ColorRGBA) { + ColorRGBA c = (ColorRGBA) val; + GLES20.glUniform4f(loc, c.r, c.g, c.b, c.a); + } else if (val instanceof Vector4f) { + Vector4f c = (Vector4f) val; + GLES20.glUniform4f(loc, c.x, c.y, c.z, c.w); + } else { + Quaternion c = (Quaternion) uniform.getValue(); + GLES20.glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW()); + } + break; + case Boolean: + Boolean b = (Boolean) uniform.getValue(); + GLES20.glUniform1i(loc, b.booleanValue() ? GLES20.GL_TRUE : GLES20.GL_FALSE); + break; + case Matrix3: + fb = (FloatBuffer) uniform.getValue(); + assert fb.remaining() == 9; + GLES20.glUniformMatrix3fv(loc, 1, false, fb); + break; + case Matrix4: + fb = (FloatBuffer) uniform.getValue(); + assert fb.remaining() == 16; + GLES20.glUniformMatrix4fv(loc, 1, false, fb); + break; + case IntArray: + ib = (IntBuffer) uniform.getValue(); + GLES20.glUniform1iv(loc, ib.limit(), ib); + break; + case FloatArray: + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniform1fv(loc, fb.limit(), fb); + break; + case Vector2Array: + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniform2fv(loc, fb.limit() / 2, fb); + break; + case Vector3Array: + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniform3fv(loc, fb.limit() / 3, fb); + break; + case Vector4Array: + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniform4fv(loc, fb.limit() / 4, fb); + break; + case Matrix4Array: + fb = (FloatBuffer) uniform.getValue(); + GLES20.glUniformMatrix4fv(loc, fb.limit() / 16, false, fb); + break; + case Int: + Integer i = (Integer) uniform.getValue(); + GLES20.glUniform1i(loc, i.intValue()); + break; + default: + throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType()); + } + RendererUtil.checkGLError(); + } + + protected void updateShaderUniforms(Shader shader) { + ListMap uniforms = shader.getUniformMap(); + 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 (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) { + int id = source.getId(); + if (id == -1) { + // Create id + id = GLES20.glCreateShader(convertShaderType(source.getType())); + RendererUtil.checkGLError(); + + if (id <= 0) { + throw new RendererException("Invalid ID received when trying to create shader."); + } + source.setId(id); + } + + if (!source.getLanguage().equals("GLSL100")) { + throw new RendererException("This shader cannot run in OpenGL ES. " + + "Only GLSL 1.0 shaders are supported."); + } + + // upload shader source + // merge the defines and source code + byte[] definesCodeData = source.getDefines().getBytes(); + byte[] sourceCodeData = source.getSource().getBytes(); + ByteBuffer codeBuf = BufferUtils.createByteBuffer(definesCodeData.length + + sourceCodeData.length); + codeBuf.put(definesCodeData); + codeBuf.put(sourceCodeData); + codeBuf.flip(); + + if (powerVr && source.getType() == ShaderType.Vertex) { + // XXX: This is to fix a bug in old PowerVR, remove + // when no longer applicable. + GLES20.glShaderSource( + id, source.getDefines() + + source.getSource()); + } else { + String precision =""; + if (source.getType() == ShaderType.Fragment) { + precision = "precision mediump float;\n"; + } + GLES20.glShaderSource( + id, + precision + +source.getDefines() + + source.getSource()); + } +// int range[] = new int[2]; +// int precision[] = new int[1]; +// GLES20.glGetShaderPrecisionFormat(GLES20.GL_VERTEX_SHADER, GLES20.GL_HIGH_FLOAT, range, 0, precision, 0); +// System.out.println("PRECISION HIGH FLOAT VERTEX"); +// System.out.println("range "+range[0]+"," +range[1]); +// System.out.println("precision "+precision[0]); + + GLES20.glCompileShader(id); + RendererUtil.checkGLError(); + + GLES20.glGetShaderiv(id, GLES20.GL_COMPILE_STATUS, intBuf1); + RendererUtil.checkGLError(); + + boolean compiledOK = intBuf1.get(0) == GLES20.GL_TRUE; + String infoLog = null; + + if (VALIDATE_SHADER || !compiledOK) { + // even if compile succeeded, check + // log for warnings + GLES20.glGetShaderiv(id, GLES20.GL_INFO_LOG_LENGTH, intBuf1); + RendererUtil.checkGLError(); + infoLog = GLES20.glGetShaderInfoLog(id); + } + + if (compiledOK) { + if (infoLog != null) { + logger.log(Level.FINE, "compile success: {0}, {1}", new Object[]{source.getName(), infoLog}); + } else { + logger.log(Level.FINE, "compile success: {0}", source.getName()); + } + source.clearUpdateNeeded(); + } else { + logger.log(Level.WARNING, "Bad compile of:\n{0}", + new Object[]{ShaderDebug.formatShaderSource(source.getDefines(), source.getSource(),stringBuf.toString())}); + if (infoLog != null) { + throw new RendererException("compile error in:" + source + " error:" + infoLog); + } else { + throw new RendererException("compile error in:" + source + " error: "); + } + } + } + + public void updateShaderData(Shader shader) { + int id = shader.getId(); + boolean needRegister = false; + if (id == -1) { + // create program + id = GLES20.glCreateProgram(); + RendererUtil.checkGLError(); + + 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); + } + + GLES20.glAttachShader(id, source.getId()); + RendererUtil.checkGLError(); + } + + // link shaders to program + GLES20.glLinkProgram(id); + RendererUtil.checkGLError(); + + GLES20.glGetProgramiv(id, GLES20.GL_LINK_STATUS, intBuf1); + RendererUtil.checkGLError(); + + boolean linkOK = intBuf1.get(0) == GLES20.GL_TRUE; + String infoLog = null; + + if (VALIDATE_SHADER || !linkOK) { + GLES20.glGetProgramiv(id, GLES20.GL_INFO_LOG_LENGTH, intBuf1); + RendererUtil.checkGLError(); + + int length = intBuf1.get(0); + if (length > 3) { + // get infos + infoLog = GLES20.glGetProgramInfoLog(id); + RendererUtil.checkGLError(); + } + } + + if (linkOK) { + if (infoLog != null) { + logger.log(Level.FINE, "shader link success. \n{0}", infoLog); + } else { + logger.fine("shader link success"); + } + shader.clearUpdateNeeded(); + if (needRegister) { + // Register shader for clean up if it was created in this method. + objManager.registerObject(shader); + statistics.onNewShader(); + } else { + // OpenGL spec: uniform locations may change after re-link + resetUniformLocations(shader); + } + } else { + if (infoLog != null) { + throw new RendererException("Shader link failure, shader:" + shader + " info:" + infoLog); + } else { + throw new RendererException("Shader link failure, shader:" + shader + " info: "); + } + } + } + + public void setShader(Shader shader) { + if (shader == null) { + throw new IllegalArgumentException("Shader cannot be null"); + } else { + if (shader.isUpdateNeeded()) { + updateShaderData(shader); + } + + // NOTE: might want to check if any of the + // sources need an update? + + assert shader.getId() > 0; + + updateShaderUniforms(shader); + bindProgram(shader); + } + } + + public void deleteShaderSource(ShaderSource source) { + if (source.getId() < 0) { + logger.warning("Shader source is not uploaded to GPU, cannot delete."); + return; + } + + source.clearUpdateNeeded(); + + GLES20.glDeleteShader(source.getId()); + RendererUtil.checkGLError(); + + 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) { + GLES20.glDetachShader(shader.getId(), source.getId()); + RendererUtil.checkGLError(); + + deleteShaderSource(source); + } + } + + GLES20.glDeleteProgram(shader.getId()); + RendererUtil.checkGLError(); + + statistics.onDeleteShader(); + shader.resetObject(); + } + + private int convertTestFunction(RenderState.TestFunction testFunc) { + switch (testFunc) { + case Never: + return GLES20.GL_NEVER; + case Less: + return GLES20.GL_LESS; + case LessOrEqual: + return GLES20.GL_LEQUAL; + case Greater: + return GLES20.GL_GREATER; + case GreaterOrEqual: + return GLES20.GL_GEQUAL; + case Equal: + return GLES20.GL_EQUAL; + case NotEqual: + return GLES20.GL_NOTEQUAL; + case Always: + return GLES20.GL_ALWAYS; + default: + throw new UnsupportedOperationException("Unrecognized test function: " + testFunc); + } + } + + /*********************************************************************\ + |* Framebuffers *| + \*********************************************************************/ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { + copyFrameBuffer(src, dst, true); + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { + throw new RendererException("Copy framebuffer not implemented yet."); + +// if (GLContext.getCapabilities().GL_EXT_framebuffer_blit) { +// int srcX0 = 0; +// int srcY0 = 0; +// int srcX1 = 0; +// int srcY1 = 0; +// +// int dstX0 = 0; +// int dstY0 = 0; +// int dstX1 = 0; +// int dstY1 = 0; +// +// int prevFBO = context.boundFBO; +// +// if (mainFbOverride != null) { +// if (src == null) { +// src = mainFbOverride; +// } +// if (dst == null) { +// dst = mainFbOverride; +// } +// } +// +// if (src != null && src.isUpdateNeeded()) { +// updateFrameBuffer(src); +// } +// +// if (dst != null && dst.isUpdateNeeded()) { +// updateFrameBuffer(dst); +// } +// +// if (src == null) { +// GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); +// srcX0 = vpX; +// srcY0 = vpY; +// srcX1 = vpX + vpW; +// srcY1 = vpY + vpH; +// } else { +// GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, src.getId()); +// srcX1 = src.getWidth(); +// srcY1 = src.getHeight(); +// } +// if (dst == null) { +// GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); +// dstX0 = vpX; +// dstY0 = vpY; +// dstX1 = vpX + vpW; +// dstY1 = vpY + vpH; +// } else { +// GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, dst.getId()); +// dstX1 = dst.getWidth(); +// dstY1 = dst.getHeight(); +// } +// +// +// int mask = GL_COLOR_BUFFER_BIT; +// if (copyDepth) { +// mask |= GL_DEPTH_BUFFER_BIT; +// } +// GLES20.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, +// dstX0, dstY0, dstX1, dstY1, mask, +// GL_NEAREST); +// +// +// GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, prevFBO); +// try { +// checkFrameBufferError(); +// } catch (IllegalStateException ex) { +// logger.log(Level.SEVERE, "Source FBO:\n{0}", src); +// logger.log(Level.SEVERE, "Dest FBO:\n{0}", dst); +// throw ex; +// } +// } else { +// throw new RendererException("EXT_framebuffer_blit required."); +// // TODO: support non-blit copies? +// } + } + + private void checkFrameBufferStatus(FrameBuffer fb) { + try { + checkFrameBufferError(); + } catch (IllegalStateException ex) { + logger.log(Level.SEVERE, "=== jMonkeyEngine FBO State ===\n{0}", fb); + printRealFrameBufferInfo(fb); + throw ex; + } + } + + private void checkFrameBufferError() { + int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); + switch (status) { + case GLES20.GL_FRAMEBUFFER_COMPLETE: + break; + case GLES20.GL_FRAMEBUFFER_UNSUPPORTED: + //Choose different formats + throw new IllegalStateException("Framebuffer object format is " + + "unsupported by the video hardware."); + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + throw new IllegalStateException("Framebuffer has erronous attachment."); + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + throw new IllegalStateException("Framebuffer doesn't have any renderbuffers attached."); + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + throw new IllegalStateException("Framebuffer attachments must have same dimensions."); +// case GLES20.GL_FRAMEBUFFER_INCOMPLETE_FORMATS: +// throw new IllegalStateException("Framebuffer attachments must have same formats."); +// case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: +// throw new IllegalStateException("Incomplete draw buffer."); +// case GLES20.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: +// throw new IllegalStateException("Incomplete read buffer."); +// case GLES20.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: " + status); + } + } + + private void printRealRenderBufferInfo(FrameBuffer fb, RenderBuffer rb, String name) { + System.out.println("== Renderbuffer " + name + " =="); + System.out.println("RB ID: " + rb.getId()); + System.out.println("Is proper? " + GLES20.glIsRenderbuffer(rb.getId())); + + int attachment = convertAttachmentSlot(rb.getSlot()); + + intBuf16.clear(); + GLES20.glGetFramebufferAttachmentParameteriv(GLES20.GL_FRAMEBUFFER, + attachment, GLES20.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, intBuf16); + int type = intBuf16.get(0); + + intBuf16.clear(); + GLES20.glGetFramebufferAttachmentParameteriv(GLES20.GL_FRAMEBUFFER, + attachment, GLES20.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, intBuf16); + int rbName = intBuf16.get(0); + + switch (type) { + case GLES20.GL_NONE: + System.out.println("Type: None"); + break; + case GLES20.GL_TEXTURE: + System.out.println("Type: Texture"); + break; + case GLES20.GL_RENDERBUFFER: + System.out.println("Type: Buffer"); + System.out.println("RB ID: " + rbName); + break; + } + + + + } + + private void printRealFrameBufferInfo(FrameBuffer fb) { +// boolean doubleBuffer = GLES20.glGetBooleanv(GLES20.GL_DOUBLEBUFFER); + boolean doubleBuffer = false; // FIXME +// String drawBuf = getTargetBufferName(glGetInteger(GL_DRAW_BUFFER)); +// String readBuf = getTargetBufferName(glGetInteger(GL_READ_BUFFER)); + + int fbId = fb.getId(); + intBuf16.clear(); +// int curDrawBinding = GLES20.glGetIntegerv(GLES20.GL_DRAW_FRAMEBUFFER_BINDING); +// int curReadBinding = glGetInteger(ARBFramebufferObject.GL_READ_FRAMEBUFFER_BINDING); + + System.out.println("=== OpenGL FBO State ==="); + System.out.println("Context doublebuffered? " + doubleBuffer); + System.out.println("FBO ID: " + fbId); + System.out.println("Is proper? " + GLES20.glIsFramebuffer(fbId)); +// System.out.println("Is bound to draw? " + (fbId == curDrawBinding)); +// System.out.println("Is bound to read? " + (fbId == curReadBinding)); +// System.out.println("Draw buffer: " + drawBuf); +// System.out.println("Read buffer: " + readBuf); + + if (context.boundFBO != fbId) { + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbId); + context.boundFBO = fbId; + } + + if (fb.getDepthBuffer() != null) { + printRealRenderBufferInfo(fb, fb.getDepthBuffer(), "Depth"); + } + for (int i = 0; i < fb.getNumColorBuffers(); i++) { + printRealRenderBufferInfo(fb, fb.getColorBuffer(i), "Color" + i); + } + } + + private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb) { + int id = rb.getId(); + if (id == -1) { + GLES20.glGenRenderbuffers(1, intBuf1); + RendererUtil.checkGLError(); + + id = intBuf1.get(0); + rb.setId(id); + } + + if (context.boundRB != id) { + GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, id); + RendererUtil.checkGLError(); + + context.boundRB = id; + } + + if (fb.getWidth() > maxRBSize || fb.getHeight() > maxRBSize) { + throw new RendererException("Resolution " + fb.getWidth() + + ":" + fb.getHeight() + " is not supported."); + } + + AndroidGLImageFormat imageFormat = TextureUtil.getImageFormat(rb.getFormat()); + if (imageFormat.renderBufferStorageFormat == 0) { + throw new RendererException("The format '" + rb.getFormat() + "' cannot be used for renderbuffers."); + } + +// if (fb.getSamples() > 1 && GLContext.getCapabilities().GL_EXT_framebuffer_multisample) { + if (fb.getSamples() > 1) { +// // FIXME + throw new RendererException("Multisample FrameBuffer is not supported yet."); +// int samples = fb.getSamples(); +// if (maxFBOSamples < samples) { +// samples = maxFBOSamples; +// } +// glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, +// samples, +// glFmt.internalFormat, +// fb.getWidth(), +// fb.getHeight()); + } else { + GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, + imageFormat.renderBufferStorageFormat, + fb.getWidth(), + fb.getHeight()); + + RendererUtil.checkGLError(); + } + } + + private int convertAttachmentSlot(int attachmentSlot) { + // can also add support for stencil here + if (attachmentSlot == -100) { + return GLES20.GL_DEPTH_ATTACHMENT; + } else if (attachmentSlot == 0) { + return GLES20.GL_COLOR_ATTACHMENT0; + } else { + throw new UnsupportedOperationException("Android does not support multiple color attachments to an FBO"); + } + } + + public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) { + Texture tex = rb.getTexture(); + Image image = tex.getImage(); + if (image.isUpdateNeeded()) { + updateTexImageData(image, tex.getType()); + + // NOTE: For depth textures, sets nearest/no-mips mode + // Required to fix "framebuffer unsupported" + // for old NVIDIA drivers! + setupTextureParams(tex); + } + + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, + convertAttachmentSlot(rb.getSlot()), + convertTextureType(tex.getType()), + image.getId(), + 0); + + RendererUtil.checkGLError(); + } + + 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) { + GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, + convertAttachmentSlot(rb.getSlot()), + GLES20.GL_RENDERBUFFER, + rb.getId()); + + RendererUtil.checkGLError(); + } + } + + public void updateFrameBuffer(FrameBuffer fb) { + int id = fb.getId(); + if (id == -1) { + intBuf1.clear(); + // create FBO + GLES20.glGenFramebuffers(1, intBuf1); + RendererUtil.checkGLError(); + + id = intBuf1.get(0); + fb.setId(id); + objManager.registerObject(fb); + + statistics.onNewFrameBuffer(); + } + + if (context.boundFBO != id) { + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, id); + RendererUtil.checkGLError(); + + // binding an FBO automatically sets draw buf to GL_COLOR_ATTACHMENT0 + context.boundDrawBuf = 0; + context.boundFBO = id; + } + + FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer(); + if (depthBuf != null) { + updateFrameBufferAttachment(fb, depthBuf); + } + + for (int i = 0; i < fb.getNumColorBuffers(); i++) { + FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i); + updateFrameBufferAttachment(fb, colorBuf); + } + + fb.clearUpdateNeeded(); + } + + public void setMainFrameBufferOverride(FrameBuffer fb){ + mainFbOverride = fb; + } + + public void setFrameBuffer(FrameBuffer fb) { + if (fb == null && mainFbOverride != null) { + fb = mainFbOverride; + } + + if (lastFb == fb) { + if (fb == null || !fb.isUpdateNeeded()) { + return; + } + } + + // generate mipmaps for last FB if needed + if (lastFb != null) { + for (int i = 0; i < lastFb.getNumColorBuffers(); i++) { + RenderBuffer rb = lastFb.getColorBuffer(i); + Texture tex = rb.getTexture(); + if (tex != null + && tex.getMinFilter().usesMipMapLevels()) { + setTexture(0, rb.getTexture()); + +// int textureType = convertTextureType(tex.getType(), tex.getImage().getMultiSamples(), rb.getFace()); + int textureType = convertTextureType(tex.getType()); + GLES20.glGenerateMipmap(textureType); + RendererUtil.checkGLError(); + } + } + } + + if (fb == null) { + // unbind any fbos + if (context.boundFBO != 0) { + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + RendererUtil.checkGLError(); + + statistics.onFrameBufferUse(null, true); + + context.boundFBO = 0; + } + + /* + // select back buffer + if (context.boundDrawBuf != -1) { + glDrawBuffer(initialDrawBuf); + context.boundDrawBuf = -1; + } + if (context.boundReadBuf != -1) { + glReadBuffer(initialReadBuf); + context.boundReadBuf = -1; + } + */ + + lastFb = null; + } else { + if (fb.getNumColorBuffers() == 0 && fb.getDepthBuffer() == null) { + throw new IllegalArgumentException("The framebuffer: " + fb + + "\nDoesn't have any color/depth buffers"); + } + + if (fb.isUpdateNeeded()) { + updateFrameBuffer(fb); + } + + if (context.boundFBO != fb.getId()) { + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fb.getId()); + RendererUtil.checkGLError(); + + 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.getNumColorBuffers() > maxFBOAttachs) { + throw new RendererException("Framebuffer has more color " + + "attachments than are supported" + + " by the video hardware!"); + } + if (fb.isMultiTarget()) { + if (fb.getNumColorBuffers() > maxMRTFBOAttachs) { + throw new RendererException("Framebuffer has more" + + " multi targets than are supported" + + " by the video hardware!"); + } + + if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()) { + intBuf16.clear(); + for (int i = 0; i < fb.getNumColorBuffers(); i++) { + intBuf16.put(GLES20.GL_COLOR_ATTACHMENT0 + 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()) { + GLES20.glActiveTexture(convertAttachmentSlot(rb.getSlot())); + RendererUtil.checkGLError(); + + context.boundDrawBuf = rb.getSlot(); + } + } + } + + assert fb.getId() >= 0; + assert context.boundFBO == fb.getId(); + + lastFb = fb; + + checkFrameBufferStatus(fb); + } + } + + /** + * Reads the Color Buffer from OpenGL and stores into the ByteBuffer. + * Make sure to call setViewPort with the appropriate viewport size before + * calling readFrameBuffer. + * @param fb FrameBuffer + * @param byteBuf ByteBuffer to store the Color Buffer from OpenGL + */ + 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); + } else { + setFrameBuffer(null); + } + + GLES20.glReadPixels(vpX, vpY, vpW, vpH, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, byteBuf); + RendererUtil.checkGLError(); + } + + private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { + intBuf1.put(0, rb.getId()); + GLES20.glDeleteRenderbuffers(1, intBuf1); + RendererUtil.checkGLError(); + } + + public void deleteFrameBuffer(FrameBuffer fb) { + if (fb.getId() != -1) { + if (context.boundFBO == fb.getId()) { + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + RendererUtil.checkGLError(); + + context.boundFBO = 0; + } + + if (fb.getDepthBuffer() != null) { + deleteRenderBuffer(fb, fb.getDepthBuffer()); + } + if (fb.getColorBuffer() != null) { + deleteRenderBuffer(fb, fb.getColorBuffer()); + } + + intBuf1.put(0, fb.getId()); + GLES20.glDeleteFramebuffers(1, intBuf1); + RendererUtil.checkGLError(); + + 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: + case 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); + } + } + + /** + * setupTextureParams sets the OpenGL context texture parameters + * @param tex the Texture to set the texture parameters from + */ + private void setupTextureParams(Texture tex) { + int target = convertTextureType(tex.getType()); + + // filter things + int minFilter = convertMinFilter(tex.getMinFilter()); + int magFilter = convertMagFilter(tex.getMagFilter()); + + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_MIN_FILTER, minFilter); + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_MAG_FILTER, magFilter); + RendererUtil.checkGLError(); + + /* + if (tex.getAnisotropicFilter() > 1){ + + if (GLContext.getCapabilities().GL_EXT_texture_filter_anisotropic){ + glTexParameterf(target, + EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, + tex.getAnisotropicFilter()); + } + + } + */ + // repeat modes + + switch (tex.getType()) { + case ThreeDimensional: + case CubeMap: // cubemaps use 3D coords + // GL_TEXTURE_WRAP_R is not available in api 8 + //GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_R, convertWrapMode(tex.getWrap(WrapAxis.R))); + case TwoDimensional: + case TwoDimensionalArray: + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); + + // fall down here is intentional.. +// case OneDimensional: + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S))); + + RendererUtil.checkGLError(); + 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); + } + } + */ + } + + /** + * activates and binds the texture + * @param img + * @param type + */ + public void updateTexImageData(Image img, Texture.Type type) { + int texId = img.getId(); + if (texId == -1) { + // create texture + GLES20.glGenTextures(1, intBuf1); + RendererUtil.checkGLError(); + + texId = intBuf1.get(0); + img.setId(texId); + objManager.registerObject(img); + + statistics.onNewTexture(); + } + + // bind texture + int target = convertTextureType(type); + if (context.boundTextures[0] != img) { + if (context.boundTextureUnit != 0) { + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + RendererUtil.checkGLError(); + + context.boundTextureUnit = 0; + } + + GLES20.glBindTexture(target, texId); + RendererUtil.checkGLError(); + + context.boundTextures[0] = img; + } + + boolean needMips = false; + if (img.isGeneratedMipmapsRequired()) { + needMips = true; + img.setMipmapsGenerated(true); + } + + if (target == GLES20.GL_TEXTURE_CUBE_MAP) { + // Check max texture size before upload + if (img.getWidth() > maxCubeTexSize || img.getHeight() > maxCubeTexSize) { + throw new RendererException("Cannot upload cubemap " + img + ". The maximum supported cubemap resolution is " + maxCubeTexSize); + } + } else { + if (img.getWidth() > maxTexSize || img.getHeight() > maxTexSize) { + throw new RendererException("Cannot upload texture " + img + ". The maximum supported texture resolution is " + maxTexSize); + } + } + + if (target == GLES20.GL_TEXTURE_CUBE_MAP) { + // Upload a cube map / sky box + @SuppressWarnings("unchecked") + List bmps = (List) img.getEfficentData(); + if (bmps != null) { + // Native android bitmap + if (bmps.size() != 6) { + throw new UnsupportedOperationException("Invalid texture: " + img + + "Cubemap textures must contain 6 data units."); + } + for (int i = 0; i < 6; i++) { + TextureUtil.uploadTextureBitmap(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, bmps.get(i).getBitmap(), needMips); + bmps.get(i).notifyBitmapUploaded(); + } + } else { + // Standard jme3 image data + List data = img.getData(); + if (data.size() != 6) { + throw new UnsupportedOperationException("Invalid texture: " + img + + "Cubemap textures must contain 6 data units."); + } + for (int i = 0; i < 6; i++) { + TextureUtil.uploadTextureAny(img, GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, needMips); + } + } + } else { + TextureUtil.uploadTextureAny(img, target, 0, needMips); + if (img.getEfficentData() instanceof AndroidImageInfo) { + AndroidImageInfo info = (AndroidImageInfo) img.getEfficentData(); + info.notifyBitmapUploaded(); + } + } + + img.clearUpdateNeeded(); + } + + public void setTexture(int unit, Texture tex) { + Image image = tex.getImage(); + if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated()) ) { + updateTexImageData(image, tex.getType()); + } + + 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) { + GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit); + context.boundTextureUnit = unit; + } + + GLES20.glBindTexture(type, texId); + RendererUtil.checkGLError(); + + textures[unit] = image; + + statistics.onTextureUse(tex.getImage(), true); + } else { + statistics.onTextureUse(tex.getImage(), false); + } + + setupTextureParams(tex); + } + + public void modifyTexture(Texture tex, Image pixels, int x, int y) { + setTexture(0, tex); + TextureUtil.uploadSubTexture(pixels, convertTextureType(tex.getType()), 0, x, y); + } + + 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); + + GLES20.glDeleteTextures(1, intBuf1); + RendererUtil.checkGLError(); + + 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 convertVertexBufferFormat(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) { + int bufId = vb.getId(); + boolean created = false; + if (bufId == -1) { + // create buffer + GLES20.glGenBuffers(1, intBuf1); + RendererUtil.checkGLError(); + + bufId = intBuf1.get(0); + vb.setId(bufId); + objManager.registerObject(vb); + + created = true; + } + + // bind buffer + int target; + if (vb.getBufferType() == VertexBuffer.Type.Index) { + target = GLES20.GL_ELEMENT_ARRAY_BUFFER; + if (context.boundElementArrayVBO != bufId) { + GLES20.glBindBuffer(target, bufId); + RendererUtil.checkGLError(); + + context.boundElementArrayVBO = bufId; + } + } else { + target = GLES20.GL_ARRAY_BUFFER; + if (context.boundArrayVBO != bufId) { + GLES20.glBindBuffer(target, bufId); + RendererUtil.checkGLError(); + + context.boundArrayVBO = bufId; + } + } + + int usage = convertUsage(vb.getUsage()); + vb.getData().rewind(); + + if (created || vb.hasDataSizeChanged()) { + // upload data based on format + int size = vb.getData().limit() * vb.getFormat().getComponentSize(); + + switch (vb.getFormat()) { + case Byte: + case UnsignedByte: + GLES20.glBufferData(target, size, (ByteBuffer) vb.getData(), usage); + RendererUtil.checkGLError(); + break; + case Short: + case UnsignedShort: + GLES20.glBufferData(target, size, (ShortBuffer) vb.getData(), usage); + RendererUtil.checkGLError(); + break; + case Int: + case UnsignedInt: + GLES20.glBufferData(target, size, (IntBuffer) vb.getData(), usage); + RendererUtil.checkGLError(); + break; + case Float: + GLES20.glBufferData(target, size, (FloatBuffer) vb.getData(), usage); + RendererUtil.checkGLError(); + break; + default: + throw new RuntimeException("Unknown buffer format."); + } + } else { + int size = vb.getData().limit() * vb.getFormat().getComponentSize(); + + switch (vb.getFormat()) { + case Byte: + case UnsignedByte: + GLES20.glBufferSubData(target, 0, size, (ByteBuffer) vb.getData()); + RendererUtil.checkGLError(); + break; + case Short: + case UnsignedShort: + GLES20.glBufferSubData(target, 0, size, (ShortBuffer) vb.getData()); + RendererUtil.checkGLError(); + break; + case Int: + case UnsignedInt: + GLES20.glBufferSubData(target, 0, size, (IntBuffer) vb.getData()); + RendererUtil.checkGLError(); + break; + case Float: + GLES20.glBufferSubData(target, 0, size, (FloatBuffer) vb.getData()); + RendererUtil.checkGLError(); + break; + default: + throw new RuntimeException("Unknown buffer format."); + } + } + 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); + + GLES20.glDeleteBuffers(1, intBuf1); + RendererUtil.checkGLError(); + + vb.resetObject(); + } + } + + public void clearVertexAttribs() { + IDList attribList = context.attribIndexList; + for (int i = 0; i < attribList.oldLen; i++) { + int idx = attribList.oldList[i]; + + GLES20.glDisableVertexAttribArray(idx); + RendererUtil.checkGLError(); + + 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()); + 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(); + + String attributeName = "in" + vb.getBufferType().name(); + loc = GLES20.glGetAttribLocation(programId, attributeName); + RendererUtil.checkGLError(); + + // 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)) { + GLES20.glEnableVertexAttribArray(loc); + RendererUtil.checkGLError(); + //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) { + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufId); + RendererUtil.checkGLError(); + + context.boundArrayVBO = bufId; + } + + vb.getData().rewind(); + + Android22Workaround.glVertexAttribPointer(loc, + vb.getNumComponents(), + convertVertexBufferFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + 0); + + RendererUtil.checkGLError(); + + 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{*/ + GLES20.glDrawArrays(convertElementMode(mode), 0, vertCount); + RendererUtil.checkGLError(); + /* + }*/ + } + + 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 (bufId == -1) { + throw new RendererException("Invalid buffer ID"); + } + + if (context.boundElementArrayVBO != bufId) { + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufId); + RendererUtil.checkGLError(); + + context.boundElementArrayVBO = bufId; + } + + int vertCount = mesh.getVertexCount(); + boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); + + Buffer indexData = indexBuf.getData(); + + if (indexBuf.getFormat() == Format.UnsignedInt) { + throw new RendererException("OpenGL ES does not support 32-bit index buffers." + + "Split your models to avoid going over 65536 vertices."); + } + + if (mesh.getMode() == Mode.Hybrid) { + int[] modeStart = mesh.getModeStart(); + int[] elementLengths = mesh.getElementLengths(); + + int elMode = convertElementMode(Mode.Triangles); + int fmt = convertVertexBufferFormat(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); + GLES20.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); + RendererUtil.checkGLError(); + /* + 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().limit(), + convertVertexBufferFormat(indexBuf.getFormat()), + 0, + count); + */ + } else { + indexData.rewind(); + GLES20.glDrawElements( + convertElementMode(mesh.getMode()), + indexBuf.getData().limit(), + convertVertexBufferFormat(indexBuf.getFormat()), + 0); + RendererUtil.checkGLError(); + } + } + } + + /*********************************************************************\ + |* Render Calls *| + \*********************************************************************/ + public int convertElementMode(Mesh.Mode mode) { + switch (mode) { + case Points: + return GLES20.GL_POINTS; + case Lines: + return GLES20.GL_LINES; + case LineLoop: + return GLES20.GL_LINE_LOOP; + case LineStrip: + return GLES20.GL_LINE_STRIP; + case Triangles: + return GLES20.GL_TRIANGLES; + case TriangleFan: + return GLES20.GL_TRIANGLE_FAN; + case TriangleStrip: + return GLES20.GL_TRIANGLE_STRIP; + default: + throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode); + } + } + + public void updateVertexArray(Mesh mesh) { + logger.log(Level.FINE, "updateVertexArray({0})", mesh); + int id = mesh.getId(); + /* + if (id == -1){ + IntBuffer temp = intBuf1; + // ARBVertexArrayObject.glGenVertexArrays(temp); + GLES20.glGenVertexArrays(temp); + id = temp.get(0); + mesh.setId(id); + } + + if (context.boundVertexArray != id){ + // ARBVertexArrayObject.glBindVertexArray(id); + GLES20.glBindVertexArray(id); + context.boundVertexArray = id; + } + */ + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()) { + updateBufferData(interleavedData); + } + + + for (VertexBuffer vb : mesh.getBufferList().getArray()){ + + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } + + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + } + + /** + * renderMeshVertexArray renders a mesh using vertex arrays + */ + private void renderMeshVertexArray(Mesh mesh, int lod, int count) { + for (VertexBuffer vb : mesh.getBufferList().getArray()) { + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } + + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib_Array(vb); + } else { + // interleaved + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + setVertexAttrib_Array(vb, interleavedData); + } + } + + VertexBuffer indices = null; + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = mesh.getBuffer(Type.Index);//buffers.get(Type.Index.ordinal()); + } + if (indices != null) { + drawTriangleList_Array(indices, mesh, count); + } else { + GLES20.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + RendererUtil.checkGLError(); + } + 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 = mesh.getBuffer(Type.Index);// buffers.get(Type.Index.ordinal()); + } + for (VertexBuffer vb : mesh.getBufferList().getArray()){ + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } + + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + if (indices != null) { + drawTriangleList(indices, mesh, count); + } else { +// throw new UnsupportedOperationException("Cannot render without index buffer"); + GLES20.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + RendererUtil.checkGLError(); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + public void renderMesh(Mesh mesh, int lod, int count) { + if (mesh.getVertexCount() == 0) { + return; + } + + /* + * NOTE: not supported in OpenGL ES 2.0. + if (context.pointSize != mesh.getPointSize()) { + GLES10.glPointSize(mesh.getPointSize()); + context.pointSize = mesh.getPointSize(); + } + */ + if (context.lineWidth != mesh.getLineWidth()) { + GLES20.glLineWidth(mesh.getLineWidth()); + RendererUtil.checkGLError(); + context.lineWidth = mesh.getLineWidth(); + } + + statistics.onMeshDrawn(mesh, lod); +// if (GLContext.getCapabilities().GL_ARB_vertex_array_object){ +// renderMeshVertexArray(mesh, lod, count); +// }else{ + + if (useVBO) { + renderMeshDefault(mesh, lod, count); + } else { + renderMeshVertexArray(mesh, lod, count); + } + } + + /** + * drawTriangleList_Array uses Vertex Array + * @param indexBuf + * @param mesh + * @param count + */ + public void drawTriangleList_Array(VertexBuffer indexBuf, Mesh mesh, int count) { + if (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.rewind(); + + if (mesh.getMode() == Mode.Hybrid) { + int[] modeStart = mesh.getModeStart(); + int[] elementLengths = mesh.getElementLengths(); + + int elMode = convertElementMode(Mode.Triangles); + int fmt = convertVertexBufferFormat(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.TriangleFan); + } + int elementLength = elementLengths[i]; + + indexBuf.getData().position(curOffset); + GLES20.glDrawElements(elMode, elementLength, fmt, indexBuf.getData()); + RendererUtil.checkGLError(); + + curOffset += elementLength * elSize; + } + } else { + GLES20.glDrawElements( + convertElementMode(mesh.getMode()), + indexBuf.getData().limit(), + convertVertexBufferFormat(indexBuf.getFormat()), + indexBuf.getData()); + RendererUtil.checkGLError(); + } + } + + /** + * setVertexAttrib_Array uses Vertex Array + * @param vb + * @param idb + */ + public void setVertexAttrib_Array(VertexBuffer vb, VertexBuffer idb) { + if (vb.getBufferType() == VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); + } + + // Get shader + int programId = context.boundShaderProgram; + if (programId > 0) { + VertexBuffer[] attribs = context.boundAttribs; + + Attribute attrib = boundShader.getAttribute(vb.getBufferType()); + int loc = attrib.getLocation(); + if (loc == -1) { + //throw new IllegalArgumentException("Location is invalid for attrib: [" + vb.getBufferType().name() + "]"); + return; + } else if (loc == -2) { + String attributeName = "in" + vb.getBufferType().name(); + + loc = GLES20.glGetAttribLocation(programId, attributeName); + RendererUtil.checkGLError(); + + if (loc < 0) { + attrib.setLocation(-1); + 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().rewind(); + avb.getData().position(vb.getOffset()); + + // Upload attribute data + GLES20.glVertexAttribPointer(loc, + vb.getNumComponents(), + convertVertexBufferFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + avb.getData()); + + RendererUtil.checkGLError(); + + GLES20.glEnableVertexAttribArray(loc); + RendererUtil.checkGLError(); + + attribs[loc] = vb; + } // if (attribs[loc] != vb) + } else { + throw new IllegalStateException("Cannot render mesh without shader bound"); + } + } + + /** + * setVertexAttrib_Array uses Vertex Array + * @param vb + */ + public void setVertexAttrib_Array(VertexBuffer vb) { + setVertexAttrib_Array(vb, null); + } + + public void setAlphaToCoverage(boolean value) { + if (value) { + GLES20.glEnable(GLES20.GL_SAMPLE_ALPHA_TO_COVERAGE); + RendererUtil.checkGLError(); + } else { + GLES20.glDisable(GLES20.GL_SAMPLE_ALPHA_TO_COVERAGE); + RendererUtil.checkGLError(); + } + } + + @Override + public void invalidateState() { + context.reset(); + boundShader = null; + lastFb = null; + } +} diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/RendererUtil.java b/jme3-android/src/main/java/com/jme3/renderer/android/RendererUtil.java new file mode 100644 index 000000000..ce611044d --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/renderer/android/RendererUtil.java @@ -0,0 +1,129 @@ +package com.jme3.renderer.android; + +import android.opengl.GLES20; +import android.opengl.GLU; +import com.jme3.renderer.RendererException; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGL11; + +/** + * Utility class used by the {@link OGLESShaderRenderer renderer} and sister + * classes. + * + * @author Kirill Vainer + */ +public class RendererUtil { + + /** + * When set to true, every OpenGL call will check for errors and throw an + * exception if there is one, if false, no error checking is performed. + */ + public static boolean ENABLE_ERROR_CHECKING = true; + + /** + * Checks for an OpenGL error and throws a {@link RendererException} if + * there is one. Ignores the value of + * {@link RendererUtil#ENABLE_ERROR_CHECKING}. + */ + public static void checkGLErrorForced() { + int error = GLES20.glGetError(); + if (error != 0) { + String message = GLU.gluErrorString(error); + if (message == null) { + throw new RendererException("An unknown OpenGL error has occurred."); + } else { + throw new RendererException("An OpenGL error has occurred: " + message); + } + } + } + + /** + * Checks for an EGL error and throws a {@link RendererException} if there + * is one. Ignores the value of {@link RendererUtil#ENABLE_ERROR_CHECKING}. + */ + public static void checkEGLError(EGL10 egl) { + int error = egl.eglGetError(); + if (error != EGL10.EGL_SUCCESS) { + String errorMessage; + switch (error) { + case EGL10.EGL_SUCCESS: + return; + case EGL10.EGL_NOT_INITIALIZED: + errorMessage = "EGL is not initialized, or could not be " + + "initialized, for the specified EGL display connection. "; + break; + case EGL10.EGL_BAD_ACCESS: + errorMessage = "EGL cannot access a requested resource " + + "(for example a context is bound in another thread). "; + break; + case EGL10.EGL_BAD_ALLOC: + errorMessage = "EGL failed to allocate resources for the requested operation."; + break; + case EGL10.EGL_BAD_ATTRIBUTE: + errorMessage = "An unrecognized attribute or attribute " + + "value was passed in the attribute list. "; + break; + case EGL10.EGL_BAD_CONTEXT: + errorMessage = "An EGLContext argument does not name a valid EGL rendering context. "; + break; + case EGL10.EGL_BAD_CONFIG: + errorMessage = "An EGLConfig argument does not name a valid EGL frame buffer configuration. "; + break; + case EGL10.EGL_BAD_CURRENT_SURFACE: + errorMessage = "The current surface of the calling thread " + + "is a window, pixel buffer or pixmap that is no longer valid. "; + break; + case EGL10.EGL_BAD_DISPLAY: + errorMessage = "An EGLDisplay argument does not name a valid EGL display connection. "; + break; + case EGL10.EGL_BAD_SURFACE: + errorMessage = "An EGLSurface argument does not name a " + + "valid surface (window, pixel buffer or pixmap) configured for GL rendering. "; + break; + case EGL10.EGL_BAD_MATCH: + errorMessage = "Arguments are inconsistent (for example, a " + + "valid context requires buffers not supplied by a valid surface). "; + break; + case EGL10.EGL_BAD_PARAMETER: + errorMessage = "One or more argument values are invalid."; + break; + case EGL10.EGL_BAD_NATIVE_PIXMAP: + errorMessage = "A NativePixmapType argument does not refer to a valid native pixmap. "; + break; + case EGL10.EGL_BAD_NATIVE_WINDOW: + errorMessage = "A NativeWindowType argument does not refer to a valid native window. "; + break; + case EGL11.EGL_CONTEXT_LOST: + errorMessage = "A power management event has occurred. " + + "The application must destroy all contexts and reinitialise " + + "OpenGL ES state and objects to continue rendering. "; + break; + default: + errorMessage = "Unknown"; + } + + throw new RendererException("EGL error 0x" + Integer.toHexString(error) + ": " + errorMessage); + } + } + + /** + * Checks for an OpenGL error and throws a {@link RendererException} if + * there is one. Does nothing if {@link RendererUtil#ENABLE_ERROR_CHECKING} + * is set to + * false. + */ + public static void checkGLError() { + if (!ENABLE_ERROR_CHECKING) { + return; + } + int error = GLES20.glGetError(); + if (error != 0) { + String message = GLU.gluErrorString(error); + if (message == null) { + throw new RendererException("An unknown OpenGL error has occurred."); + } else { + throw new RendererException("An OpenGL error has occurred: " + message); + } + } + } +} diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/TextureUtil.java b/jme3-android/src/main/java/com/jme3/renderer/android/TextureUtil.java new file mode 100644 index 000000000..aa55f30a6 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/renderer/android/TextureUtil.java @@ -0,0 +1,571 @@ +package com.jme3.renderer.android; + +import android.graphics.Bitmap; +import android.opengl.ETC1; +import android.opengl.ETC1Util.ETC1Texture; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import com.jme3.asset.AndroidImageInfo; +import com.jme3.math.FastMath; +import com.jme3.renderer.RendererException; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TextureUtil { + + private static final Logger logger = Logger.getLogger(TextureUtil.class.getName()); + //TODO Make this configurable through appSettings + public static boolean ENABLE_COMPRESSION = true; + private static boolean NPOT = false; + private static boolean ETC1support = false; + private static boolean DXT1 = false; + private static boolean DEPTH24_STENCIL8 = false; + private static boolean DEPTH_TEXTURE = false; + private static boolean RGBA8 = false; + + // Same constant used by both GL_ARM_rgba8 and GL_OES_rgb8_rgba8. + private static final int GL_RGBA8 = 0x8058; + + private static final int GL_DXT1 = 0x83F0; + private static final int GL_DXT1A = 0x83F1; + + private static final int GL_DEPTH_STENCIL_OES = 0x84F9; + private static final int GL_UNSIGNED_INT_24_8_OES = 0x84FA; + private static final int GL_DEPTH24_STENCIL8_OES = 0x88F0; + + public static void loadTextureFeatures(String extensionString) { + ETC1support = extensionString.contains("GL_OES_compressed_ETC1_RGB8_texture"); + DEPTH24_STENCIL8 = extensionString.contains("GL_OES_packed_depth_stencil"); + NPOT = extensionString.contains("GL_IMG_texture_npot") + || extensionString.contains("GL_OES_texture_npot") + || extensionString.contains("GL_NV_texture_npot_2D_mipmap"); + + DXT1 = extensionString.contains("GL_EXT_texture_compression_dxt1"); + DEPTH_TEXTURE = extensionString.contains("GL_OES_depth_texture"); + + RGBA8 = extensionString.contains("GL_ARM_rgba8") || + extensionString.contains("GL_OES_rgb8_rgba8"); + + logger.log(Level.FINE, "Supports ETC1? {0}", ETC1support); + logger.log(Level.FINE, "Supports DEPTH24_STENCIL8? {0}", DEPTH24_STENCIL8); + logger.log(Level.FINE, "Supports NPOT? {0}", NPOT); + logger.log(Level.FINE, "Supports DXT1? {0}", DXT1); + logger.log(Level.FINE, "Supports DEPTH_TEXTURE? {0}", DEPTH_TEXTURE); + logger.log(Level.FINE, "Supports RGBA8? {0}", RGBA8); + } + + private static void buildMipmap(Bitmap bitmap, boolean compress) { + int level = 0; + int height = bitmap.getHeight(); + int width = bitmap.getWidth(); + + logger.log(Level.FINEST, " - Generating mipmaps for bitmap using SOFTWARE"); + + GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); + + while (height >= 1 || width >= 1) { + //First of all, generate the texture from our bitmap and set it to the according level + if (compress) { + logger.log(Level.FINEST, " - Uploading LOD level {0} ({1}x{2}) with compression.", new Object[]{level, width, height}); + uploadBitmapAsCompressed(GLES20.GL_TEXTURE_2D, level, bitmap, false, 0, 0); + } else { + logger.log(Level.FINEST, " - Uploading LOD level {0} ({1}x{2}) directly.", new Object[]{level, width, height}); + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, level, bitmap, 0); + } + + if (height == 1 || width == 1) { + break; + } + + //Increase the mipmap level + height /= 2; + width /= 2; + Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true); + + // Recycle any bitmaps created as a result of scaling the bitmap. + // Do not recycle the original image (mipmap level 0) + if (level != 0) { + bitmap.recycle(); + } + + bitmap = bitmap2; + + level++; + } + } + + private static void uploadBitmapAsCompressed(int target, int level, Bitmap bitmap, boolean subTexture, int x, int y) { + if (bitmap.hasAlpha()) { + logger.log(Level.FINEST, " - Uploading bitmap directly. Cannot compress as alpha present."); + if (subTexture) { + GLUtils.texSubImage2D(target, level, x, y, bitmap); + RendererUtil.checkGLError(); + } else { + GLUtils.texImage2D(target, level, bitmap, 0); + RendererUtil.checkGLError(); + } + } else { + // Convert to RGB565 + int bytesPerPixel = 2; + Bitmap rgb565 = bitmap.copy(Bitmap.Config.RGB_565, true); + + // Put texture data into ByteBuffer + ByteBuffer inputImage = BufferUtils.createByteBuffer(bitmap.getRowBytes() * bitmap.getHeight()); + rgb565.copyPixelsToBuffer(inputImage); + inputImage.position(0); + + // Delete the copied RGB565 image + rgb565.recycle(); + + // Encode the image into the output bytebuffer + int encodedImageSize = ETC1.getEncodedDataSize(bitmap.getWidth(), bitmap.getHeight()); + ByteBuffer compressedImage = BufferUtils.createByteBuffer(encodedImageSize); + ETC1.encodeImage(inputImage, bitmap.getWidth(), + bitmap.getHeight(), + bytesPerPixel, + bytesPerPixel * bitmap.getWidth(), + compressedImage); + + // Delete the input image buffer + BufferUtils.destroyDirectBuffer(inputImage); + + // Create an ETC1Texture from the compressed image data + ETC1Texture etc1tex = new ETC1Texture(bitmap.getWidth(), bitmap.getHeight(), compressedImage); + + // Upload the ETC1Texture + if (bytesPerPixel == 2) { + int oldSize = (bitmap.getRowBytes() * bitmap.getHeight()); + int newSize = compressedImage.capacity(); + logger.log(Level.FINEST, " - Uploading compressed image to GL, oldSize = {0}, newSize = {1}, ratio = {2}", new Object[]{oldSize, newSize, (float) oldSize / newSize}); + if (subTexture) { + GLES20.glCompressedTexSubImage2D(target, + level, + x, y, + bitmap.getWidth(), + bitmap.getHeight(), + ETC1.ETC1_RGB8_OES, + etc1tex.getData().capacity(), + etc1tex.getData()); + + RendererUtil.checkGLError(); + } else { + GLES20.glCompressedTexImage2D(target, + level, + ETC1.ETC1_RGB8_OES, + bitmap.getWidth(), + bitmap.getHeight(), + 0, + etc1tex.getData().capacity(), + etc1tex.getData()); + + RendererUtil.checkGLError(); + } + +// ETC1Util.loadTexture(target, level, 0, GLES20.GL_RGB, +// GLES20.GL_UNSIGNED_SHORT_5_6_5, etc1Texture); +// } else if (bytesPerPixel == 3) { +// ETC1Util.loadTexture(target, level, 0, GLES20.GL_RGB, +// GLES20.GL_UNSIGNED_BYTE, etc1Texture); + } + + BufferUtils.destroyDirectBuffer(compressedImage); + } + } + + /** + * uploadTextureBitmap uploads a native android bitmap + */ + public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips) { + uploadTextureBitmap(target, bitmap, needMips, false, 0, 0); + } + + /** + * uploadTextureBitmap uploads a native android bitmap + */ + public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips, boolean subTexture, int x, int y) { + boolean recycleBitmap = false; + //TODO, maybe this should raise an exception when NPOT is not supported + + boolean willCompress = ENABLE_COMPRESSION && ETC1support && !bitmap.hasAlpha(); + if (needMips && willCompress) { + // Image is compressed and mipmaps are desired, generate them + // using software. + buildMipmap(bitmap, willCompress); + } else { + if (willCompress) { + // Image is compressed but mipmaps are not desired, upload directly. + logger.log(Level.FINEST, " - Uploading compressed bitmap. Mipmaps are not generated."); + uploadBitmapAsCompressed(target, 0, bitmap, subTexture, x, y); + + } else { + // Image is not compressed, mipmaps may or may not be desired. + logger.log(Level.FINEST, " - Uploading bitmap directly.{0}", + (needMips + ? " Mipmaps will be generated in HARDWARE" + : " Mipmaps are not generated.")); + if (subTexture) { + System.err.println("x : " + x + " y :" + y + " , " + bitmap.getWidth() + "/" + bitmap.getHeight()); + GLUtils.texSubImage2D(target, 0, x, y, bitmap); + RendererUtil.checkGLError(); + } else { + GLUtils.texImage2D(target, 0, bitmap, 0); + RendererUtil.checkGLError(); + } + + if (needMips) { + // No pregenerated mips available, + // generate from base level if required + GLES20.glGenerateMipmap(target); + RendererUtil.checkGLError(); + } + } + } + + if (recycleBitmap) { + bitmap.recycle(); + } + } + + public static void uploadTextureAny(Image img, int target, int index, boolean needMips) { + if (img.getEfficentData() instanceof AndroidImageInfo) { + logger.log(Level.FINEST, " === Uploading image {0}. Using BITMAP PATH === ", img); + // If image was loaded from asset manager, use fast path + AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData(); + uploadTextureBitmap(target, imageInfo.getBitmap(), needMips); + } else { + logger.log(Level.FINEST, " === Uploading image {0}. Using BUFFER PATH === ", img); + boolean wantGeneratedMips = needMips && !img.hasMipmaps(); + if (wantGeneratedMips && img.getFormat().isCompressed()) { + logger.log(Level.WARNING, "Generating mipmaps is only" + + " supported for Bitmap based or non-compressed images!"); + } + + // Upload using slower path + logger.log(Level.FINEST, " - Uploading bitmap directly.{0}", + (wantGeneratedMips + ? " Mipmaps will be generated in HARDWARE" + : " Mipmaps are not generated.")); + + uploadTexture(img, target, index); + + // Image was uploaded using slower path, since it is not compressed, + // then compress it + if (wantGeneratedMips) { + // No pregenerated mips available, + // generate from base level if required + GLES20.glGenerateMipmap(target); + } + } + } + + private static void unsupportedFormat(Format fmt) { + throw new UnsupportedOperationException("The image format '" + fmt + "' is unsupported by the video hardware."); + } + + public static AndroidGLImageFormat getImageFormat(Format fmt) throws UnsupportedOperationException { + AndroidGLImageFormat imageFormat = new AndroidGLImageFormat(); + switch (fmt) { + case RGBA16: + case RGB16: + case RGB10: + case Luminance16: + case Luminance16Alpha16: + case Alpha16: + case Depth32: + case Depth32F: + throw new UnsupportedOperationException("The image format '" + + fmt + "' is not supported by OpenGL ES 2.0 specification."); + case Alpha8: + imageFormat.format = GLES20.GL_ALPHA; + imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE; + if (RGBA8) { + imageFormat.renderBufferStorageFormat = GL_RGBA8; + } else { + // Highest precision alpha supported by vanilla OGLES2 + imageFormat.renderBufferStorageFormat = GLES20.GL_RGBA4; + } + break; + case Luminance8: + imageFormat.format = GLES20.GL_LUMINANCE; + imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE; + if (RGBA8) { + imageFormat.renderBufferStorageFormat = GL_RGBA8; + } else { + // Highest precision luminance supported by vanilla OGLES2 + imageFormat.renderBufferStorageFormat = GLES20.GL_RGB565; + } + break; + case Luminance8Alpha8: + imageFormat.format = GLES20.GL_LUMINANCE_ALPHA; + imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE; + if (RGBA8) { + imageFormat.renderBufferStorageFormat = GL_RGBA8; + } else { + imageFormat.renderBufferStorageFormat = GLES20.GL_RGBA4; + } + break; + case RGB565: + imageFormat.format = GLES20.GL_RGB; + imageFormat.dataType = GLES20.GL_UNSIGNED_SHORT_5_6_5; + imageFormat.renderBufferStorageFormat = GLES20.GL_RGB565; + break; + case ARGB4444: + imageFormat.format = GLES20.GL_RGBA4; + imageFormat.dataType = GLES20.GL_UNSIGNED_SHORT_4_4_4_4; + imageFormat.renderBufferStorageFormat = GLES20.GL_RGBA4; + break; + case RGB5A1: + imageFormat.format = GLES20.GL_RGBA; + imageFormat.dataType = GLES20.GL_UNSIGNED_SHORT_5_5_5_1; + imageFormat.renderBufferStorageFormat = GLES20.GL_RGB5_A1; + break; + case RGB8: + imageFormat.format = GLES20.GL_RGB; + imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE; + if (RGBA8) { + imageFormat.renderBufferStorageFormat = GL_RGBA8; + } else { + // Fallback: Use RGB565 if RGBA8 is not available. + imageFormat.renderBufferStorageFormat = GLES20.GL_RGB565; + } + break; + case BGR8: + imageFormat.format = GLES20.GL_RGB; + imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE; + if (RGBA8) { + imageFormat.renderBufferStorageFormat = GL_RGBA8; + } else { + imageFormat.renderBufferStorageFormat = GLES20.GL_RGB565; + } + break; + case RGBA8: + imageFormat.format = GLES20.GL_RGBA; + imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE; + if (RGBA8) { + imageFormat.renderBufferStorageFormat = GL_RGBA8; + } else { + imageFormat.renderBufferStorageFormat = GLES20.GL_RGBA4; + } + break; + case Depth: + case Depth16: + if (!DEPTH_TEXTURE) { + unsupportedFormat(fmt); + } + imageFormat.format = GLES20.GL_DEPTH_COMPONENT; + imageFormat.dataType = GLES20.GL_UNSIGNED_SHORT; + imageFormat.renderBufferStorageFormat = GLES20.GL_DEPTH_COMPONENT16; + break; + case Depth24: + case Depth24Stencil8: + if (!DEPTH_TEXTURE) { + unsupportedFormat(fmt); + } + if (DEPTH24_STENCIL8) { + // NEW: True Depth24 + Stencil8 format. + imageFormat.format = GL_DEPTH_STENCIL_OES; + imageFormat.dataType = GL_UNSIGNED_INT_24_8_OES; + imageFormat.renderBufferStorageFormat = GL_DEPTH24_STENCIL8_OES; + } else { + // Vanilla OGLES2, only Depth16 available. + imageFormat.format = GLES20.GL_DEPTH_COMPONENT; + imageFormat.dataType = GLES20.GL_UNSIGNED_SHORT; + imageFormat.renderBufferStorageFormat = GLES20.GL_DEPTH_COMPONENT16; + } + break; + case DXT1: + if (!DXT1) { + unsupportedFormat(fmt); + } + imageFormat.format = GL_DXT1; + imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE; + imageFormat.compress = true; + break; + case DXT1A: + if (!DXT1) { + unsupportedFormat(fmt); + } + imageFormat.format = GL_DXT1A; + imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE; + imageFormat.compress = true; + break; + default: + throw new UnsupportedOperationException("Unrecognized format: " + fmt); + } + return imageFormat; + } + + public static class AndroidGLImageFormat { + + boolean compress = false; + int format = -1; + int renderBufferStorageFormat = -1; + int dataType = -1; + } + + private static void uploadTexture(Image img, + int target, + int index) { + + if (img.getEfficentData() instanceof AndroidImageInfo) { + throw new RendererException("This image uses efficient data. " + + "Use uploadTextureBitmap instead."); + } + + // Otherwise upload image directly. + // Prefer to only use power of 2 textures here to avoid errors. + Image.Format fmt = img.getFormat(); + ByteBuffer data; + if (index >= 0 || img.getData() != null && img.getData().size() > 0) { + data = img.getData(index); + } else { + data = null; + } + + int width = img.getWidth(); + int height = img.getHeight(); + + if (!NPOT) { + // Check if texture is POT + if (!FastMath.isPowerOfTwo(width) || !FastMath.isPowerOfTwo(height)) { + throw new RendererException("Non-power-of-2 textures " + + "are not supported by the video hardware " + + "and no scaling path available for image: " + img); + } + } + AndroidGLImageFormat imageFormat = getImageFormat(fmt); + + if (data != null) { + GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); + } + + int[] mipSizes = img.getMipMapSizes(); + int pos = 0; + if (mipSizes == null) { + if (data != null) { + mipSizes = new int[]{data.capacity()}; + } else { + mipSizes = new int[]{width * height * fmt.getBitsPerPixel() / 8}; + } + } + + for (int i = 0; i < mipSizes.length; i++) { + int mipWidth = Math.max(1, width >> i); + int mipHeight = Math.max(1, height >> i); + + if (data != null) { + data.position(pos); + data.limit(pos + mipSizes[i]); + } + + if (imageFormat.compress && data != null) { + GLES20.glCompressedTexImage2D(target, + i, + imageFormat.format, + mipWidth, + mipHeight, + 0, + data.remaining(), + data); + } else { + GLES20.glTexImage2D(target, + i, + imageFormat.format, + mipWidth, + mipHeight, + 0, + imageFormat.format, + imageFormat.dataType, + data); + } + + pos += mipSizes[i]; + } + } + + /** + * Update the texture currently bound to target at with data from the given + * Image at position x and y. The parameter index is used as the zoffset in + * case a 3d texture or texture 2d array is being updated. + * + * @param image Image with the source data (this data will be put into the + * texture) + * @param target the target texture + * @param index the mipmap level to update + * @param x the x position where to put the image in the texture + * @param y the y position where to put the image in the texture + */ + public static void uploadSubTexture( + Image img, + int target, + int index, + int x, + int y) { + if (img.getEfficentData() instanceof AndroidImageInfo) { + AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData(); + uploadTextureBitmap(target, imageInfo.getBitmap(), true, true, x, y); + return; + } + + // Otherwise upload image directly. + // Prefer to only use power of 2 textures here to avoid errors. + Image.Format fmt = img.getFormat(); + ByteBuffer data; + if (index >= 0 || img.getData() != null && img.getData().size() > 0) { + data = img.getData(index); + } else { + data = null; + } + + int width = img.getWidth(); + int height = img.getHeight(); + + if (!NPOT) { + // Check if texture is POT + if (!FastMath.isPowerOfTwo(width) || !FastMath.isPowerOfTwo(height)) { + throw new RendererException("Non-power-of-2 textures " + + "are not supported by the video hardware " + + "and no scaling path available for image: " + img); + } + } + AndroidGLImageFormat imageFormat = getImageFormat(fmt); + + if (data != null) { + GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); + } + + int[] mipSizes = img.getMipMapSizes(); + int pos = 0; + if (mipSizes == null) { + if (data != null) { + mipSizes = new int[]{data.capacity()}; + } else { + mipSizes = new int[]{width * height * fmt.getBitsPerPixel() / 8}; + } + } + + for (int i = 0; i < mipSizes.length; i++) { + int mipWidth = Math.max(1, width >> i); + int mipHeight = Math.max(1, height >> i); + + if (data != null) { + data.position(pos); + data.limit(pos + mipSizes[i]); + } + + if (imageFormat.compress && data != null) { + GLES20.glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, data.remaining(), data); + RendererUtil.checkGLError(); + } else { + GLES20.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, imageFormat.dataType, data); + RendererUtil.checkGLError(); + } + + pos += mipSizes[i]; + } + } +} diff --git a/jme3-android/src/main/java/com/jme3/system/android/AndroidConfigChooser.java b/jme3-android/src/main/java/com/jme3/system/android/AndroidConfigChooser.java new file mode 100644 index 000000000..0b143f479 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/system/android/AndroidConfigChooser.java @@ -0,0 +1,518 @@ +package com.jme3.system.android; + +import android.opengl.GLSurfaceView.EGLConfigChooser; +import com.jme3.renderer.android.RendererUtil; +import com.jme3.system.AppSettings; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; + +/** + * AndroidConfigChooser is used to determine the best suited EGL Config + * + * @author iwgeric + */ +public class AndroidConfigChooser implements EGLConfigChooser { + + private static final Logger logger = Logger.getLogger(AndroidConfigChooser.class.getName()); + protected AppSettings settings; + private final static int EGL_OPENGL_ES2_BIT = 4; + + + @Deprecated + public enum ConfigType { + + /** + * RGB565, 0 alpha, 16 depth, 0 stencil + */ + FASTEST(5, 6, 5, 0, 16, 0, 5, 6, 5, 0, 16, 0), + /** + * min RGB888, 0 alpha, 16 depth, 0 stencil max RGB888, 0 alpha, 32 + * depth, 8 stencil + */ + BEST(8, 8, 8, 0, 32, 8, 8, 8, 8, 0, 16, 0), + /** + * Turn off config chooser and use hardcoded + * setEGLContextClientVersion(2); setEGLConfigChooser(5, 6, 5, 0, 16, + * 0); + */ + LEGACY(5, 6, 5, 0, 16, 0, 5, 6, 5, 0, 16, 0), + /** + * min RGB888, 8 alpha, 16 depth, 0 stencil max RGB888, 8 alpha, 32 + * depth, 8 stencil + */ + BEST_TRANSLUCENT(8, 8, 8, 8, 32, 8, 8, 8, 8, 8, 16, 0); + /** + * red, green, blue, alpha, depth, stencil (max values) + */ + int r, g, b, a, d, s; + /** + * minimal values + */ + int mr, mg, mb, ma, md, ms; + + private ConfigType(int r, int g, int b, int a, int d, int s, int mr, int mg, int mb, int ma, int md, int ms) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + this.d = d; + this.s = s; + this.mr = mr; + this.mg = mg; + this.mb = mb; + this.ma = ma; + this.md = md; + this.ms = ms; + } + } + + public AndroidConfigChooser(AppSettings settings) { + this.settings = settings; + } + + /** + * Gets called by the GLSurfaceView class to return the best config + */ + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + logger.fine("GLSurfaceView asking for egl config"); + Config requestedConfig = getRequestedConfig(); + EGLConfig[] configs = getConfigs(egl, display); + + // First try to find an exact match, but allowing a higher stencil + EGLConfig choosenConfig = chooseConfig(egl, display, configs, requestedConfig, false, false, false, true); + if (choosenConfig == null && requestedConfig.d > 16) { + logger.log(Level.INFO, "EGL configuration not found, reducing depth"); + requestedConfig.d = 16; + choosenConfig = chooseConfig(egl, display, configs, requestedConfig, false, false, false, true); + } + + if (choosenConfig == null) { + logger.log(Level.INFO, "EGL configuration not found, allowing higher RGB"); + choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, false, false, true); + } + + if (choosenConfig == null && requestedConfig.a > 0) { + logger.log(Level.INFO, "EGL configuration not found, allowing higher alpha"); + choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, false, true); + } + + if (choosenConfig == null && requestedConfig.s > 0) { + logger.log(Level.INFO, "EGL configuration not found, allowing higher samples"); + choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, true, true); + } + + if (choosenConfig == null && requestedConfig.a > 0) { + logger.log(Level.INFO, "EGL configuration not found, reducing alpha"); + requestedConfig.a = 1; + choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, false, true); + } + + if (choosenConfig == null && requestedConfig.s > 0) { + logger.log(Level.INFO, "EGL configuration not found, reducing samples"); + requestedConfig.s = 1; + if (requestedConfig.a > 0) { + choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, true, true); + } else { + choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, false, true, true); + } + } + + if (choosenConfig == null && requestedConfig.getBitsPerPixel() > 16) { + logger.log(Level.INFO, "EGL configuration not found, setting to RGB565"); + requestedConfig.r = 5; + requestedConfig.g = 6; + requestedConfig.b = 5; + choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, false, false, true); + + if (choosenConfig == null) { + logger.log(Level.INFO, "EGL configuration not found, allowing higher alpha"); + choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, false, true); + } + } + + if (choosenConfig == null) { + logger.log(Level.INFO, "EGL configuration not found, looking for best config with >= 16 bit Depth"); + //failsafe, should pick best config with at least 16 depth + requestedConfig = new Config(0, 0, 0, 0, 16, 0, 0); + choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, false, false, true); + } + + if (choosenConfig != null) { + logger.fine("GLSurfaceView asks for egl config, returning: "); + logEGLConfig(choosenConfig, display, egl, Level.FINE); + + storeSelectedConfig(egl, display, choosenConfig); + return choosenConfig; + } else { + logger.severe("No EGL Config found"); + return null; + } + } + + private Config getRequestedConfig() { + int r, g, b; + if (settings.getBitsPerPixel() == 24) { + r = g = b = 8; + } else { + if (settings.getBitsPerPixel() != 16) { + logger.log(Level.SEVERE, "Invalid bitsPerPixel setting: {0}, setting to RGB565 (16)", settings.getBitsPerPixel()); + settings.setBitsPerPixel(16); + } + r = 5; + g = 6; + b = 5; + } + logger.log(Level.FINE, "Requested Display Config:"); + logger.log(Level.FINE, "RGB: {0}, alpha: {1}, depth: {2}, samples: {3}, stencil: {4}", + new Object[]{settings.getBitsPerPixel(), + settings.getAlphaBits(), settings.getDepthBits(), + settings.getSamples(), settings.getStencilBits()}); + return new Config( + r, g, b, + settings.getAlphaBits(), + settings.getDepthBits(), + settings.getSamples(), + settings.getStencilBits()); + } + + /** + * Query egl for the available configs + * @param egl + * @param display + * @return + */ + private EGLConfig[] getConfigs(EGL10 egl, EGLDisplay display) { + + int[] num_config = new int[1]; + int[] configSpec = new int[]{ + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL10.EGL_NONE}; + + if (!egl.eglChooseConfig(display, configSpec, null, 0, num_config)) { + RendererUtil.checkEGLError(egl); + throw new AssertionError(); + } + + int numConfigs = num_config[0]; + EGLConfig[] configs = new EGLConfig[numConfigs]; + if (!egl.eglChooseConfig(display, configSpec, configs, numConfigs, num_config)) { + RendererUtil.checkEGLError(egl); + throw new AssertionError(); + } + + logger.fine("--------------Display Configurations---------------"); + for (EGLConfig eGLConfig : configs) { + logEGLConfig(eGLConfig, display, egl, Level.FINE); + logger.fine("----------------------------------------"); + } + + return configs; + } + + private EGLConfig chooseConfig( + EGL10 egl, EGLDisplay display, EGLConfig[] configs, Config requestedConfig, + boolean higherRGB, boolean higherAlpha, + boolean higherSamples, boolean higherStencil) { + + EGLConfig keptConfig = null; + int kr = 0; + int kg = 0; + int kb = 0; + int ka = 0; + int kd = 0; + int ks = 0; + int kst = 0; + + + // first pass through config list. Try to find an exact match. + for (EGLConfig config : configs) { + int r = eglGetConfigAttribSafe(egl, display, config, + EGL10.EGL_RED_SIZE); + int g = eglGetConfigAttribSafe(egl, display, config, + EGL10.EGL_GREEN_SIZE); + int b = eglGetConfigAttribSafe(egl, display, config, + EGL10.EGL_BLUE_SIZE); + int a = eglGetConfigAttribSafe(egl, display, config, + EGL10.EGL_ALPHA_SIZE); + int d = eglGetConfigAttribSafe(egl, display, config, + EGL10.EGL_DEPTH_SIZE); + int s = eglGetConfigAttribSafe(egl, display, config, + EGL10.EGL_SAMPLES); + int st = eglGetConfigAttribSafe(egl, display, config, + EGL10.EGL_STENCIL_SIZE); + + logger.log(Level.FINE, "Checking Config r: {0}, g: {1}, b: {2}, alpha: {3}, depth: {4}, samples: {5}, stencil: {6}", + new Object[]{r, g, b, a, d, s, st}); + + if (higherRGB && r < requestedConfig.r) { continue; } + if (!higherRGB && r != requestedConfig.r) { continue; } + + if (higherRGB && g < requestedConfig.g) { continue; } + if (!higherRGB && g != requestedConfig.g) { continue; } + + if (higherRGB && b < requestedConfig.b) { continue; } + if (!higherRGB && b != requestedConfig.b) { continue; } + + if (higherAlpha && a < requestedConfig.a) { continue; } + if (!higherAlpha && a != requestedConfig.a) { continue; } + + if (d < requestedConfig.d) { continue; } // always allow higher depth + + if (higherSamples && s < requestedConfig.s) { continue; } + if (!higherSamples && s != requestedConfig.s) { continue; } + + if (higherStencil && st < requestedConfig.st) { continue; } + if (!higherStencil && !inRange(st, 0, requestedConfig.st)) { continue; } + + //we keep the config if it is better + if ( r >= kr || g >= kg || b >= kb || a >= ka || + d >= kd || s >= ks || st >= kst ) { + kr = r; kg = g; kb = b; ka = a; + kd = d; ks = s; kst = st; + keptConfig = config; + logger.log(Level.FINE, "Keeping Config r: {0}, g: {1}, b: {2}, alpha: {3}, depth: {4}, samples: {5}, stencil: {6}", + new Object[]{r, g, b, a, d, s, st}); + } + + } + + if (keptConfig != null) { + return keptConfig; + } + + //no match found + logger.log(Level.SEVERE, "No egl config match found"); + return null; + } + + private static int eglGetConfigAttribSafe(EGL10 egl, EGLDisplay display, EGLConfig config, int attribute) { + int[] value = new int[1]; + if (!egl.eglGetConfigAttrib(display, config, attribute, value)) { + RendererUtil.checkEGLError(egl); + throw new AssertionError(); + } + return value[0]; + } + + private void storeSelectedConfig(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { + int r = eglGetConfigAttribSafe(egl, display, eglConfig, EGL10.EGL_RED_SIZE); + int g = eglGetConfigAttribSafe(egl, display, eglConfig, EGL10.EGL_GREEN_SIZE); + int b = eglGetConfigAttribSafe(egl, display, eglConfig, EGL10.EGL_BLUE_SIZE); + settings.setBitsPerPixel(r+g+b); + + settings.setAlphaBits( + eglGetConfigAttribSafe(egl, display, eglConfig, EGL10.EGL_ALPHA_SIZE)); + settings.setDepthBits( + eglGetConfigAttribSafe(egl, display, eglConfig, EGL10.EGL_DEPTH_SIZE)); + settings.setSamples( + eglGetConfigAttribSafe(egl, display, eglConfig, EGL10.EGL_SAMPLES)); + settings.setStencilBits( + eglGetConfigAttribSafe(egl, display, eglConfig, EGL10.EGL_STENCIL_SIZE)); + } + + /** + * log output with egl config details + * + * @param conf + * @param display + * @param egl + */ + private void logEGLConfig(EGLConfig conf, EGLDisplay display, EGL10 egl, Level level) { + + logger.log(level, "EGL_RED_SIZE = {0}", + eglGetConfigAttribSafe(egl, display, conf, EGL10.EGL_RED_SIZE)); + + logger.log(level, "EGL_GREEN_SIZE = {0}", + eglGetConfigAttribSafe(egl, display, conf, EGL10.EGL_GREEN_SIZE)); + + logger.log(level, "EGL_BLUE_SIZE = {0}", + eglGetConfigAttribSafe(egl, display, conf, EGL10.EGL_BLUE_SIZE)); + + logger.log(level, "EGL_ALPHA_SIZE = {0}", + eglGetConfigAttribSafe(egl, display, conf, EGL10.EGL_ALPHA_SIZE)); + + logger.log(level, "EGL_DEPTH_SIZE = {0}", + eglGetConfigAttribSafe(egl, display, conf, EGL10.EGL_DEPTH_SIZE)); + + logger.log(level, "EGL_STENCIL_SIZE = {0}", + eglGetConfigAttribSafe(egl, display, conf, EGL10.EGL_STENCIL_SIZE)); + + logger.log(level, "EGL_RENDERABLE_TYPE = {0}", + eglGetConfigAttribSafe(egl, display, conf, EGL10.EGL_RENDERABLE_TYPE)); + + logger.log(level, "EGL_SURFACE_TYPE = {0}", + eglGetConfigAttribSafe(egl, display, conf, EGL10.EGL_SURFACE_TYPE)); + + logger.log(level, "EGL_SAMPLE_BUFFERS = {0}", + eglGetConfigAttribSafe(egl, display, conf, EGL10.EGL_SAMPLE_BUFFERS)); + + logger.log(level, "EGL_SAMPLES = {0}", + eglGetConfigAttribSafe(egl, display, conf, EGL10.EGL_SAMPLES)); + } + + private boolean inRange(int val, int min, int max) { + return min <= val && val <= max; + } + + private class Config { + /** + * red, green, blue, alpha, depth, samples, stencil + */ + int r, g, b, a, d, s, st; + + private Config(int r, int g, int b, int a, int d, int s, int st) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + this.d = d; + this.s = s; + this.st = st; + } + + private int getBitsPerPixel() { + return r+g+b; + } + } + +//DON'T REMOVE THIS, USED FOR UNIT TESTING FAILING CONFIGURATION LISTS. +// private static class Config { +// +// int r, g, b, a, d, s, ms, ns; +// +// public Config(int r, int g, int b, int a, int d, int s, int ms, int ns) { +// this.r = r; +// this.g = g; +// this.b = b; +// this.a = a; +// this.d = d; +// this.s = s; +// this.ms = ms; +// this.ns = ns; +// } +// +// @Override +// public String toString() { +// return "Config{" + "r=" + r + ", g=" + g + ", b=" + b + ", a=" + a + ", d=" + d + ", s=" + s + ", ms=" + ms + ", ns=" + ns + '}'; +// } +// } +// +// public static Config chooseConfig(List configs, ConfigType configType, int mSamples) { +// +// Config keptConfig = null; +// int kd = 0; +// int knbMs = 0; +// +// +// // first pass through config list. Try to find an exact match. +// for (Config config : configs) { +//// logEGLConfig(config, display, egl); +// int r = config.r; +// int g = config.g; +// int b = config.b; +// int a = config.a; +// int d = config.d; +// int s = config.s; +// int isMs = config.ms; +// int nbMs = config.ns; +// +// if (inRange(r, configType.mr, configType.r) +// && inRange(g, configType.mg, configType.g) +// && inRange(b, configType.mb, configType.b) +// && inRange(a, configType.ma, configType.a) +// && inRange(d, configType.md, configType.d) +// && inRange(s, configType.ms, configType.s)) { +// if (mSamples == 0 && isMs != 0) { +// continue; +// } +// boolean keep = false; +// //we keep the config if the depth is better or if the AA setting is better +// if (d >= kd) { +// kd = d; +// keep = true; +// } else { +// keep = false; +// } +// +// if (mSamples != 0) { +// if (nbMs >= knbMs && nbMs <= mSamples) { +// knbMs = nbMs; +// keep = true; +// } else { +// keep = false; +// } +// } +// +// if (keep) { +// keptConfig = config; +// } +// } +// } +// +// if (keptConfig != null) { +// return keptConfig; +// } +// +// if (configType == ConfigType.BEST) { +// keptConfig = chooseConfig(configs, ConfigType.BEST_TRANSLUCENT, mSamples); +// +// if (keptConfig != null) { +// return keptConfig; +// } +// } +// +// if (configType == ConfigType.BEST_TRANSLUCENT) { +// keptConfig = chooseConfig(configs, ConfigType.FASTEST, mSamples); +// +// if (keptConfig != null) { +// return keptConfig; +// } +// } +// // failsafe. pick the 1st config. +// +// for (Config config : configs) { +// if (config.d >= 16) { +// return config; +// } +// } +// +// return null; +// } +// +// private static boolean inRange(int val, int min, int max) { +// return min <= val && val <= max; +// } +// +// public static void main(String... argv) { +// List confs = new ArrayList(); +// confs.add(new Config(5, 6, 5, 0, 0, 0, 0, 0)); +// confs.add(new Config(5, 6, 5, 0, 16, 0, 0, 0)); +// confs.add(new Config(5, 6, 5, 0, 24, 8, 0, 0)); +// confs.add(new Config(8, 8, 8, 8, 0, 0, 0, 0)); +//// confs.add(new Config(8, 8, 8, 8, 16, 0, 0, 0)); +//// confs.add(new Config(8, 8, 8, 8, 24, 8, 0, 0)); +// +// confs.add(new Config(5, 6, 5, 0, 0, 0, 1, 2)); +// confs.add(new Config(5, 6, 5, 0, 16, 0, 1, 2)); +// confs.add(new Config(5, 6, 5, 0, 24, 8, 1, 2)); +// confs.add(new Config(8, 8, 8, 8, 0, 0, 1, 2)); +//// confs.add(new Config(8, 8, 8, 8, 16, 0, 1, 2)); +//// confs.add(new Config(8, 8, 8, 8, 24, 8, 1, 2)); +// +// confs.add(new Config(5, 6, 5, 0, 0, 0, 1, 4)); +// confs.add(new Config(5, 6, 5, 0, 16, 0, 1, 4)); +// confs.add(new Config(5, 6, 5, 0, 24, 8, 1, 4)); +// confs.add(new Config(8, 8, 8, 8, 0, 0, 1, 4)); +//// confs.add(new Config(8, 8, 8, 8, 16, 0, 1, 4)); +//// confs.add(new Config(8, 8, 8, 8, 24, 8, 1, 4)); +// +// Config chosen = chooseConfig(confs, ConfigType.BEST, 0); +// +// System.err.println(chosen); +// +// } +} diff --git a/jme3-android/src/main/java/com/jme3/system/android/AndroidTimer.java b/jme3-android/src/main/java/com/jme3/system/android/AndroidTimer.java new file mode 100644 index 000000000..965895c41 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/system/android/AndroidTimer.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.android; + +import com.jme3.system.Timer; + +/** + * AndroidTimer is a System.nanoTime implementation of Timer. + */ +public class AndroidTimer extends Timer { + + //private static final long TIMER_RESOLUTION = 1000L; + //private static final float INVERSE_TIMER_RESOLUTION = 1f/1000L; + private static final long TIMER_RESOLUTION = 1000000000L; + private static final float INVERSE_TIMER_RESOLUTION = 1f/1000000000L; + + private long startTime; + private long previousTime; + private float tpf; + private float fps; + + public AndroidTimer() { + //startTime = System.currentTimeMillis(); + startTime = System.nanoTime(); + } + + /** + * Returns the time in seconds. The timer starts + * at 0.0 seconds. + * + * @return the current time in seconds + */ + @Override + public float getTimeInSeconds() { + return getTime() * INVERSE_TIMER_RESOLUTION; + } + + public long getTime() { + //return System.currentTimeMillis() - startTime; + return System.nanoTime() - startTime; + } + + public long getResolution() { + return TIMER_RESOLUTION; + } + + public float getFrameRate() { + return fps; + } + + public float getTimePerFrame() { + return tpf; + } + + public void update() { + tpf = (getTime() - previousTime) * (1.0f / TIMER_RESOLUTION); + fps = 1.0f / tpf; + previousTime = getTime(); + } + + public void reset() { + //startTime = System.currentTimeMillis(); + startTime = System.nanoTime(); + previousTime = getTime(); + } +} diff --git a/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java new file mode 100644 index 000000000..52406daaf --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java @@ -0,0 +1,226 @@ +package com.jme3.system.android; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Environment; +import com.jme3.asset.AndroidAssetManager; +import com.jme3.asset.AndroidImageInfo; +import com.jme3.asset.AssetManager; +import com.jme3.audio.AudioRenderer; +import com.jme3.audio.android.AndroidAudioRenderer; +import com.jme3.audio.android.AndroidMediaPlayerAudioRenderer; +import com.jme3.audio.android.AndroidOpenALSoftAudioRenderer; +import com.jme3.system.*; +import com.jme3.system.JmeContext.Type; +import com.jme3.texture.Image; +import com.jme3.texture.image.DefaultImageRaster; +import com.jme3.texture.image.ImageRaster; +import com.jme3.util.AndroidScreenshots; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.nio.ByteBuffer; +import java.util.logging.Level; + +public class JmeAndroidSystem extends JmeSystemDelegate { + + private static Activity activity; + private static String audioRendererType = AppSettings.ANDROID_MEDIAPLAYER; + + static { + try { + System.loadLibrary("bulletjme"); + } catch (UnsatisfiedLinkError e) { + } + } + + @Override + public void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException { + Bitmap bitmapImage = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + AndroidScreenshots.convertScreenShot(imageData, bitmapImage); + Bitmap.CompressFormat compressFormat; + if (format.equals("png")) { + compressFormat = Bitmap.CompressFormat.PNG; + } else if (format.equals("jpg")) { + compressFormat = Bitmap.CompressFormat.JPEG; + } else { + throw new UnsupportedOperationException("Only 'png' and 'jpg' formats are supported on Android"); + } + bitmapImage.compress(compressFormat, 95, outStream); + bitmapImage.recycle(); + } + + @Override + public ImageRaster createImageRaster(Image image, int slice) { + if (image.getEfficentData() != null) { + return (AndroidImageInfo) image.getEfficentData(); + } else { + return new DefaultImageRaster(image, slice); + } + } + + @Override + public AssetManager newAssetManager(URL configFile) { + logger.log(Level.FINE, "Creating asset manager with config {0}", configFile); + return new AndroidAssetManager(configFile); + } + + @Override + public AssetManager newAssetManager() { + logger.log(Level.FINE, "Creating asset manager with default config"); + return new AndroidAssetManager(null); + } + + @Override + public void showErrorDialog(String message) { + final String finalMsg = message; + final String finalTitle = "Error in application"; + final Activity context = JmeAndroidSystem.getActivity(); + + context.runOnUiThread(new Runnable() { + @Override + public void run() { + AlertDialog dialog = new AlertDialog.Builder(context) + .setTitle(finalTitle).setMessage(finalMsg).create(); + dialog.show(); + } + }); + } + + @Override + public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) { + return true; + } + + @Override + public JmeContext newContext(AppSettings settings, Type contextType) { + if (settings.getAudioRenderer().equals(AppSettings.ANDROID_MEDIAPLAYER)) { + audioRendererType = AppSettings.ANDROID_MEDIAPLAYER; + } else if (settings.getAudioRenderer().equals(AppSettings.ANDROID_OPENAL_SOFT)) { + audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; + } else { + logger.log(Level.INFO, "AudioRenderer not set. Defaulting to Android MediaPlayer / SoundPool"); + audioRendererType = AppSettings.ANDROID_MEDIAPLAYER; + } + initialize(settings); + JmeContext ctx = new OGLESContext(); + ctx.setSettings(settings); + return ctx; + } + + @Override + public AudioRenderer newAudioRenderer(AppSettings settings) { + + if (settings.getAudioRenderer().equals(AppSettings.ANDROID_MEDIAPLAYER)) { + logger.log(Level.INFO, "newAudioRenderer settings set to Android MediaPlayer / SoundPool"); + audioRendererType = AppSettings.ANDROID_MEDIAPLAYER; + return new AndroidMediaPlayerAudioRenderer(activity); + } else if (settings.getAudioRenderer().equals(AppSettings.ANDROID_OPENAL_SOFT)) { + logger.log(Level.INFO, "newAudioRenderer settings set to Android OpenAL Soft"); + audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; + return new AndroidOpenALSoftAudioRenderer(); + } else { + logger.log(Level.INFO, "AudioRenderer not set. Defaulting to Android MediaPlayer / SoundPool"); + audioRendererType = AppSettings.ANDROID_MEDIAPLAYER; + return new AndroidMediaPlayerAudioRenderer(activity); + } + } + + @Override + public void initialize(AppSettings settings) { + if (initialized) { + return; + } + + initialized = true; + + logger.log(Level.INFO, "Running on {0}", getFullName()); + } + + @Override + public Platform getPlatform() { + String arch = System.getProperty("os.arch").toLowerCase(); + if (arch.contains("arm")) { + if (arch.contains("v5")) { + return Platform.Android_ARM5; + } else if (arch.contains("v6")) { + return Platform.Android_ARM6; + } else if (arch.contains("v7")) { + return Platform.Android_ARM7; + } else { + return Platform.Android_ARM5; // unknown ARM + } + } else { + throw new UnsupportedOperationException("Unsupported Android Platform"); + } + } + + @Override + public synchronized File getStorageFolder(JmeSystem.StorageFolderType type) { + File storageFolder = null; + + switch (type) { + case Internal: + // http://developer.android.com/guide/topics/data/data-storage.html + // http://developer.android.com/guide/topics/data/data-storage.html#filesInternal + // http://developer.android.com/reference/android/content/Context.html#getFilesDir() + // http://developer.android.com/reference/android/content/Context.html#getDir(java.lang.String, int) + + // getDir automatically creates the directory if necessary. + // Directory structure should be: /data/data//app_ + // When created this way, the directory is automatically removed by the Android + // system when the app is uninstalled. + // The directory is NOT accessible by a PC connected to the device + // The files can only be accessed by this application + storageFolder = storageFolders.get(type); + if (storageFolder == null) { + storageFolder = activity.getApplicationContext().getDir("", Context.MODE_PRIVATE); + storageFolders.put(type, storageFolder); + } + break; + case External: + //http://developer.android.com/reference/android/content/Context.html#getExternalFilesDir + //http://developer.android.com/guide/topics/data/data-storage.html + + // getExternalFilesDir automatically creates the directory if necessary. + // Directory structure should be: /mnt/sdcard/Android/data//files + // When created this way, the directory is automatically removed by the Android + // system when the app is uninstalled. + // The directory is also accessible by a PC connected to the device + // so the files can be copied to the PC (ie. screenshots) + storageFolder = storageFolders.get(type); + if (storageFolder == null) { + String state = Environment.getExternalStorageState(); + logger.log(Level.FINE, "ExternalStorageState: {0}", state); + if (state.equals(Environment.MEDIA_MOUNTED)) { + storageFolder = activity.getApplicationContext().getExternalFilesDir(null); + storageFolders.put(type, storageFolder); + } + } + break; + default: + break; + } + if (storageFolder != null) { + logger.log(Level.FINE, "Base Storage Folder Path: {0}", storageFolder.getAbsolutePath()); + } else { + logger.log(Level.FINE, "Base Storage Folder not found!"); + } + return storageFolder; + } + + public static void setActivity(Activity activity) { + JmeAndroidSystem.activity = activity; + } + + public static Activity getActivity() { + return activity; + } + + public static String getAudioRendererType() { + return audioRendererType; + } +} diff --git a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java new file mode 100644 index 000000000..cf49c1cee --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system.android; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.ConfigurationInfo; +import android.graphics.PixelFormat; +import android.opengl.GLSurfaceView; +import android.os.Build; +import android.text.InputType; +import android.view.Gravity; +import android.view.SurfaceHolder; +import android.view.ViewGroup.LayoutParams; +import android.widget.EditText; +import android.widget.FrameLayout; +import com.jme3.input.*; +import com.jme3.input.android.AndroidInput; +import com.jme3.input.android.AndroidSensorJoyInput; +import com.jme3.input.android.AndroidInputHandler; +import com.jme3.input.controls.SoftTextDialogInputListener; +import com.jme3.input.dummy.DummyKeyInput; +import com.jme3.input.dummy.DummyMouseInput; +import com.jme3.renderer.android.AndroidGLSurfaceView; +import com.jme3.renderer.android.OGLESShaderRenderer; +import com.jme3.system.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTextDialogInput { + + private static final Logger logger = Logger.getLogger(OGLESContext.class.getName()); + protected final AtomicBoolean created = new AtomicBoolean(false); + protected final AtomicBoolean renderable = new AtomicBoolean(false); + protected final AtomicBoolean needClose = new AtomicBoolean(false); + protected AppSettings settings = new AppSettings(true); + + /* + * >= OpenGL ES 2.0 (Android 2.2+) + */ + protected OGLESShaderRenderer renderer; + protected Timer timer; + protected SystemListener listener; + protected boolean autoFlush = true; + protected AndroidInputHandler androidInput; + protected int minFrameDuration = 0; // No FPS cap + protected JoyInput androidSensorJoyInput = null; + /** + * EGL_RENDERABLE_TYPE: EGL_OPENGL_ES_BIT = OpenGL ES 1.0 | + * EGL_OPENGL_ES2_BIT = OpenGL ES 2.0 + */ + protected int clientOpenGLESVersion = 1; + + public OGLESContext() { + } + + @Override + public Type getType() { + return Type.Display; + } + + /** + * createView creates the GLSurfaceView that the renderer will + * draw to.

The result GLSurfaceView will receive input events and + * forward them to the Application. Any rendering will be done into the + * GLSurfaceView. Only one GLSurfaceView can be created at this time. The + * given configType specifies how to determine the display configuration. + * + * @return GLSurfaceView The newly created view + */ + public AndroidGLSurfaceView createView() { + AndroidGLSurfaceView view; + int buildVersion = Build.VERSION.SDK_INT; + + // Start to set up the view + view = new AndroidGLSurfaceView(JmeAndroidSystem.getActivity().getApplication()); + if (androidInput == null) { + androidInput = new AndroidInputHandler(); + } + androidInput.setView(view); + androidInput.loadSettings(settings); + + // setEGLContextClientVersion must be set before calling setRenderer + // this means it cannot be set in AndroidConfigChooser (too late) + int rawOpenGLESVersion = getOpenGLESVersion(); +// logger.log(Level.FINE, "clientOpenGLESVersion {0}.{1}", +// new Object[]{clientOpenGLESVersion>>16, clientOpenGLESVersion<<16}); + if (rawOpenGLESVersion < 0x20000) { + throw new UnsupportedOperationException("OpenGL ES 2.0 is not supported on this device"); + } else { + clientOpenGLESVersion = 2; + view.setEGLContextClientVersion(clientOpenGLESVersion); + } + + view.setFocusableInTouchMode(true); + view.setFocusable(true); + view.getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU); + + // setFormat must be set before AndroidConfigChooser is called by the surfaceview. + // if setFormat is called after ConfigChooser is called, then execution + // stops at the setFormat call without a crash. + // We look at the user setting for alpha bits and set the surfaceview + // PixelFormat to either Opaque, Transparent, or Translucent. + // ConfigChooser will do it's best to honor the alpha requested by the user + // For best rendering performance, use Opaque (alpha bits = 0). + int curAlphaBits = settings.getAlphaBits(); + logger.log(Level.FINE, "curAlphaBits: {0}", curAlphaBits); + if (curAlphaBits >= 8) { + logger.log(Level.FINE, "Pixel Format: TRANSLUCENT"); + view.getHolder().setFormat(PixelFormat.TRANSLUCENT); + view.setZOrderOnTop(true); + } else if (curAlphaBits >= 1) { + logger.log(Level.FINE, "Pixel Format: TRANSPARENT"); + view.getHolder().setFormat(PixelFormat.TRANSPARENT); + } else { + logger.log(Level.FINE, "Pixel Format: OPAQUE"); + view.getHolder().setFormat(PixelFormat.OPAQUE); + } + + AndroidConfigChooser configChooser = new AndroidConfigChooser(settings); + view.setEGLConfigChooser(configChooser); + view.setRenderer(this); + + // Attempt to preserve the EGL Context on app pause/resume. + // Not destroying and recreating the EGL context + // will help with resume time by reusing the existing context to avoid + // reloading all the OpenGL objects. + if (buildVersion >= 11) { + view.setPreserveEGLContextOnPause(true); + } + + return view; + } + /** + * Get the OpenGL ES version + * @return version returns the int value of the GLES version + */ + public int getOpenGLESVersion() { + ActivityManager am = + (ActivityManager) JmeAndroidSystem.getActivity().getApplication().getSystemService(Context.ACTIVITY_SERVICE); + ConfigurationInfo info = am.getDeviceConfigurationInfo(); + logger.log(Level.FINE, "OpenGL Version {0}:", info.getGlEsVersion()); + return info.reqGlEsVersion; +// return (info.reqGlEsVersion >= 0x20000); + } + + // renderer:initialize + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig cfg) { + if (created.get() && renderer != null) { + renderer.resetGLObjects(); + } else { + if (!created.get()) { + logger.fine("GL Surface created, initializing JME3 renderer"); + initInThread(); + } else { + logger.warning("GL Surface already created"); + } + } + } + + protected void initInThread() { + created.set(true); + + logger.fine("OGLESContext create"); + logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName()); + + // Setup unhandled Exception Handler + Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Exception thrown in " + thread.toString(), thrown); + } + }); + + timer = new AndroidTimer(); + renderer = new OGLESShaderRenderer(); + + renderer.initialize(); + + JmeSystem.setSoftTextDialogInput(this); + + needClose.set(false); + } + + /** + * De-initialize in the OpenGL thread. + */ + protected void deinitInThread() { + if (renderable.get()) { + created.set(false); + if (renderer != null) { + renderer.cleanup(); + } + + listener.destroy(); + + listener = null; + renderer = null; + timer = null; + + // do android specific cleaning here + logger.fine("Display destroyed."); + + renderable.set(false); + } + } + + @Override + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + if (androidInput != null) { + androidInput.loadSettings(settings); + } + + } + + @Override + public void setSystemListener(SystemListener listener) { + this.listener = listener; + } + + @Override + public AppSettings getSettings() { + return settings; + } + + @Override + public com.jme3.renderer.Renderer getRenderer() { + return renderer; + } + + @Override + public MouseInput getMouseInput() { + return new DummyMouseInput(); + } + + @Override + public KeyInput getKeyInput() { + return new DummyKeyInput(); + } + + @Override + public JoyInput getJoyInput() { + if (androidSensorJoyInput == null) { + androidSensorJoyInput = new AndroidSensorJoyInput(); + } + return androidSensorJoyInput; + } + + @Override + public TouchInput getTouchInput() { + return androidInput; + } + + @Override + public Timer getTimer() { + return timer; + } + + @Override + public void setTitle(String title) { + } + + @Override + public boolean isCreated() { + return created.get(); + } + + @Override + public void setAutoFlushFrames(boolean enabled) { + this.autoFlush = enabled; + } + + // SystemListener:reshape + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + logger.log(Level.FINE, "GL Surface changed, width: {0} height: {1}", new Object[]{width, height}); + // update the application settings with the new resolution + settings.setResolution(width, height); + // reload settings in androidInput so the correct touch event scaling can be + // calculated in case the surface resolution is different than the view + androidInput.loadSettings(settings); + // if the application has already been initialized (ie renderable is set) + // then call reshape so the app can adjust to the new resolution. + if (renderable.get()) { + logger.log(Level.FINE, "App already initialized, calling reshape"); + listener.reshape(width, height); + } + } + + // SystemListener:update + @Override + public void onDrawFrame(GL10 gl) { + if (needClose.get()) { + deinitInThread(); + return; + } + + if (!renderable.get()) { + if (created.get()) { + logger.fine("GL Surface is setup, initializing application"); + listener.initialize(); + renderable.set(true); + } + } else { + if (!created.get()) { + throw new IllegalStateException("onDrawFrame without create"); + } + + long milliStart = System.currentTimeMillis(); + + listener.update(); + if (autoFlush) { + renderer.onFrame(); + } + + long milliDelta = System.currentTimeMillis() - milliStart; + + // Enforce a FPS cap + if (milliDelta < minFrameDuration) { + //logger.log(Level.FINE, "Time per frame {0}", milliDelta); + try { + Thread.sleep(minFrameDuration - milliDelta); + } catch (InterruptedException e) { + } + } + } + } + + @Override + public boolean isRenderable() { + return renderable.get(); + } + + @Override + public void create(boolean waitFor) { + if (waitFor) { + waitFor(true); + } + } + + public void create() { + create(false); + } + + @Override + public void restart() { + } + + @Override + public void destroy(boolean waitFor) { + needClose.set(true); + if (waitFor) { + waitFor(false); + } + } + + public void destroy() { + destroy(true); + } + + protected void waitFor(boolean createdVal) { + while (renderable.get() != createdVal) { + try { + Thread.sleep(10); + } catch (InterruptedException ex) { + } + } + } + + public void requestDialog(final int id, final String title, final String initialValue, final SoftTextDialogInputListener listener) { + logger.log(Level.FINE, "requestDialog: title: {0}, initialValue: {1}", + new Object[]{title, initialValue}); + + final Activity activity = JmeAndroidSystem.getActivity(); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + + final FrameLayout layoutTextDialogInput = new FrameLayout(activity); + final EditText editTextDialogInput = new EditText(activity); + editTextDialogInput.setWidth(LayoutParams.FILL_PARENT); + editTextDialogInput.setHeight(LayoutParams.FILL_PARENT); + editTextDialogInput.setPadding(20, 20, 20, 20); + editTextDialogInput.setGravity(Gravity.FILL_HORIZONTAL); + //editTextDialogInput.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); + + editTextDialogInput.setText(initialValue); + + switch (id) { + case SoftTextDialogInput.TEXT_ENTRY_DIALOG: + + editTextDialogInput.setInputType(InputType.TYPE_CLASS_TEXT); + break; + + case SoftTextDialogInput.NUMERIC_ENTRY_DIALOG: + + editTextDialogInput.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED); + break; + + case SoftTextDialogInput.NUMERIC_KEYPAD_DIALOG: + + editTextDialogInput.setInputType(InputType.TYPE_CLASS_PHONE); + break; + + default: + break; + } + + layoutTextDialogInput.addView(editTextDialogInput); + + AlertDialog dialogTextInput = new AlertDialog.Builder(activity).setTitle(title).setView(layoutTextDialogInput).setPositiveButton("OK", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + /* User clicked OK, send COMPLETE action + * and text */ + listener.onSoftText(SoftTextDialogInputListener.COMPLETE, editTextDialogInput.getText().toString()); + } + }).setNegativeButton("Cancel", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + /* User clicked CANCEL, send CANCEL action + * and text */ + listener.onSoftText(SoftTextDialogInputListener.CANCEL, editTextDialogInput.getText().toString()); + } + }).create(); + + dialogTextInput.show(); + } + }); + } +} diff --git a/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidImageLoader.java b/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidImageLoader.java new file mode 100644 index 000000000..17f850e7a --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidImageLoader.java @@ -0,0 +1,20 @@ +package com.jme3.texture.plugins; + +import android.graphics.Bitmap; +import com.jme3.asset.AndroidImageInfo; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.texture.Image; +import java.io.IOException; + +public class AndroidImageLoader implements AssetLoader { + + public Object load(AssetInfo info) throws IOException { + AndroidImageInfo imageInfo = new AndroidImageInfo(info); + Bitmap bitmap = imageInfo.getBitmap(); + + Image image = new Image(imageInfo.getFormat(), bitmap.getWidth(), bitmap.getHeight(), null); + image.setEfficentData(imageInfo); + return image; + } +} diff --git a/jme3-android/src/main/java/com/jme3/util/AndroidLogHandler.java b/jme3-android/src/main/java/com/jme3/util/AndroidLogHandler.java new file mode 100644 index 000000000..9966be955 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/util/AndroidLogHandler.java @@ -0,0 +1,110 @@ +package com.jme3.util; + +import android.util.Log; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +/** + * Converts from Java based logging ({@link Logger} to Android based logging + * {@link Log}. + */ +public class AndroidLogHandler extends Handler { + + private static final Formatter JME_FORMATTER = new JmeFormatter() { + + String lineSeperator = System.getProperty("line.separator"); + + @Override + public String format(LogRecord record) { + StringBuilder sb = new StringBuilder(); + + sb.append(record.getLevel().getLocalizedName()).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(); + } + + }; + + @Override + public void close() { + } + + @Override + public void flush() { + } + + @Override + public void publish(LogRecord record) { + + int level = getAndroidLevel(record.getLevel()); +// String tag = loggerNameToTag(record.getLoggerName()); + String tag = record.getLoggerName(); + + try { + String message = JME_FORMATTER.format(record); + Log.println(level, tag, message); + } catch (RuntimeException e) { + Log.e("AndroidHandler", "Error logging message.", e); + } + } + + /** + * Converts a {@link java.util.logging.Logger} logging level into an Android + * one. + * + * @param level The {@link java.util.logging.Logger} logging level. + * + * @return The resulting Android logging level. + */ + static int getAndroidLevel(Level level) { + int value = level.intValue(); + if (value >= 1000) { // SEVERE + return Log.ERROR; + } else if (value >= 900) { // WARNING + return Log.WARN; + } else if (value >= 800) { // INFO + return Log.INFO; + } else { + return Log.DEBUG; + } + } + + /** + * Returns the short logger tag for the given logger name. + * Traditionally loggers are named by fully-qualified Java classes; this + * method attempts to return a concise identifying part of such names. + */ + public static String loggerNameToTag(String loggerName) { + // Anonymous logger. + if (loggerName == null) { + return "null"; + } + + int length = loggerName.length(); + int lastPeriod = loggerName.lastIndexOf("."); + + if (lastPeriod == -1) { + return loggerName; + } + + return loggerName.substring(lastPeriod + 1); + } + +} diff --git a/jme3-android/src/main/java/com/jme3/util/AndroidScreenshots.java b/jme3-android/src/main/java/com/jme3/util/AndroidScreenshots.java new file mode 100644 index 000000000..8c742618a --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/util/AndroidScreenshots.java @@ -0,0 +1,42 @@ +package com.jme3.util; + +import android.graphics.Bitmap; +import java.nio.ByteBuffer; +import java.util.logging.Logger; + +public final class AndroidScreenshots { + + private static final Logger logger = Logger.getLogger(AndroidScreenshots.class.getName()); + + /** + * Convert OpenGL GLES20.GL_RGBA to Bitmap.Config.ARGB_8888 and store result + * in a Bitmap + * + * @param buf ByteBuffer that has the pixel color data from OpenGL + * @param bitmapImage Bitmap to be used after converting the data + */ + public static void convertScreenShot(ByteBuffer buf, Bitmap bitmapImage) { + int width = bitmapImage.getWidth(); + int height = bitmapImage.getHeight(); + int size = width * height; + + // Grab data from ByteBuffer as Int Array to manipulate data and send to image + int[] data = new int[size]; + buf.asIntBuffer().get(data); + + // convert from GLES20.GL_RGBA to Bitmap.Config.ARGB_8888 + // ** need to swap RED and BLUE ** + for (int idx = 0; idx < data.length; idx++) { + int initial = data[idx]; + int pb = (initial >> 16) & 0xff; + int pr = (initial << 16) & 0x00ff0000; + int pix1 = (initial & 0xff00ff00) | pr | pb; + data[idx] = pix1; + } + + // OpenGL and Bitmap have opposite starting points for Y axis (top vs bottom) + // Need to write the data in the image from the bottom to the top + // Use size-width to indicate start with last row and increment by -width for each row + bitmapImage.setPixels(data, size - width, -width, 0, 0, width, height); + } +} \ No newline at end of file diff --git a/jme3-android/src/main/java/com/jme3/util/RingBuffer.java b/jme3-android/src/main/java/com/jme3/util/RingBuffer.java new file mode 100644 index 000000000..1d3c22d7e --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/util/RingBuffer.java @@ -0,0 +1,76 @@ +package com.jme3.util; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Ring buffer (fixed size queue) implementation using a circular array (array + * with wrap-around). + */ +// suppress unchecked warnings in Java 1.5.0_6 and later +@SuppressWarnings("unchecked") +public class RingBuffer implements Iterable { + + private T[] buffer; // queue elements + private int count = 0; // number of elements on queue + private int indexOut = 0; // index of first element of queue + private int indexIn = 0; // index of next available slot + + // cast needed since no generic array creation in Java + public RingBuffer(int capacity) { + buffer = (T[]) new Object[capacity]; + } + + public boolean isEmpty() { + return count == 0; + } + + public int size() { + return count; + } + + public void push(T item) { + if (count == buffer.length) { + throw new RuntimeException("Ring buffer overflow"); + } + buffer[indexIn] = item; + indexIn = (indexIn + 1) % buffer.length; // wrap-around + count++; + } + + public T pop() { + if (isEmpty()) { + throw new RuntimeException("Ring buffer underflow"); + } + T item = buffer[indexOut]; + buffer[indexOut] = null; // to help with garbage collection + count--; + indexOut = (indexOut + 1) % buffer.length; // wrap-around + return item; + } + + public Iterator iterator() { + return new RingBufferIterator(); + } + + // an iterator, doesn't implement remove() since it's optional + private class RingBufferIterator implements Iterator { + + private int i = 0; + + public boolean hasNext() { + return i < count; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return buffer[i++]; + } + } +} diff --git a/jme3-android/src/main/java/jme3test/android/AndroidManifest.xml b/jme3-android/src/main/java/jme3test/android/AndroidManifest.xml new file mode 100644 index 000000000..a1f8f3fbf --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jme3-android/src/main/java/jme3test/android/DemoAndroidHarness.java b/jme3-android/src/main/java/jme3test/android/DemoAndroidHarness.java new file mode 100644 index 000000000..0a431cfee --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/DemoAndroidHarness.java @@ -0,0 +1,54 @@ +package jme3test.android; + +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import com.jme3.app.AndroidHarness; +import com.jme3.system.android.AndroidConfigChooser.ConfigType; + +public class DemoAndroidHarness extends AndroidHarness +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + // Set the application class to run + // First Extract the bundle from intent + Bundle bundle = getIntent().getExtras(); + + //Next extract the values using the key as + appClass = bundle.getString("APPCLASSNAME"); + + + String eglConfig = bundle.getString("EGLCONFIG"); + if (eglConfig.equals("Best")) + { + eglConfigType = ConfigType.BEST; + } + else if (eglConfig.equals("Legacy")) + { + eglConfigType = ConfigType.LEGACY; + } + else + { + eglConfigType = ConfigType.FASTEST; + } + + + if (bundle.getBoolean("VERBOSE")) + { + eglConfigVerboseLogging = true; + } + else + { + eglConfigVerboseLogging = false; + } + + + exitDialogTitle = "Close Demo?"; + exitDialogMessage = "Press Yes"; + + screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + + super.onCreate(savedInstanceState); + } + +} diff --git a/jme3-android/src/main/java/jme3test/android/DemoLaunchAdapter.java b/jme3-android/src/main/java/jme3test/android/DemoLaunchAdapter.java new file mode 100644 index 000000000..9d4c9de66 --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/DemoLaunchAdapter.java @@ -0,0 +1,72 @@ +package jme3test.android; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; +import java.util.List; + +/** + * The view adapter which gets a list of LaunchEntries and displaqs them + * @author larynx + * + */ +public class DemoLaunchAdapter extends BaseAdapter implements OnClickListener +{ + + private Context context; + + private List listDemos; + + public DemoLaunchAdapter(Context context, List listDemos) { + this.context = context; + this.listDemos = listDemos; + } + + public int getCount() { + return listDemos.size(); + } + + public Object getItem(int position) { + return listDemos.get(position); + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup viewGroup) { + DemoLaunchEntry entry = listDemos.get(position); + if (convertView == null) { + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + convertView = inflater.inflate(R.layout.demo_row, null); + } + TextView tvDemoName = (TextView) convertView.findViewById(R.id.tvDemoName); + tvDemoName.setText(entry.getName()); + + TextView tvDescription = (TextView) convertView.findViewById(R.id.tvDescription); + tvDescription.setText(entry.getDescription()); + + return convertView; + } + + @Override + public void onClick(View view) { + DemoLaunchEntry entry = (DemoLaunchEntry) view.getTag(); + + + + + } + + private void showDialog(DemoLaunchEntry entry) { + // Create and show your dialog + // Depending on the Dialogs button clicks delete it or do nothing + } + +} + diff --git a/jme3-android/src/main/java/jme3test/android/DemoLaunchEntry.java b/jme3-android/src/main/java/jme3test/android/DemoLaunchEntry.java new file mode 100644 index 000000000..e23d5ebe7 --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/DemoLaunchEntry.java @@ -0,0 +1,38 @@ +package jme3test.android; + +/** + * Name (=appClass) and Description of one demo launch inside the main apk + * @author larynx + * + */ +public class DemoLaunchEntry +{ + private String name; + private String description; + + /** + * @param name + * @param description + */ + public DemoLaunchEntry(String name, String description) { + super(); + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + + + +} diff --git a/jme3-android/src/main/java/jme3test/android/DemoMainActivity.java b/jme3-android/src/main/java/jme3test/android/DemoMainActivity.java new file mode 100644 index 000000000..9e2595308 --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/DemoMainActivity.java @@ -0,0 +1,131 @@ +package jme3test.android; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import android.view.View; +import android.widget.*; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; +import java.util.ArrayList; +import java.util.List; + +public class DemoMainActivity extends Activity { + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + + final Intent myIntent = new Intent(DemoMainActivity.this, DemoAndroidHarness.class); + + //Next create the bundle and initialize it + final Bundle bundle = new Bundle(); + + + final Spinner spinnerConfig = (Spinner) findViewById(R.id.spinnerConfig); + ArrayAdapter adapterDropDownConfig = ArrayAdapter.createFromResource( + this, R.array.eglconfig_array, android.R.layout.simple_spinner_item); + adapterDropDownConfig.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnerConfig.setAdapter(adapterDropDownConfig); + + + spinnerConfig.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView parent, + View view, int pos, long id) { + Toast.makeText(parent.getContext(), "Set EGLConfig " + + parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show(); + //Add the parameters to bundle as + bundle.putString("EGLCONFIG", parent.getItemAtPosition(pos).toString()); + } + + public void onNothingSelected(AdapterView parent) { + // Do nothing. + } + }); + + + final Spinner spinnerLogging = (Spinner) findViewById(R.id.spinnerLogging); + ArrayAdapter adapterDropDownLogging = ArrayAdapter.createFromResource( + this, R.array.logging_array, android.R.layout.simple_spinner_item); + adapterDropDownLogging.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnerLogging.setAdapter(adapterDropDownLogging); + + + spinnerLogging.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView parent, + View view, int pos, long id) { + Toast.makeText(parent.getContext(), "Set Logging " + + parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show(); + + //Add the parameters to bundle as + bundle.putBoolean("VERBOSE", parent.getItemAtPosition(pos).toString().equals("Verbose")); + } + + public void onNothingSelected(AdapterView parent) { + // Do nothing. + } + }); + + + ListView list = (ListView) findViewById(R.id.ListView01); + list.setClickable(true); + + final List listDemos = new ArrayList(); + + listDemos.add(new DemoLaunchEntry("jme3test.android.SimpleTexturedTest", "An field of textured boxes rotating")); + listDemos.add(new DemoLaunchEntry("jme3test.android.TestSkyLoadingLagoon", "Sky box demonstration with jpg")); + listDemos.add(new DemoLaunchEntry("jme3test.android.TestSkyLoadingPrimitives", "Sky box demonstration with png")); + listDemos.add(new DemoLaunchEntry("jme3test.android.TestBumpModel", "Shows a bump mapped well with a moving light")); + listDemos.add(new DemoLaunchEntry("jme3test.android.TestNormalMapping", "Shows a normal mapped sphere")); + listDemos.add(new DemoLaunchEntry("jme3test.android.TestUnshadedModel", "Shows an unshaded model of the sphere")); + listDemos.add(new DemoLaunchEntry("jme3test.android.TestMovingParticle", "Demonstrates particle effects")); + listDemos.add(new DemoLaunchEntry("jme3test.android.TestAmbient", "Positional sound - You sit in a dark cave under a waterfall")); + + //listDemos.add(new DemoLaunchEntry("jme3test.effect.TestParticleEmitter", "")); + //listDemos.add(new DemoLaunchEntry("jme3test.effect.TestPointSprite", "")); + //listDemos.add(new DemoLaunchEntry("jme3test.light.TestLightRadius", "")); + listDemos.add(new DemoLaunchEntry("jme3test.android.TestMotionPath", "Shows cinematics - see a teapot on its journey - model loading needs a long time - just let it load, looks like freezed")); + //listDemos.add(new DemoLaunchEntry("com.jme3.androiddemo.TestSimpleWater", "Post processors - not working correctly due to missing framebuffer support, looks interresting :)")); + //listDemos.add(new DemoLaunchEntry("jme3test.model.TestHoverTank", "")); + //listDemos.add(new DemoLaunchEntry("jme3test.niftygui.TestNiftyGui", "")); + //listDemos.add(new DemoLaunchEntry("com.jme3.androiddemo.TestNiftyGui", "")); + + + DemoLaunchAdapter adapterList = new DemoLaunchAdapter(this, listDemos); + + list.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView arg0, View view, int position, long index) { + System.out.println("onItemClick"); + showToast(listDemos.get(position).getName()); + + + //Add the parameters to bundle as + bundle.putString("APPCLASSNAME", listDemos.get(position).getName()); + + //Add this bundle to the intent + myIntent.putExtras(bundle); + + //Start the JME3 app harness activity + DemoMainActivity.this.startActivity(myIntent); + + } + }); + + list.setAdapter(adapterList); + } + + private void showToast(String message) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + } +} + diff --git a/jme3-android/src/main/java/jme3test/android/R.java b/jme3-android/src/main/java/jme3test/android/R.java new file mode 100644 index 000000000..ffb1f3702 --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/R.java @@ -0,0 +1,46 @@ +/* AUTO-GENERATED FILE. DO NOT MODIFY. + * + * This class was automatically generated by the + * aapt tool from the resource data it found. It + * should not be modified by hand. + */ + +package jme3test.android; + +public final class R { + public static final class array { + public static final int eglconfig_array=0x7f060000; + public static final int logging_array=0x7f060001; + } + public static final class attr { + } + public static final class drawable { + public static final int icon=0x7f020000; + } + public static final class id { + public static final int LinearLayout01=0x7f070000; + public static final int LinearLayout02=0x7f070002; + public static final int ListView01=0x7f070009; + public static final int TextView01=0x7f070003; + public static final int spinnerConfig=0x7f070006; + public static final int spinnerLogging=0x7f070008; + public static final int tvConfig=0x7f070005; + public static final int tvDemoName=0x7f070001; + public static final int tvDescription=0x7f070004; + public static final int tvLogging=0x7f070007; + } + public static final class layout { + public static final int demo_row=0x7f030000; + public static final int main=0x7f030001; + } + public static final class raw { + public static final int oddbounce=0x7f040000; + } + public static final class string { + public static final int app_name=0x7f050000; + public static final int eglconfig_prompt=0x7f050001; + public static final int eglconfig_text=0x7f050002; + public static final int logging_prompt=0x7f050003; + public static final int logging_text=0x7f050004; + } +} diff --git a/jme3-android/src/main/java/jme3test/android/SimpleSoundTest.java b/jme3-android/src/main/java/jme3test/android/SimpleSoundTest.java new file mode 100644 index 000000000..9503bca0e --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/SimpleSoundTest.java @@ -0,0 +1,40 @@ +package jme3test.android; + +import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioNode; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.InputListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.math.Vector3f; + +public class SimpleSoundTest extends SimpleApplication implements InputListener { + + private AudioNode gun; + private AudioNode nature; + + @Override + public void simpleInitApp() { + gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav"); + gun.setPositional(true); + gun.setLocalTranslation(new Vector3f(0, 0, 0)); + gun.setMaxDistance(100); + gun.setRefDistance(5); + + nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", true); + nature.setVolume(3); + nature.setLooping(true); + nature.play(); + + inputManager.addMapping("click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(this, "click"); + + rootNode.attachChild(gun); + rootNode.attachChild(nature); + } + + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("click") && isPressed) { + gun.playInstance(); + } + } +} diff --git a/jme3-android/src/main/java/jme3test/android/SimpleTexturedTest.java b/jme3-android/src/main/java/jme3test/android/SimpleTexturedTest.java new file mode 100644 index 000000000..bfc106b17 --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/SimpleTexturedTest.java @@ -0,0 +1,150 @@ + +/* + * Android 2.2+ SimpleTextured test. + * + * created: Mon Nov 8 00:08:22 EST 2010 + */ + +package jme3test.android; + + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.texture.Texture; +import com.jme3.util.TangentBinormalGenerator; + + +public class SimpleTexturedTest extends SimpleApplication { + + private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(SimpleTexturedTest.class.getName()); + + + private Node spheresContainer = new Node("spheres-container"); + + + private boolean lightingEnabled = true; + private boolean texturedEnabled = true; + private boolean spheres = true; + + @Override + public void simpleInitApp() { + + //flyCam.setRotationSpeed(0.01f); + + + Mesh shapeSphere = null; + Mesh shapeBox = null; + + + shapeSphere = new Sphere(16, 16, .5f); + shapeBox = new Box(Vector3f.ZERO, 0.3f, 0.3f, 0.3f); + + + // ModelConverter.optimize(geom); + + Texture texture = assetManager.loadTexture(new TextureKey("Interface/Logo/Monkey.jpg")); + Texture textureMonkey = assetManager.loadTexture(new TextureKey("Interface/Logo/Monkey.jpg")); + + Material material = null; + Material materialMonkey = null; + + if (texturedEnabled) { + if (lightingEnabled) { + material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + material.setBoolean("VertexLighting", true); + material.setFloat("Shininess", 127); + material.setBoolean("LowQuality", true); + material.setTexture("DiffuseMap", texture); + + materialMonkey = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + materialMonkey.setBoolean("VertexLighting", true); + materialMonkey.setFloat("Shininess", 127); + materialMonkey.setBoolean("LowQuality", true); + materialMonkey.setTexture("DiffuseMap", textureMonkey); + + } else { + material = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.j3md"); + material.setTexture("ColorMap", texture); + + materialMonkey = new Material(assetManager, "Common/MatDefs/Misc/SimpleTextured.j3md"); + materialMonkey.setTexture("ColorMap", textureMonkey); + } + } else { + material = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md"); + material.setColor("Color", ColorRGBA.Red); + materialMonkey = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md"); + materialMonkey.setColor("Color", ColorRGBA.Red); + } + + TangentBinormalGenerator.generate(shapeSphere); + TangentBinormalGenerator.generate(shapeBox); + + int iFlipper = 0; + for (int y = -1; y < 2; y++) { + for (int x = -1; x < 2; x++){ + Geometry geomClone = null; + + //iFlipper++; + if (iFlipper % 2 == 0) + { + geomClone = new Geometry("geometry-" + y + "-" + x, shapeBox); + } + else + { + geomClone = new Geometry("geometry-" + y + "-" + x, shapeSphere); + } + if (iFlipper % 3 == 0) + { + geomClone.setMaterial(materialMonkey); + } + else + { + geomClone.setMaterial(material); + } + geomClone.setLocalTranslation(x, y, 0); + +// Transform t = geom.getLocalTransform().clone(); +// Transform t2 = geomClone.getLocalTransform().clone(); +// t.combineWithParent(t2); +// geomClone.setLocalTransform(t); + + spheresContainer.attachChild(geomClone); + } + } + + spheresContainer.setLocalTranslation(new Vector3f(0, 0, -4f)); + spheresContainer.setLocalScale(2.0f); + + rootNode.attachChild(spheresContainer); + + PointLight pointLight = new PointLight(); + + pointLight.setColor(new ColorRGBA(0.7f, 0.7f, 1.0f, 1.0f)); + + pointLight.setPosition(new Vector3f(0f, 0f, 0f)); + pointLight.setRadius(8); + + rootNode.addLight(pointLight); + } + + @Override + public void simpleUpdate(float tpf) { + + // secondCounter has been removed from SimpleApplication + //if (secondCounter == 0) + // logger.fine("Frames per second: " + timer.getFrameRate()); + + spheresContainer.rotate(0.2f * tpf, 0.4f * tpf, 0.8f * tpf); + } + +} + diff --git a/jme3-android/src/main/java/jme3test/android/TestAmbient.java b/jme3-android/src/main/java/jme3test/android/TestAmbient.java new file mode 100644 index 000000000..5164b67df --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/TestAmbient.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.android; + +import android.media.SoundPool; +import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioNode; +import com.jme3.math.Vector3f; + +public class TestAmbient extends SimpleApplication { + + private AudioNode footsteps, beep; + private AudioNode nature, waves; + + SoundPool soundPool; + +// private PointAudioSource waves; + private float time = 0; + private float nextTime = 1; + + public static void main(String[] args){ + TestAmbient test = new TestAmbient(); + test.start(); + } + + + @Override + public void simpleInitApp() + { + /* + footsteps = new AudioNode(audioRenderer, assetManager, "Sound/Effects/Foot steps.ogg", true); + + footsteps.setPositional(true); + footsteps.setLocalTranslation(new Vector3f(4, -1, 30)); + footsteps.setMaxDistance(5); + footsteps.setRefDistance(1); + footsteps.setLooping(true); + + beep = new AudioNode(audioRenderer, assetManager, "Sound/Effects/Beep.ogg", true); + beep.setVolume(3); + beep.setLooping(true); + + audioRenderer.playSourceInstance(footsteps); + audioRenderer.playSource(beep); + */ + + waves = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", true); + waves.setPositional(true); + + nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", true); + + waves.setLocalTranslation(new Vector3f(4, -1, 30)); + waves.setMaxDistance(5); + waves.setRefDistance(1); + + nature.setVolume(3); + audioRenderer.playSourceInstance(waves); + audioRenderer.playSource(nature); + } + + @Override + public void simpleUpdate(float tpf) + { + + } + +} diff --git a/jme3-android/src/main/java/jme3test/android/TestBumpModel.java b/jme3-android/src/main/java/jme3test/android/TestBumpModel.java new file mode 100644 index 000000000..c85e3a58e --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/TestBumpModel.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.android; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.ogre.OgreMeshKey; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.TangentBinormalGenerator; + +public class TestBumpModel extends SimpleApplication { + + float angle; + PointLight pl; + Spatial lightMdl; + + public static void main(String[] args){ + TestBumpModel app = new TestBumpModel(); + app.start(); + } + + @Override + public void simpleInitApp() { + Spatial signpost = (Spatial) assetManager.loadAsset(new OgreMeshKey("Models/Sign Post/Sign Post.mesh.xml")); + signpost.setMaterial( (Material) assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m")); + TangentBinormalGenerator.generate(signpost); + rootNode.attachChild(signpost); + + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial( (Material) assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + rootNode.attachChild(lightMdl); + + // flourescent main light + pl = new PointLight(); + pl.setColor(new ColorRGBA(0.88f, 0.92f, 0.95f, 1.0f)); + rootNode.addLight(pl); + + AmbientLight al = new AmbientLight(); + al.setColor(new ColorRGBA(0.44f, 0.40f, 0.20f, 1.0f)); + rootNode.addLight(al); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(1,-1,-1).normalizeLocal()); + dl.setColor(new ColorRGBA(0.92f, 0.85f, 0.8f, 1.0f)); + rootNode.addLight(dl); + } + + @Override + public void simpleUpdate(float tpf){ + angle += tpf * 0.25f; + angle %= FastMath.TWO_PI; + + pl.setPosition(new Vector3f(FastMath.cos(angle) * 6f, 3f, FastMath.sin(angle) * 6f)); + lightMdl.setLocalTranslation(pl.getPosition()); + } + +} diff --git a/jme3-android/src/main/java/jme3test/android/TestMovingParticle.java b/jme3-android/src/main/java/jme3test/android/TestMovingParticle.java new file mode 100644 index 000000000..b6b7922c7 --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/TestMovingParticle.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.android; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; + +/** + * Particle that moves in a circle. + * + * @author Kirill Vainer + */ +public class TestMovingParticle extends SimpleApplication { + + private ParticleEmitter emit; + private float angle = 0; + + public static void main(String[] args) { + TestMovingParticle app = new TestMovingParticle(); + app.start(); + } + + @Override + public void simpleInitApp() { + emit = new ParticleEmitter("Emitter", Type.Triangle, 300); + emit.setGravity(0, 0, 0); + emit.setVelocityVariation(1); + emit.setLowLife(1); + emit.setHighLife(1); + emit.setInitialVelocity(new Vector3f(0, .5f, 0)); + emit.setImagesX(15); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png")); + emit.setMaterial(mat); + + rootNode.attachChild(emit); + + AmbientLight al = new AmbientLight(); + al.setColor(new ColorRGBA(0.84f, 0.80f, 0.80f, 1.0f)); + rootNode.addLight(al); + + + + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if ("setNum".equals(name) && isPressed) { + emit.setNumParticles(1000); + } + } + }, "setNum"); + + inputManager.addMapping("setNum", new KeyTrigger(KeyInput.KEY_SPACE)); + } + + @Override + public void simpleUpdate(float tpf) { + angle += tpf; + angle %= FastMath.TWO_PI; + float x = FastMath.cos(angle) * 2; + float y = FastMath.sin(angle) * 2; + emit.setLocalTranslation(x, 0, y); + } +} diff --git a/jme3-android/src/main/java/jme3test/android/TestNormalMapping.java b/jme3-android/src/main/java/jme3test/android/TestNormalMapping.java new file mode 100644 index 000000000..7538625f1 --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/TestNormalMapping.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.android; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.TangentBinormalGenerator; + +public class TestNormalMapping extends SimpleApplication { + + float angle; + PointLight pl; + Spatial lightMdl; + + public static void main(String[] args){ + TestNormalMapping app = new TestNormalMapping(); + app.start(); + } + + @Override + public void simpleInitApp() { + Sphere sphMesh = new Sphere(32, 32, 1); + sphMesh.setTextureMode(Sphere.TextureMode.Projected); + sphMesh.updateGeometry(32, 32, 1, false, false); + TangentBinormalGenerator.generate(sphMesh); + + Geometry sphere = new Geometry("Rock Ball", sphMesh); + Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); + sphere.setMaterial(mat); + rootNode.attachChild(sphere); + + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + rootNode.attachChild(lightMdl); + + pl = new PointLight(); + pl.setColor(ColorRGBA.White); + pl.setPosition(new Vector3f(0f, 0f, 4f)); + rootNode.addLight(pl); + + AmbientLight al = new AmbientLight(); + al.setColor(new ColorRGBA(0.44f, 0.40f, 0.20f, 1.0f)); + rootNode.addLight(al); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(1,-1,-1).normalizeLocal()); + dl.setColor(new ColorRGBA(0.92f, 0.85f, 0.8f, 1.0f)); + rootNode.addLight(dl); + } + + @Override + public void simpleUpdate(float tpf){ + angle += tpf * 0.25f; + angle %= FastMath.TWO_PI; + + pl.setPosition(new Vector3f(FastMath.cos(angle) * 4f, 0.5f, FastMath.sin(angle) * 4f)); + lightMdl.setLocalTranslation(pl.getPosition()); + } + +} diff --git a/jme3-android/src/main/java/jme3test/android/TestSkyLoadingLagoon.java b/jme3-android/src/main/java/jme3test/android/TestSkyLoadingLagoon.java new file mode 100644 index 000000000..33aca8b30 --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/TestSkyLoadingLagoon.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.android; + +import com.jme3.app.SimpleApplication; +import com.jme3.scene.Spatial; +import com.jme3.texture.Texture; +import com.jme3.util.SkyFactory; + +public class TestSkyLoadingLagoon extends SimpleApplication { + + public static void main(String[] args){ + TestSkyLoadingLagoon app = new TestSkyLoadingLagoon(); + app.start(); + } + + public void simpleInitApp() { + + Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg"); + Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg"); + Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg"); + Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg"); + Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg"); + Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg"); + + + /* + Texture west = assetManager.loadTexture("Textures/Sky/Primitives/primitives_positive_x.png"); + Texture east = assetManager.loadTexture("Textures/Sky/Primitives/primitives_negative_x.png"); + Texture north = assetManager.loadTexture("Textures/Sky/Primitives/primitives_negative_z.png"); + Texture south = assetManager.loadTexture("Textures/Sky/Primitives/primitives_positive_z.png"); + Texture up = assetManager.loadTexture("Textures/Sky/Primitives/primitives_positive_y.png"); + Texture down = assetManager.loadTexture("Textures/Sky/Primitives/primitives_negative_y.png"); + */ + + Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down); + rootNode.attachChild(sky); + } + +} diff --git a/jme3-android/src/main/java/jme3test/android/TestSkyLoadingPrimitives.java b/jme3-android/src/main/java/jme3test/android/TestSkyLoadingPrimitives.java new file mode 100644 index 000000000..808b0e3d6 --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/TestSkyLoadingPrimitives.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.android; + +import com.jme3.app.SimpleApplication; +import com.jme3.scene.Spatial; +import com.jme3.texture.Texture; +import com.jme3.util.SkyFactory; + +public class TestSkyLoadingPrimitives extends SimpleApplication { + + public static void main(String[] args){ + TestSkyLoadingPrimitives app = new TestSkyLoadingPrimitives(); + app.start(); + } + + public void simpleInitApp() { + /* + Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg"); + Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg"); + Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg"); + Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg"); + Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg"); + Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg"); + */ + + Texture west = assetManager.loadTexture("Textures/Sky/Primitives/primitives_positive_x.png"); + Texture east = assetManager.loadTexture("Textures/Sky/Primitives/primitives_negative_x.png"); + Texture north = assetManager.loadTexture("Textures/Sky/Primitives/primitives_negative_z.png"); + Texture south = assetManager.loadTexture("Textures/Sky/Primitives/primitives_positive_z.png"); + Texture up = assetManager.loadTexture("Textures/Sky/Primitives/primitives_positive_y.png"); + Texture down = assetManager.loadTexture("Textures/Sky/Primitives/primitives_negative_y.png"); + + Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down); + rootNode.attachChild(sky); + } + +} diff --git a/jme3-android/src/main/java/jme3test/android/TestUnshadedModel.java b/jme3-android/src/main/java/jme3test/android/TestUnshadedModel.java new file mode 100644 index 000000000..4e4ff8c09 --- /dev/null +++ b/jme3-android/src/main/java/jme3test/android/TestUnshadedModel.java @@ -0,0 +1,44 @@ +package jme3test.android; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.TangentBinormalGenerator; + +public class TestUnshadedModel extends SimpleApplication { + + public static void main(String[] args){ + TestUnshadedModel app = new TestUnshadedModel(); + app.start(); + } + + @Override + public void simpleInitApp() { + Sphere sphMesh = new Sphere(32, 32, 1); + sphMesh.setTextureMode(Sphere.TextureMode.Projected); + sphMesh.updateGeometry(32, 32, 1, false, false); + TangentBinormalGenerator.generate(sphMesh); + + Geometry sphere = new Geometry("Rock Ball", sphMesh); + Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); + mat.setColor("Ambient", ColorRGBA.DarkGray); + mat.setColor("Diffuse", ColorRGBA.White); + mat.setBoolean("UseMaterialColors", true); + sphere.setMaterial(mat); + rootNode.attachChild(sphere); + + PointLight pl = new PointLight(); + pl.setColor(ColorRGBA.White); + pl.setPosition(new Vector3f(4f, 0f, 0f)); + rootNode.addLight(pl); + + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White); + rootNode.addLight(al); + } +} diff --git a/jme3-android/src/main/resources/res/layout/about.xml b/jme3-android/src/main/resources/res/layout/about.xml new file mode 100644 index 000000000..ca249e7be --- /dev/null +++ b/jme3-android/src/main/resources/res/layout/about.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/jme3-android/src/main/resources/res/layout/tests.xml b/jme3-android/src/main/resources/res/layout/tests.xml new file mode 100644 index 000000000..f38e2336e --- /dev/null +++ b/jme3-android/src/main/resources/res/layout/tests.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/jme3-android/src/main/resources/res/menu/options.xml b/jme3-android/src/main/resources/res/menu/options.xml new file mode 100644 index 000000000..8efec52be --- /dev/null +++ b/jme3-android/src/main/resources/res/menu/options.xml @@ -0,0 +1,12 @@ + +

+ + + + diff --git a/jme3-android/src/main/resources/res/values/strings.xml b/jme3-android/src/main/resources/res/values/strings.xml new file mode 100644 index 000000000..92d63fa9e --- /dev/null +++ b/jme3-android/src/main/resources/res/values/strings.xml @@ -0,0 +1,6 @@ + + + JMEAndroidTest + About + Quit + diff --git a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java new file mode 100644 index 000000000..dd153e393 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java @@ -0,0 +1,909 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; + +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; +import com.jme3.collision.UnsupportedCollisionException; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.material.RenderState.FaceCullMode; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.CameraNode; +import com.jme3.scene.LightNode; +import com.jme3.scene.Node; +import com.jme3.scene.SceneGraphVisitor; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.animations.AnimationData; +import com.jme3.texture.Texture; + +/** + * Blender key. Contains path of the blender file and its loading properties. + * @author Marcin Roguski (Kaelthas) + */ +public class BlenderKey extends ModelKey { + + protected static final int DEFAULT_FPS = 25; + /** + * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time + * between the frames. + */ + protected int fps = DEFAULT_FPS; + /** + * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded. + */ + protected int featuresToLoad = FeaturesToLoad.ALL; + /** This variable determines if assets that are not linked to the objects should be loaded. */ + protected boolean loadUnlinkedAssets; + /** The root path for all the assets. */ + protected String assetRootPath; + /** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */ + protected boolean fixUpAxis = true; + /** Generated textures resolution (PPU - Pixels Per Unit). */ + protected int generatedTexturePPU = 128; + /** + * The name of world settings that the importer will use. If not set or specified name does not occur in the file + * then the first world settings in the file will be used. + */ + protected String usedWorld; + /** + * User's default material that is set fo objects that have no material definition in blender. The default value is + * null. If the value is null the importer will use its own default material (gray color - like in blender). + */ + protected Material defaultMaterial; + /** Face cull mode. By default it is disabled. */ + protected FaceCullMode faceCullMode = FaceCullMode.Back; + /** + * Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded. + * If set to -1 then the current layer will be loaded. + */ + protected int layersToLoad = -1; + /** A variable that toggles the object custom properties loading. */ + protected boolean loadObjectProperties = true; + /** + * Maximum texture size. Might be dependant on the graphic card. + * This value is taken from org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE. + */ + protected int maxTextureSize = 8192; + /** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */ + protected boolean loadGeneratedTextures; + /** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */ + protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED; + /** + * If the sky has only generated textures applied then they will have the following size (both width and height). If 2d textures are used then the generated + * textures will get their proper size. + */ + protected int skyGeneratedTextureSize = 1000; + /** The radius of a shape that will be used while creating the generated texture for the sky. The higher it is the larger part of the texture will be seen. */ + protected float skyGeneratedTextureRadius = 1; + /** The shape against which the generated texture for the sky will be created. */ + protected SkyGeneratedTextureShape skyGeneratedTextureShape = SkyGeneratedTextureShape.SPHERE; + /** + * This field tells if the importer should optimise the use of textures or not. If set to true, then textures of the same mapping type will be merged together + * and textures that in the final result will never be visible - will be discarded. + */ + protected boolean optimiseTextures; + + /** + * Constructor used by serialization mechanisms. + */ + public BlenderKey() { + } + + /** + * Constructor. Creates a key for the given file name. + * @param name + * the name (path) of a file + */ + public BlenderKey(String name) { + super(name); + } + + /** + * This method returns frames per second amount. The default value is BlenderKey.DEFAULT_FPS = 25. + * @return the frames per second amount + */ + public int getFps() { + return fps; + } + + /** + * This method sets frames per second amount. + * @param fps + * the frames per second amount + */ + public void setFps(int fps) { + this.fps = fps; + } + + /** + * This method returns the face cull mode. + * @return the face cull mode + */ + public FaceCullMode getFaceCullMode() { + return faceCullMode; + } + + /** + * This method sets the face cull mode. + * @param faceCullMode + * the face cull mode + */ + public void setFaceCullMode(FaceCullMode faceCullMode) { + this.faceCullMode = faceCullMode; + } + + /** + * This method sets layers to be loaded. + * @param layersToLoad + * layers to be loaded + */ + public void setLayersToLoad(int layersToLoad) { + this.layersToLoad = layersToLoad; + } + + /** + * This method returns layers to be loaded. + * @return layers to be loaded + */ + public int getLayersToLoad() { + return layersToLoad; + } + + /** + * This method sets the properies loading policy. + * By default the value is true. + * @param loadObjectProperties + * true to load properties and false to suspend their loading + */ + public void setLoadObjectProperties(boolean loadObjectProperties) { + this.loadObjectProperties = loadObjectProperties; + } + + /** + * @return the current properties loading properties + */ + public boolean isLoadObjectProperties() { + return loadObjectProperties; + } + + /** + * The default value for this parameter is the same as defined by: org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE. + * If by any means this is too large for user's hardware configuration use the 'setMaxTextureSize' method to change that. + * @return maximum texture size (width/height) + */ + public int getMaxTextureSize() { + return maxTextureSize; + } + + /** + * This method sets the maximum texture size. + * @param maxTextureSize + * the maximum texture size + */ + public void setMaxTextureSize(int maxTextureSize) { + this.maxTextureSize = maxTextureSize; + } + + /** + * This method sets the flag that toggles the generated textures loading. + * @param loadGeneratedTextures + * true if generated textures should be loaded and false otherwise + */ + public void setLoadGeneratedTextures(boolean loadGeneratedTextures) { + this.loadGeneratedTextures = loadGeneratedTextures; + } + + /** + * @return tells if the generated textures should be loaded (false is the default value) + */ + public boolean isLoadGeneratedTextures() { + return loadGeneratedTextures; + } + + /** + * This method sets the asset root path. + * @param assetRootPath + * the assets root path + */ + public void setAssetRootPath(String assetRootPath) { + this.assetRootPath = assetRootPath; + } + + /** + * This method returns the asset root path. + * @return the asset root path + */ + public String getAssetRootPath() { + return assetRootPath; + } + + /** + * This method adds features to be loaded. + * @param featuresToLoad + * bitwise flag of FeaturesToLoad interface values + */ + public void includeInLoading(int featuresToLoad) { + this.featuresToLoad |= featuresToLoad; + } + + /** + * This method removes features from being loaded. + * @param featuresNotToLoad + * bitwise flag of FeaturesToLoad interface values + */ + public void excludeFromLoading(int featuresNotToLoad) { + featuresToLoad &= ~featuresNotToLoad; + } + + public boolean shouldLoad(int featureToLoad) { + return (featuresToLoad & featureToLoad) != 0; + } + + /** + * This method returns bitwise value of FeaturesToLoad interface value. It describes features that will be loaded by + * the blender file loader. + * @return features that will be loaded by the blender file loader + */ + public int getFeaturesToLoad() { + return featuresToLoad; + } + + /** + * This method determines if unlinked assets should be loaded. + * If not then only objects on selected layers will be loaded and their assets if required. + * If yes then all assets will be loaded even if they are on inactive layers or are not linked + * to anything. + * @return true if unlinked assets should be loaded and false otherwise + */ + public boolean isLoadUnlinkedAssets() { + return loadUnlinkedAssets; + } + + /** + * This method sets if unlinked assets should be loaded. + * If not then only objects on selected layers will be loaded and their assets if required. + * If yes then all assets will be loaded even if they are on inactive layers or are not linked + * to anything. + * @param loadUnlinkedAssets + * true if unlinked assets should be loaded and false otherwise + */ + public void setLoadUnlinkedAssets(boolean loadUnlinkedAssets) { + this.loadUnlinkedAssets = loadUnlinkedAssets; + } + + /** + * This method creates an object where loading results will be stores. Only those features will be allowed to store + * that were specified by features-to-load flag. + * @return an object to store loading results + */ + public LoadingResults prepareLoadingResults() { + return new LoadingResults(featuresToLoad); + } + + /** + * This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y + * is up axis. + * @param fixUpAxis + * the up axis state variable + */ + public void setFixUpAxis(boolean fixUpAxis) { + this.fixUpAxis = fixUpAxis; + } + + /** + * This method returns the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By + * default Y is up axis. + * @return the up axis state variable + */ + public boolean isFixUpAxis() { + return fixUpAxis; + } + + /** + * This method sets the generated textures resolution. + * @param generatedTexturePPU + * the generated textures resolution + */ + public void setGeneratedTexturePPU(int generatedTexturePPU) { + this.generatedTexturePPU = generatedTexturePPU; + } + + /** + * @return the generated textures resolution + */ + public int getGeneratedTexturePPU() { + return generatedTexturePPU; + } + + /** + * @return mipmaps generation method + */ + public MipmapGenerationMethod getMipmapGenerationMethod() { + return mipmapGenerationMethod; + } + + /** + * @param mipmapGenerationMethod + * mipmaps generation method + */ + public void setMipmapGenerationMethod(MipmapGenerationMethod mipmapGenerationMethod) { + this.mipmapGenerationMethod = mipmapGenerationMethod; + } + + /** + * @return the size of the generated textures for the sky (used if no flat textures are applied) + */ + public int getSkyGeneratedTextureSize() { + return skyGeneratedTextureSize; + } + + /** + * @param skyGeneratedTextureSize + * the size of the generated textures for the sky (used if no flat textures are applied) + */ + public void setSkyGeneratedTextureSize(int skyGeneratedTextureSize) { + if (skyGeneratedTextureSize <= 0) { + throw new IllegalArgumentException("The texture size must be a positive value (the value given as a parameter: " + skyGeneratedTextureSize + ")!"); + } + this.skyGeneratedTextureSize = skyGeneratedTextureSize; + } + + /** + * @return the radius of a shape that will be used while creating the generated texture for the sky, the higher it is the larger part of the texture will be seen + */ + public float getSkyGeneratedTextureRadius() { + return skyGeneratedTextureRadius; + } + + /** + * @param skyGeneratedTextureRadius + * the radius of a shape that will be used while creating the generated texture for the sky, the higher it is the larger part of the texture will be seen + */ + public void setSkyGeneratedTextureRadius(float skyGeneratedTextureRadius) { + this.skyGeneratedTextureRadius = skyGeneratedTextureRadius; + } + + /** + * @return the shape against which the generated texture for the sky will be created (by default it is a sphere). + */ + public SkyGeneratedTextureShape getSkyGeneratedTextureShape() { + return skyGeneratedTextureShape; + } + + /** + * @param skyGeneratedTextureShape + * the shape against which the generated texture for the sky will be created + */ + public void setSkyGeneratedTextureShape(SkyGeneratedTextureShape skyGeneratedTextureShape) { + if (skyGeneratedTextureShape == null) { + throw new IllegalArgumentException("The sky generated shape type cannot be null!"); + } + this.skyGeneratedTextureShape = skyGeneratedTextureShape; + } + + /** + * If set to true, then textures of the same mapping type will be merged together + * and textures that in the final result will never be visible - will be discarded. + * @param optimiseTextures + * the variable that tells if the textures should be optimised or not + */ + public void setOptimiseTextures(boolean optimiseTextures) { + this.optimiseTextures = optimiseTextures; + } + + /** + * @return the variable that tells if the textures should be optimised or not (by default the optimisation is disabled) + */ + public boolean isOptimiseTextures() { + return optimiseTextures; + } + + /** + * This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is + * not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used + * during loading (assumin any exists in the file). + * @param usedWorld + * the name of the WORLD block used during loading + */ + public void setUsedWorld(String usedWorld) { + this.usedWorld = usedWorld; + } + + /** + * This mehtod returns the name of the WORLD data block taht should be used during file loading. + * @return the name of the WORLD block used during loading + */ + public String getUsedWorld() { + return usedWorld; + } + + /** + * This method sets the default material for objects. + * @param defaultMaterial + * the default material + */ + public void setDefaultMaterial(Material defaultMaterial) { + this.defaultMaterial = defaultMaterial; + } + + /** + * This method returns the default material. + * @return the default material + */ + public Material getDefaultMaterial() { + return defaultMaterial; + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule oc = e.getCapsule(this); + oc.write(fps, "fps", DEFAULT_FPS); + oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL); + oc.write(loadUnlinkedAssets, "load-unlinked-assets", false); + oc.write(assetRootPath, "asset-root-path", null); + oc.write(fixUpAxis, "fix-up-axis", true); + oc.write(generatedTexturePPU, "generated-texture-ppu", 128); + oc.write(usedWorld, "used-world", null); + oc.write(defaultMaterial, "default-material", null); + oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off); + oc.write(layersToLoad, "layers-to-load", -1); + oc.write(mipmapGenerationMethod, "mipmap-generation-method", MipmapGenerationMethod.GENERATE_WHEN_NEEDED); + oc.write(skyGeneratedTextureSize, "sky-generated-texture-size", 1000); + oc.write(skyGeneratedTextureRadius, "sky-generated-texture-radius", 1f); + oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE); + oc.write(optimiseTextures, "optimise-textures", false); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule ic = e.getCapsule(this); + fps = ic.readInt("fps", DEFAULT_FPS); + featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL); + loadUnlinkedAssets = ic.readBoolean("load-unlinked-assets", false); + assetRootPath = ic.readString("asset-root-path", null); + fixUpAxis = ic.readBoolean("fix-up-axis", true); + generatedTexturePPU = ic.readInt("generated-texture-ppu", 128); + usedWorld = ic.readString("used-world", null); + defaultMaterial = (Material) ic.readSavable("default-material", null); + faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off); + layersToLoad = ic.readInt("layers-to=load", -1); + mipmapGenerationMethod = ic.readEnum("mipmap-generation-method", MipmapGenerationMethod.class, MipmapGenerationMethod.GENERATE_WHEN_NEEDED); + skyGeneratedTextureSize = ic.readInt("sky-generated-texture-size", 1000); + skyGeneratedTextureRadius = ic.readFloat("sky-generated-texture-radius", 1f); + skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE); + optimiseTextures = ic.readBoolean("optimise-textures", false); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode()); + result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode()); + result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode()); + result = prime * result + featuresToLoad; + result = prime * result + (fixUpAxis ? 1231 : 1237); + result = prime * result + fps; + result = prime * result + generatedTexturePPU; + result = prime * result + layersToLoad; + result = prime * result + (loadGeneratedTextures ? 1231 : 1237); + result = prime * result + (loadObjectProperties ? 1231 : 1237); + result = prime * result + (loadUnlinkedAssets ? 1231 : 1237); + result = prime * result + maxTextureSize; + result = prime * result + (mipmapGenerationMethod == null ? 0 : mipmapGenerationMethod.hashCode()); + result = prime * result + (optimiseTextures ? 1231 : 1237); + result = prime * result + Float.floatToIntBits(skyGeneratedTextureRadius); + result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode()); + result = prime * result + skyGeneratedTextureSize; + result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (this.getClass() != obj.getClass()) { + return false; + } + BlenderKey other = (BlenderKey) obj; + if (assetRootPath == null) { + if (other.assetRootPath != null) { + return false; + } + } else if (!assetRootPath.equals(other.assetRootPath)) { + return false; + } + if (defaultMaterial == null) { + if (other.defaultMaterial != null) { + return false; + } + } else if (!defaultMaterial.equals(other.defaultMaterial)) { + return false; + } + if (faceCullMode != other.faceCullMode) { + return false; + } + if (featuresToLoad != other.featuresToLoad) { + return false; + } + if (fixUpAxis != other.fixUpAxis) { + return false; + } + if (fps != other.fps) { + return false; + } + if (generatedTexturePPU != other.generatedTexturePPU) { + return false; + } + if (layersToLoad != other.layersToLoad) { + return false; + } + if (loadGeneratedTextures != other.loadGeneratedTextures) { + return false; + } + if (loadObjectProperties != other.loadObjectProperties) { + return false; + } + if (loadUnlinkedAssets != other.loadUnlinkedAssets) { + return false; + } + if (maxTextureSize != other.maxTextureSize) { + return false; + } + if (mipmapGenerationMethod != other.mipmapGenerationMethod) { + return false; + } + if (optimiseTextures != other.optimiseTextures) { + return false; + } + if (Float.floatToIntBits(skyGeneratedTextureRadius) != Float.floatToIntBits(other.skyGeneratedTextureRadius)) { + return false; + } + if (skyGeneratedTextureShape != other.skyGeneratedTextureShape) { + return false; + } + if (skyGeneratedTextureSize != other.skyGeneratedTextureSize) { + return false; + } + if (usedWorld == null) { + if (other.usedWorld != null) { + return false; + } + } else if (!usedWorld.equals(other.usedWorld)) { + return false; + } + return true; + } + + /** + * This enum tells the importer if the mipmaps for textures will be generated by jme.
  • NEVER_GENERATE and ALWAYS_GENERATE are quite understandable
  • GENERATE_WHEN_NEEDED is an option that checks if the texture had 'Generate mipmaps' option set in blender, mipmaps are generated only when the option is set + * @author Marcin Roguski (Kaelthas) + */ + public static enum MipmapGenerationMethod { + NEVER_GENERATE, ALWAYS_GENERATE, GENERATE_WHEN_NEEDED; + } + + /** + * This interface describes the features of the scene that are to be loaded. + * @author Marcin Roguski (Kaelthas) + */ + public static interface FeaturesToLoad { + + int SCENES = 0x0000FFFF; + int OBJECTS = 0x0000000B; + int ANIMATIONS = 0x00000004; + int MATERIALS = 0x00000003; + int TEXTURES = 0x00000001; + int CAMERAS = 0x00000020; + int LIGHTS = 0x00000010; + int WORLD = 0x00000040; + int ALL = 0xFFFFFFFF; + } + + /** + * The shape againts which the sky generated texture will be created. + * + * @author Marcin Roguski (Kaelthas) + */ + public static enum SkyGeneratedTextureShape { + CUBE, SPHERE; + } + + /** + * This class holds the loading results according to the given loading flag. + * @author Marcin Roguski (Kaelthas) + */ + public static class LoadingResults extends Spatial { + + /** Bitwise mask of features that are to be loaded. */ + private final int featuresToLoad; + /** The scenes from the file. */ + private List scenes; + /** Objects from all scenes. */ + private List objects; + /** Materials from all objects. */ + private List materials; + /** Textures from all objects. */ + private List textures; + /** Animations of all objects. */ + private List animations; + /** All cameras from the file. */ + private List cameras; + /** All lights from the file. */ + private List lights; + /** Loaded sky. */ + private Spatial sky; + /** + * The background color of the render loaded from the horizon color of the world. If no world is used than the gray color + * is set to default (as in blender editor. + */ + private ColorRGBA backgroundColor = ColorRGBA.Gray; + + /** + * Private constructor prevents users to create an instance of this class from outside the + * @param featuresToLoad + * bitwise mask of features that are to be loaded + * @see FeaturesToLoad FeaturesToLoad + */ + private LoadingResults(int featuresToLoad) { + this.featuresToLoad = featuresToLoad; + if ((featuresToLoad & FeaturesToLoad.SCENES) != 0) { + scenes = new ArrayList(); + } + if ((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) { + objects = new ArrayList(); + if ((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) { + materials = new ArrayList(); + if ((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) { + textures = new ArrayList(); + } + } + if ((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) { + animations = new ArrayList(); + } + } + if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) { + cameras = new ArrayList(); + } + if ((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) { + lights = new ArrayList(); + } + } + + /** + * This method returns a bitwise flag describing what features of the blend file will be included in the result. + * @return bitwise mask of features that are to be loaded + * @see FeaturesToLoad FeaturesToLoad + */ + public int getLoadedFeatures() { + return featuresToLoad; + } + + /** + * This method adds a scene to the result set. + * @param scene + * scene to be added to the result set + */ + public void addScene(Node scene) { + if (scenes != null) { + scenes.add(scene); + } + } + + /** + * This method adds an object to the result set. + * @param object + * object to be added to the result set + */ + public void addObject(Node object) { + if (objects != null) { + objects.add(object); + } + } + + /** + * This method adds a material to the result set. + * @param material + * material to be added to the result set + */ + public void addMaterial(Material material) { + if (materials != null) { + materials.add(material); + } + } + + /** + * This method adds a texture to the result set. + * @param texture + * texture to be added to the result set + */ + public void addTexture(Texture texture) { + if (textures != null) { + textures.add(texture); + } + } + + /** + * This method adds a camera to the result set. + * @param camera + * camera to be added to the result set + */ + public void addCamera(CameraNode camera) { + if (cameras != null) { + cameras.add(camera); + } + } + + /** + * This method adds a light to the result set. + * @param light + * light to be added to the result set + */ + public void addLight(LightNode light) { + if (lights != null) { + lights.add(light); + } + } + + /** + * This method sets the sky of the scene. Only one sky can be set. + * @param sky + * the sky to be set + */ + public void setSky(Spatial sky) { + this.sky = sky; + } + + /** + * @param backgroundColor + * the background color + */ + public void setBackgroundColor(ColorRGBA backgroundColor) { + this.backgroundColor = backgroundColor; + } + + /** + * @return all loaded scenes + */ + public List getScenes() { + return scenes; + } + + /** + * @return all loaded objects + */ + public List getObjects() { + return objects; + } + + /** + * @return all loaded materials + */ + public List getMaterials() { + return materials; + } + + /** + * @return all loaded textures + */ + public List getTextures() { + return textures; + } + + /** + * @return all loaded animations + */ + public List getAnimations() { + return animations; + } + + /** + * @return all loaded cameras + */ + public List getCameras() { + return cameras; + } + + /** + * @return all loaded lights + */ + public List getLights() { + return lights; + } + + /** + * @return the scene's sky + */ + public Spatial getSky() { + return sky; + } + + /** + * @return the background color + */ + public ColorRGBA getBackgroundColor() { + return backgroundColor; + } + + public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException { + return 0; + } + + @Override + public void updateModelBound() { + } + + @Override + public void setModelBound(BoundingVolume modelBound) { + } + + @Override + public int getVertexCount() { + return 0; + } + + @Override + public int getTriangleCount() { + return 0; + } + + @Override + public Spatial deepClone() { + return null; + } + + @Override + public void depthFirstTraversal(SceneGraphVisitor visitor) { + } + + @Override + protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue queue) { + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/asset/GeneratedTextureKey.java b/jme3-blender/src/main/java/com/jme3/asset/GeneratedTextureKey.java new file mode 100644 index 000000000..6bd9017ae --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/asset/GeneratedTextureKey.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.asset; + +/** + * This key is mostly used to distinguish between textures that are loaded from + * the given assets and those being generated automatically. Every generated + * texture will have this kind of key attached. + * + * @author Marcin Roguski (Kaelthas) + */ +public class GeneratedTextureKey extends TextureKey { + + /** + * Constructor. Stores the name. Extension and folder name are empty + * strings. + * + * @param name + * the name of the texture + */ + public GeneratedTextureKey(String name) { + super(name); + } + + @Override + public String getExtension() { + return ""; + } + + @Override + public String getFolder() { + return ""; + } + + @Override + public String toString() { + return "Generated texture [" + name + "]"; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java new file mode 100644 index 000000000..4338d08d4 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender; + +import java.util.Arrays; +import java.util.List; + +import com.jme3.export.Savable; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.objects.Properties; + +/** + * A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can + * hold the state of the calculations. + * @author Marcin Roguski + */ +public abstract class AbstractBlenderHelper { + /** The blender context. */ + protected BlenderContext blenderContext; + /** The version of the blend file. */ + protected final int blenderVersion; + /** This variable indicates if the Y asxis is the UP axis or not. */ + protected boolean fixUpAxis; + /** Quaternion used to rotate data when Y is up axis. */ + protected Quaternion upAxisRotationQuaternion; + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender + * versions. + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public AbstractBlenderHelper(String blenderVersion, BlenderContext blenderContext) { + this.blenderVersion = Integer.parseInt(blenderVersion); + this.blenderContext = blenderContext; + fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis(); + if (fixUpAxis) { + upAxisRotationQuaternion = new Quaternion().fromAngles(-FastMath.HALF_PI, 0, 0); + } + } + + /** + * This method loads the properties if they are available and defined for the structure. + * @param structure + * the structure we read the properties from + * @param blenderContext + * the blender context + * @return loaded properties or null if they are not available + * @throws BlenderFileException + * an exception is thrown when the blend file is somehow corrupted + */ + protected Properties loadProperties(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + Properties properties = null; + Structure id = (Structure) structure.getFieldValue("ID"); + if (id != null) { + Pointer pProperties = (Pointer) id.getFieldValue("properties"); + if (pProperties.isNotNull()) { + Structure propertiesStructure = pProperties.fetchData().get(0); + properties = new Properties(); + properties.load(propertiesStructure, blenderContext); + } + } + return properties; + } + + /** + * The method applies properties to the given spatial. The Properties + * instance cannot be directly applied because the end-user might not have + * the blender plugin jar file and thus receive ClassNotFoundException. The + * values are set by name instead. + * + * @param spatial + * the spatial that is to have properties applied + * @param properties + * the properties to be applied + */ + protected void applyProperties(Spatial spatial, Properties properties) { + List propertyNames = properties.getSubPropertiesNames(); + if (propertyNames != null && propertyNames.size() > 0) { + for (String propertyName : propertyNames) { + Object value = properties.findValue(propertyName); + if (value instanceof Savable || value instanceof Boolean || value instanceof String || value instanceof Float || value instanceof Integer || value instanceof Long) { + spatial.setUserData(propertyName, value); + } else if (value instanceof Double) { + spatial.setUserData(propertyName, ((Double) value).floatValue()); + } else if (value instanceof int[]) { + spatial.setUserData(propertyName, Arrays.toString((int[]) value)); + } else if (value instanceof float[]) { + spatial.setUserData(propertyName, Arrays.toString((float[]) value)); + } else if (value instanceof double[]) { + spatial.setUserData(propertyName, Arrays.toString((double[]) value)); + } + } + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java new file mode 100644 index 000000000..290a578b9 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java @@ -0,0 +1,636 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender; + +import java.util.ArrayList; +import java.util.EmptyStackException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Stack; + +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.asset.AssetManager; +import com.jme3.asset.BlenderKey; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Node; +import com.jme3.scene.plugins.blender.animations.AnimationData; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.Constraint; +import com.jme3.scene.plugins.blender.file.BlenderInputStream; +import com.jme3.scene.plugins.blender.file.DnaBlockData; +import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.meshes.MeshContext; + +/** + * The class that stores temporary data and manages it during loading the belnd + * file. This class is intended to be used in a single loading thread. It holds + * the state of loading operations. + * + * @author Marcin Roguski (Kaelthas) + */ +public class BlenderContext { + /** The blender file version. */ + private int blenderVersion; + /** The blender key. */ + private BlenderKey blenderKey; + /** The header of the file block. */ + private DnaBlockData dnaBlockData; + /** The scene structure. */ + private Structure sceneStructure; + /** The input stream of the blend file. */ + private BlenderInputStream inputStream; + /** The asset manager. */ + private AssetManager assetManager; + /** The blocks read from the file. */ + protected List blocks; + /** + * A map containing the file block headers. The key is the old memory address. + */ + private Map fileBlockHeadersByOma = new HashMap(); + /** A map containing the file block headers. The key is the block code. */ + private Map> fileBlockHeadersByCode = new HashMap>(); + /** + * This map stores the loaded features by their old memory address. The + * first object in the value table is the loaded structure and the second - + * the structure already converted into proper data. + */ + private Map loadedFeatures = new HashMap(); + /** + * This map stores the loaded features by their name. Only features with ID + * structure can be stored here. The first object in the value table is the + * loaded structure and the second - the structure already converted into + * proper data. + */ + private Map loadedFeaturesByName = new HashMap(); + /** A stack that hold the parent structure of currently loaded feature. */ + private Stack parentStack = new Stack(); + /** A list of constraints for the specified object. */ + protected Map> constraints = new HashMap>(); + /** Anim data loaded for features. */ + private Map animData = new HashMap(); + /** Loaded skeletons. */ + private Map skeletons = new HashMap(); + /** A map between skeleton and node it modifies. */ + private Map nodesWithSkeletons = new HashMap(); + /** A map of mesh contexts. */ + protected Map meshContexts = new HashMap(); + /** A map of bone contexts. */ + protected Map boneContexts = new HashMap(); + /** A map og helpers that perform loading. */ + private Map helpers = new HashMap(); + /** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */ + private Map> markers = new HashMap>(); + + /** + * This method sets the blender file version. + * + * @param blenderVersion + * the blender file version + */ + public void setBlenderVersion(String blenderVersion) { + this.blenderVersion = Integer.parseInt(blenderVersion); + } + + /** + * @return the blender file version + */ + public int getBlenderVersion() { + return blenderVersion; + } + + /** + * This method sets the blender key. + * + * @param blenderKey + * the blender key + */ + public void setBlenderKey(BlenderKey blenderKey) { + this.blenderKey = blenderKey; + } + + /** + * This method returns the blender key. + * + * @return the blender key + */ + public BlenderKey getBlenderKey() { + return blenderKey; + } + + /** + * This method sets the dna block data. + * + * @param dnaBlockData + * the dna block data + */ + public void setBlockData(DnaBlockData dnaBlockData) { + this.dnaBlockData = dnaBlockData; + } + + /** + * This method returns the dna block data. + * + * @return the dna block data + */ + public DnaBlockData getDnaBlockData() { + return dnaBlockData; + } + + /** + * This method sets the scene structure data. + * + * @param sceneStructure + * the scene structure data + */ + public void setSceneStructure(Structure sceneStructure) { + this.sceneStructure = sceneStructure; + } + + /** + * This method returns the scene structure data. + * + * @return the scene structure data + */ + public Structure getSceneStructure() { + return sceneStructure; + } + + /** + * This method returns the asset manager. + * + * @return the asset manager + */ + public AssetManager getAssetManager() { + return assetManager; + } + + /** + * This method sets the asset manager. + * + * @param assetManager + * the asset manager + */ + public void setAssetManager(AssetManager assetManager) { + this.assetManager = assetManager; + } + + /** + * This method returns the input stream of the blend file. + * + * @return the input stream of the blend file + */ + public BlenderInputStream getInputStream() { + return inputStream; + } + + /** + * This method sets the input stream of the blend file. + * + * @param inputStream + * the input stream of the blend file + */ + public void setInputStream(BlenderInputStream inputStream) { + this.inputStream = inputStream; + } + + /** + * This method adds a file block header to the map. Its old memory address + * is the key. + * + * @param oldMemoryAddress + * the address of the block header + * @param fileBlockHeader + * the block header to store + */ + public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) { + fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader); + List headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode())); + if (headers == null) { + headers = new ArrayList(); + fileBlockHeadersByCode.put(Integer.valueOf(fileBlockHeader.getCode()), headers); + } + headers.add(fileBlockHeader); + } + + /** + * This method returns the block header of a given memory address. If the + * header is not present then null is returned. + * + * @param oldMemoryAddress + * the address of the block header + * @return loaded header or null if it was not yet loaded + */ + public FileBlockHeader getFileBlock(Long oldMemoryAddress) { + return fileBlockHeadersByOma.get(oldMemoryAddress); + } + + /** + * This method returns a list of file blocks' headers of a specified code. + * + * @param code + * the code of file blocks + * @return a list of file blocks' headers of a specified code + */ + public List getFileBlocks(Integer code) { + return fileBlockHeadersByCode.get(code); + } + + /** + * This method adds a helper instance to the helpers' map. + * + * @param + * the type of the helper + * @param clazz + * helper's class definition + * @param helper + * the helper instance + */ + public void putHelper(Class clazz, AbstractBlenderHelper helper) { + helpers.put(clazz.getSimpleName(), helper); + } + + @SuppressWarnings("unchecked") + public T getHelper(Class clazz) { + return (T) helpers.get(clazz.getSimpleName()); + } + + /** + * This method adds a loaded feature to the map. The key is its unique old + * memory address. + * + * @param oldMemoryAddress + * the address of the feature + * @param featureName + * the name of the feature + * @param structure + * the filled structure of the feature + * @param feature + * the feature we want to store + */ + public void addLoadedFeatures(Long oldMemoryAddress, String featureName, Structure structure, Object feature) { + if (oldMemoryAddress == null || structure == null || feature == null) { + throw new IllegalArgumentException("One of the given arguments is null!"); + } + Object[] storedData = new Object[] { structure, feature }; + loadedFeatures.put(oldMemoryAddress, storedData); + if (featureName != null) { + loadedFeaturesByName.put(featureName, storedData); + } + } + + /** + * This method returns the feature of a given memory address. If the feature + * is not yet loaded then null is returned. + * + * @param oldMemoryAddress + * the address of the feature + * @param loadedFeatureDataType + * the type of data we want to retreive it can be either filled + * structure or already converted feature + * @return loaded feature or null if it was not yet loaded + */ + public Object getLoadedFeature(Long oldMemoryAddress, LoadedFeatureDataType loadedFeatureDataType) { + Object[] result = loadedFeatures.get(oldMemoryAddress); + if (result != null) { + return result[loadedFeatureDataType.getIndex()]; + } + return null; + } + + /** + * This method adds the structure to the parent stack. + * + * @param parent + * the structure to be added to the stack + */ + public void pushParent(Structure parent) { + parentStack.push(parent); + } + + /** + * This method removes the structure from the top of the parent's stack. + * + * @return the structure that was removed from the stack + */ + public Structure popParent() { + try { + return parentStack.pop(); + } catch (EmptyStackException e) { + return null; + } + } + + /** + * This method retreives the structure at the top of the parent's stack but + * does not remove it. + * + * @return the structure from the top of the stack + */ + public Structure peekParent() { + try { + return parentStack.peek(); + } catch (EmptyStackException e) { + return null; + } + } + + /** + * This method adds a new modifier to the list. + * + * @param ownerOMA + * the owner's old memory address + * @param constraints + * the object's constraints + */ + public void addConstraints(Long ownerOMA, List constraints) { + List objectConstraints = this.constraints.get(ownerOMA); + if (objectConstraints == null) { + objectConstraints = new ArrayList(); + this.constraints.put(ownerOMA, objectConstraints); + } + objectConstraints.addAll(constraints); + } + + /** + * Returns constraints applied to the feature of the given OMA. + * @param ownerOMA + * the constraints' owner OMA + * @return a list of constraints or null if no constraints are applied to the feature + */ + public List getConstraints(Long ownerOMA) { + return constraints.get(ownerOMA); + } + + /** + * @return all available constraints + */ + public List getAllConstraints() { + List result = new ArrayList(); + for (Entry> entry : constraints.entrySet()) { + result.addAll(entry.getValue()); + } + return result; + } + + /** + * This method sets the anim data for the specified OMA of its owner. + * + * @param ownerOMA + * the owner's old memory address + * @param animData + * the animation data for the feature specified by ownerOMA + */ + public void setAnimData(Long ownerOMA, AnimationData animData) { + this.animData.put(ownerOMA, animData); + } + + /** + * This method returns the animation data for the specified owner. + * + * @param ownerOMA + * the old memory address of the animation data owner + * @return the animation data or null if none exists + */ + public AnimationData getAnimData(Long ownerOMA) { + return animData.get(ownerOMA); + } + + /** + * This method sets the skeleton for the specified OMA of its owner. + * + * @param skeletonOMA + * the skeleton's old memory address + * @param skeleton + * the skeleton specified by the given OMA + */ + public void setSkeleton(Long skeletonOMA, Skeleton skeleton) { + skeletons.put(skeletonOMA, skeleton); + } + + /** + * The method stores a binding between the skeleton and the proper armature + * node. + * + * @param skeleton + * the skeleton + * @param node + * the armature node + */ + public void setNodeForSkeleton(Skeleton skeleton, Node node) { + nodesWithSkeletons.put(skeleton, node); + } + + /** + * This method returns the armature node that is defined for the skeleton. + * + * @param skeleton + * the skeleton + * @return the armature node that defines the skeleton in blender + */ + public Node getControlledNode(Skeleton skeleton) { + return nodesWithSkeletons.get(skeleton); + } + + /** + * This method returns the skeleton for the specified OMA of its owner. + * + * @param skeletonOMA + * the skeleton's old memory address + * @return the skeleton specified by the given OMA + */ + public Skeleton getSkeleton(Long skeletonOMA) { + return skeletons.get(skeletonOMA); + } + + /** + * This method sets the mesh context for the given mesh old memory address. + * If the context is already set it will be replaced. + * + * @param meshOMA + * the mesh's old memory address + * @param meshContext + * the mesh's context + */ + public void setMeshContext(Long meshOMA, MeshContext meshContext) { + meshContexts.put(meshOMA, meshContext); + } + + /** + * This method returns the mesh context for the given mesh old memory + * address. If no context exists then null is returned. + * + * @param meshOMA + * the mesh's old memory address + * @return mesh's context + */ + public MeshContext getMeshContext(Long meshOMA) { + return meshContexts.get(meshOMA); + } + + /** + * This method sets the bone context for the given bone old memory address. + * If the context is already set it will be replaced. + * + * @param boneOMA + * the bone's old memory address + * @param boneContext + * the bones's context + */ + public void setBoneContext(Long boneOMA, BoneContext boneContext) { + boneContexts.put(boneOMA, boneContext); + } + + /** + * This method returns the bone context for the given bone old memory + * address. If no context exists then null is returned. + * + * @param boneOMA + * the bone's old memory address + * @return bone's context + */ + public BoneContext getBoneContext(Long boneOMA) { + return boneContexts.get(boneOMA); + } + + /** + * Returns bone by given name. + * + * @param skeletonOMA the OMA of the skeleton where the bone will be searched + * @param name + * the name of the bone + * @return found bone or null if none bone of a given name exists + */ + public BoneContext getBoneByName(Long skeletonOMA, String name) { + for (Entry entry : boneContexts.entrySet()) { + if(entry.getValue().getArmatureObjectOMA().equals(skeletonOMA)) { + Bone bone = entry.getValue().getBone(); + if (bone != null && name.equals(bone.getName())) { + return entry.getValue(); + } + } + } + return null; + } + + /** + * Returns bone context for the given bone. + * + * @param bone + * the bone + * @return the bone's bone context + */ + public BoneContext getBoneContext(Bone bone) { + for (Entry entry : boneContexts.entrySet()) { + if (entry.getValue().getBone().equals(bone)) { + return entry.getValue(); + } + } + throw new IllegalStateException("Cannot find context for bone: " + bone); + } + + /** + * This metod returns the default material. + * + * @return the default material + */ + public synchronized Material getDefaultMaterial() { + if (blenderKey.getDefaultMaterial() == null) { + Material defaultMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + defaultMaterial.setColor("Color", ColorRGBA.DarkGray); + blenderKey.setDefaultMaterial(defaultMaterial); + } + return blenderKey.getDefaultMaterial(); + } + + /** + * Adds a custom marker for scene's feature. + * + * @param marker + * the marker name + * @param feature + * te scene's feature (can be node, material or texture or + * anything else) + * @param markerValue + * the marker value + */ + public void addMarker(String marker, Object feature, Object markerValue) { + if (markerValue == null) { + throw new IllegalArgumentException("The marker's value cannot be null."); + } + Map markersMap = markers.get(marker); + if (markersMap == null) { + markersMap = new HashMap(); + markers.put(marker, markersMap); + } + markersMap.put(feature, markerValue); + } + + /** + * Returns the marker value. The returned value is null if no marker was + * defined for the given feature. + * + * @param marker + * the marker name + * @param feature + * the scene's feature + * @return marker value or null if it was not defined + */ + public Object getMarkerValue(String marker, Object feature) { + Map markersMap = markers.get(marker); + return markersMap == null ? null : markersMap.get(feature); + } + + /** + * This enum defines what loaded data type user wants to retreive. It can be + * either filled structure or already converted data. + * + * @author Marcin Roguski (Kaelthas) + */ + public static enum LoadedFeatureDataType { + + LOADED_STRUCTURE(0), LOADED_FEATURE(1); + private int index; + + private LoadedFeatureDataType(int index) { + this.index = index; + } + + public int getIndex() { + return index; + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java new file mode 100644 index 000000000..4b398d166 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.BlenderKey; +import com.jme3.asset.BlenderKey.FeaturesToLoad; +import com.jme3.asset.BlenderKey.LoadingResults; +import com.jme3.asset.ModelKey; +import com.jme3.scene.CameraNode; +import com.jme3.scene.LightNode; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.animations.ArmatureHelper; +import com.jme3.scene.plugins.blender.animations.IpoHelper; +import com.jme3.scene.plugins.blender.cameras.CameraHelper; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; +import com.jme3.scene.plugins.blender.curves.CurvesHelper; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.BlenderInputStream; +import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.landscape.LandscapeHelper; +import com.jme3.scene.plugins.blender.lights.LightHelper; +import com.jme3.scene.plugins.blender.materials.MaterialHelper; +import com.jme3.scene.plugins.blender.meshes.MeshHelper; +import com.jme3.scene.plugins.blender.modifiers.ModifierHelper; +import com.jme3.scene.plugins.blender.objects.ObjectHelper; +import com.jme3.scene.plugins.blender.particles.ParticlesHelper; +import com.jme3.scene.plugins.blender.textures.TextureHelper; + +/** + * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures. + * @author Marcin Roguski (Kaelthas) + */ +public class BlenderLoader implements AssetLoader { + private static final Logger LOGGER = Logger.getLogger(BlenderLoader.class.getName()); + + /** The blocks read from the file. */ + protected List blocks; + /** The blender context. */ + protected BlenderContext blenderContext; + + public Spatial load(AssetInfo assetInfo) throws IOException { + try { + this.setup(assetInfo); + + List sceneBlocks = new ArrayList(); + BlenderKey blenderKey = blenderContext.getBlenderKey(); + LoadingResults loadingResults = blenderKey.prepareLoadingResults(); + for (FileBlockHeader block : blocks) { + switch (block.getCode()) { + case FileBlockHeader.BLOCK_OB00:// Object + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + Object object = objectHelper.toObject(block.getStructure(blenderContext), blenderContext); + if (object instanceof LightNode) { + loadingResults.addLight((LightNode) object); + } else if (object instanceof CameraNode) { + loadingResults.addCamera((CameraNode) object); + } else if (object instanceof Node) { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); + } + if (this.isRootObject(loadingResults, (Node) object)) { + loadingResults.addObject((Node) object); + } + } + break; +// case FileBlockHeader.BLOCK_MA00:// Material +// MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); +// MaterialContext materialContext = materialHelper.toMaterialContext(block.getStructure(blenderContext), blenderContext); +// if (blenderKey.isLoadUnlinkedAssets() && blenderKey.shouldLoad(FeaturesToLoad.MATERIALS)) { +// loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext))); +// } +// break; + case FileBlockHeader.BLOCK_SC00:// Scene + if (blenderKey.shouldLoad(FeaturesToLoad.SCENES)) { + sceneBlocks.add(block); + } + break; + case FileBlockHeader.BLOCK_WO00:// World + if (blenderKey.shouldLoad(FeaturesToLoad.WORLD)) { + Structure worldStructure = block.getStructure(blenderContext); + String worldName = worldStructure.getName(); + if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) { + LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class); + loadingResults.addLight(landscapeHelper.toAmbientLight(worldStructure)); + loadingResults.setSky(landscapeHelper.toSky(worldStructure)); + loadingResults.setBackgroundColor(landscapeHelper.toBackgroundColor(worldStructure)); + } + } + break; + } + } + + // bake constraints after everything is loaded + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + constraintHelper.bakeConstraints(blenderContext); + + // load the scene at the very end so that the root nodes have no parent during loading or constraints applying + for (FileBlockHeader sceneBlock : sceneBlocks) { + loadingResults.addScene(this.toScene(sceneBlock.getStructure(blenderContext))); + } + + return loadingResults; + } catch (BlenderFileException e) { + throw new IOException(e.getLocalizedMessage(), e); + } catch (Exception e) { + throw new IOException("Unexpected importer exception occured: " + e.getLocalizedMessage(), e); + } finally { + this.clear(); + } + } + + /** + * This method indicates if the given spatial is a root object. It means it + * has no parent or is directly attached to one of the already loaded scene + * nodes. + * + * @param loadingResults + * loading results containing the scene nodes + * @param spatial + * spatial object + * @return true if the given spatial is a root object and + * false otherwise + */ + protected boolean isRootObject(LoadingResults loadingResults, Spatial spatial) { + if (spatial.getParent() == null) { + return true; + } + for (Node scene : loadingResults.getScenes()) { + if (spatial.getParent().equals(scene)) { + return true; + } + } + return false; + } + + /** + * This method converts the given structure to a scene node. + * @param structure + * structure of a scene + * @return scene's node + */ + private Node toScene(Structure structure) { + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + Node result = new Node(structure.getName()); + try { + List base = ((Structure) structure.getFieldValue("base")).evaluateListBase(); + for (Structure b : base) { + Pointer pObject = (Pointer) b.getFieldValue("object"); + if (pObject.isNotNull()) { + Structure objectStructure = pObject.fetchData().get(0); + + Object object = objectHelper.toObject(objectStructure, blenderContext); + if (object instanceof LightNode) { + result.addLight(((LightNode) object).getLight()); + result.attachChild((LightNode) object); + } else if (object instanceof Node) { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); + } + if (((Node) object).getParent() == null) { + result.attachChild((Spatial) object); + } + } + } + } + } catch (BlenderFileException e) { + LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); + } + return result; + } + + /** + * This method sets up the loader. + * @param assetInfo + * the asset info + * @throws BlenderFileException + * an exception is throw when something wrong happens with blender file + */ + protected void setup(AssetInfo assetInfo) throws BlenderFileException { + // registering loaders + ModelKey modelKey = (ModelKey) assetInfo.getKey(); + BlenderKey blenderKey; + if (modelKey instanceof BlenderKey) { + blenderKey = (BlenderKey) modelKey; + } else { + blenderKey = new BlenderKey(modelKey.getName()); + blenderKey.setAssetRootPath(modelKey.getFolder()); + } + + // opening stream + BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream()); + + // reading blocks + blocks = new ArrayList(); + FileBlockHeader fileBlock; + blenderContext = new BlenderContext(); + blenderContext.setBlenderVersion(inputStream.getVersionNumber()); + blenderContext.setAssetManager(assetInfo.getManager()); + blenderContext.setInputStream(inputStream); + blenderContext.setBlenderKey(blenderKey); + + // creating helpers + blenderContext.putHelper(ArmatureHelper.class, new ArmatureHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext)); + + // reading the blocks (dna block is automatically saved in the blender context when found) + FileBlockHeader sceneFileBlock = null; + do { + fileBlock = new FileBlockHeader(inputStream, blenderContext); + if (!fileBlock.isDnaBlock()) { + blocks.add(fileBlock); + // save the scene's file block + if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00) { + sceneFileBlock = fileBlock; + } + } + } while (!fileBlock.isLastBlock()); + if (sceneFileBlock != null) { + blenderContext.setSceneStructure(sceneFileBlock.getStructure(blenderContext)); + } + } + + /** + * The internal data is only needed during loading so make it unreachable so that the GC can release + * that memory (which can be quite large amount). + */ + protected void clear() { + blenderContext = null; + blocks = null; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java new file mode 100644 index 000000000..1a856df6e --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.BlenderKey; +import com.jme3.asset.BlenderKey.FeaturesToLoad; +import com.jme3.scene.LightNode; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.objects.ObjectHelper; + +/** + * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures. + * + * @author Marcin Roguski (Kaelthas) + */ +public class BlenderModelLoader extends BlenderLoader { + + private static final Logger LOGGER = Logger.getLogger(BlenderModelLoader.class.getName()); + + @Override + public Spatial load(AssetInfo assetInfo) throws IOException { + try { + this.setup(assetInfo); + + BlenderKey blenderKey = blenderContext.getBlenderKey(); + List rootObjects = new ArrayList(); + for (FileBlockHeader block : blocks) { + if (block.getCode() == FileBlockHeader.BLOCK_OB00) { + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + Object object = objectHelper.toObject(block.getStructure(blenderContext), blenderContext); + if (object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { + rootObjects.add((LightNode) object); + } else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { + LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); + if (((Node) object).getParent() == null) { + rootObjects.add((Node) object); + } + } + } + } + + // bake constraints after everything is loaded + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + constraintHelper.bakeConstraints(blenderContext); + + // attach the nodes to the root node at the very end so that the root objects have no parents during constraint applying + LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene to it."); + Node modelRoot = new Node(blenderKey.getName()); + for (Node node : rootObjects) { + if (node instanceof LightNode) { + modelRoot.addLight(((LightNode) node).getLight()); + } + modelRoot.attachChild(node); + } + + return modelRoot; + } catch (BlenderFileException e) { + throw new IOException(e.getLocalizedMessage(), e); + } catch (Exception e) { + throw new IOException("Unexpected importer exception occured: " + e.getLocalizedMessage(), e); + } finally { + this.clear(); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationData.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationData.java new file mode 100644 index 000000000..05f405fc1 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationData.java @@ -0,0 +1,29 @@ +package com.jme3.scene.plugins.blender.animations; + +import java.util.List; + +import com.jme3.animation.Animation; +import com.jme3.animation.Skeleton; + +/** + * A simple class that sotres animation data. + * If skeleton is null then we deal with object animation. + * + * @author Marcin Roguski (Kaelthas) + */ +public class AnimationData { + /** The skeleton. */ + public final Skeleton skeleton; + /** The animations list. */ + public final List anims; + + public AnimationData(List anims) { + this.anims = anims; + skeleton = null; + } + + public AnimationData(Skeleton skeleton, List anims) { + this.skeleton = skeleton; + this.anims = anims; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java new file mode 100644 index 000000000..2c87d9c0f --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.animations; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.animation.Bone; +import com.jme3.animation.BoneTrack; +import com.jme3.animation.Skeleton; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.curves.BezierCurve; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * This class defines the methods to calculate certain aspects of animation and + * armature functionalities. + * + * @author Marcin Roguski (Kaelthas) + */ +public class ArmatureHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(ArmatureHelper.class.getName()); + + public static final String ARMATURE_NODE_MARKER = "armature-node"; + + /** + * This constructor parses the given blender version and stores the result. + * Some functionalities may differ in different blender versions. + * + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public ArmatureHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + /** + * This method builds the object's bones structure. + * + * @param armatureObjectOMA + * the OMa of the armature node + * @param boneStructure + * the structure containing the bones' data + * @param parent + * the parent bone + * @param result + * the list where the newly created bone will be added + * @param spatialOMA + * the OMA of the spatial that will own the skeleton + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when there is problem with the blender + * file + */ + public void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List result, Long spatialOMA, BlenderContext blenderContext) throws BlenderFileException { + BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, blenderContext); + bc.buildBone(result, spatialOMA, blenderContext); + } + + /** + * This method returns a map where the key is the object's group index that + * is used by a bone and the key is the bone index in the armature. + * + * @param defBaseStructure + * a bPose structure of the object + * @return bone group-to-index map + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public Map getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton) throws BlenderFileException { + Map result = null; + if (skeleton.getBoneCount() != 0) { + result = new HashMap(); + List deformGroups = defBaseStructure.evaluateListBase();// bDeformGroup + int groupIndex = 0; + for (Structure deformGroup : deformGroups) { + String deformGroupName = deformGroup.getFieldValue("name").toString(); + int boneIndex = skeleton.getBoneIndex(deformGroupName); + if (boneIndex >= 0) { + result.put(groupIndex, boneIndex); + } + ++groupIndex; + } + } + return result; + } + + /** + * This method retuns the bone tracks for animation. + * + * @param actionStructure + * the structure containing the tracks + * @param blenderContext + * the blender context + * @return a list of tracks for the specified animation + * @throws BlenderFileException + * an exception is thrown when there are problems with the blend + * file + */ + public BoneTrack[] getTracks(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { + if (blenderVersion < 250) { + return this.getTracks249(actionStructure, skeleton, blenderContext); + } else { + return this.getTracks250(actionStructure, skeleton, blenderContext); + } + } + + /** + * This method retuns the bone tracks for animation for blender version 2.50 + * and higher. + * + * @param actionStructure + * the structure containing the tracks + * @param blenderContext + * the blender context + * @return a list of tracks for the specified animation + * @throws BlenderFileException + * an exception is thrown when there are problems with the blend + * file + */ + private BoneTrack[] getTracks250(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.log(Level.FINE, "Getting tracks!"); + IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); + int fps = blenderContext.getBlenderKey().getFps(); + Structure groups = (Structure) actionStructure.getFieldValue("groups"); + List actionGroups = groups.evaluateListBase();// bActionGroup + List tracks = new ArrayList(); + for (Structure actionGroup : actionGroups) { + String name = actionGroup.getFieldValue("name").toString(); + int boneIndex = skeleton.getBoneIndex(name); + if (boneIndex >= 0) { + List channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase(); + BezierCurve[] bezierCurves = new BezierCurve[channels.size()]; + int channelCounter = 0; + for (Structure c : channels) { + int type = ipoHelper.getCurveType(c, blenderContext); + Pointer pBezTriple = (Pointer) c.getFieldValue("bezt"); + List bezTriples = pBezTriple.fetchData(); + bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2); + } + + Bone bone = skeleton.getBone(boneIndex); + Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); + tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale(), 0, ipo.getLastFrame(), fps, false)); + } + } + this.equaliseBoneTracks(tracks); + return tracks.toArray(new BoneTrack[tracks.size()]); + } + + /** + * This method retuns the bone tracks for animation for blender version 2.49 + * (and probably several lower versions too). + * + * @param actionStructure + * the structure containing the tracks + * @param blenderContext + * the blender context + * @return a list of tracks for the specified animation + * @throws BlenderFileException + * an exception is thrown when there are problems with the blend + * file + */ + private BoneTrack[] getTracks249(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.log(Level.FINE, "Getting tracks!"); + IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); + int fps = blenderContext.getBlenderKey().getFps(); + Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase"); + List actionChannels = chanbase.evaluateListBase();// bActionChannel + List tracks = new ArrayList(); + for (Structure bActionChannel : actionChannels) { + String name = bActionChannel.getFieldValue("name").toString(); + int boneIndex = skeleton.getBoneIndex(name); + if (boneIndex >= 0) { + Pointer p = (Pointer) bActionChannel.getFieldValue("ipo"); + if (!p.isNull()) { + Structure ipoStructure = p.fetchData().get(0); + + Bone bone = skeleton.getBone(boneIndex); + Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext); + if (ipo != null) { + tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale(), 0, ipo.getLastFrame(), fps, false)); + } + } + } + } + this.equaliseBoneTracks(tracks); + return tracks.toArray(new BoneTrack[tracks.size()]); + } + + /** + * The method makes all the tracks to have equal frame lengths. + * @param tracks + * the tracks to be equalized + */ + private void equaliseBoneTracks(List tracks) { + // first compute the maximum amount of frames + int maximumFrameCount = -1; + float[] maximumTrackTimes = null; + for (BoneTrack track : tracks) { + if (track.getTimes().length > maximumFrameCount) { + maximumTrackTimes = track.getTimes(); + maximumFrameCount = maximumTrackTimes.length; + } + } + + // now widen all the tracks that have less frames by repeating the last values in the frame + for (BoneTrack track : tracks) { + int currentTrackLength = track.getTimes().length; + if (currentTrackLength < maximumFrameCount) { + Vector3f[] translations = new Vector3f[maximumFrameCount]; + Quaternion[] rotations = new Quaternion[maximumFrameCount]; + Vector3f[] scales = new Vector3f[maximumFrameCount]; + + Vector3f[] currentTranslations = track.getTranslations(); + Quaternion[] currentRotations = track.getRotations(); + Vector3f[] currentScales = track.getScales(); + for (int i = 0; i < currentTrackLength; ++i) { + translations[i] = currentTranslations[i]; + rotations[i] = currentRotations[i]; + scales[i] = currentScales[i]; + } + + for (int i = currentTrackLength; i < maximumFrameCount; ++i) { + translations[i] = currentTranslations[currentTranslations.length - 1]; + rotations[i] = currentRotations[currentRotations.length - 1]; + scales[i] = currentScales[currentScales.length - 1]; + } + + track.setKeyframes(maximumTrackTimes, translations, rotations, scales); + } + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java new file mode 100644 index 000000000..6b14d8097 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java @@ -0,0 +1,223 @@ +package com.jme3.scene.plugins.blender.animations; + +import java.util.ArrayList; +import java.util.List; + +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.objects.ObjectHelper; + +/** + * This class holds the basic data that describes a bone. + * + * @author Marcin Roguski (Kaelthas) + */ +public class BoneContext { + // the flags of the bone + public static final int CONNECTED_TO_PARENT = 0x10; + + /** + * The bones' matrices have, unlike objects', the coordinate system identical to JME's (Y axis is UP, X to the right and Z toward us). + * So in order to have them loaded properly we need to transform their armature matrix (which blender sees as rotated) to make sure we get identical results. + */ + public static final Matrix4f BONE_ARMATURE_TRANSFORMATION_MATRIX = new Matrix4f(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1); + + private BlenderContext blenderContext; + /** The OMA of the bone's armature object. */ + private Long armatureObjectOMA; + /** The OMA of the model that owns the bone's skeleton. */ + private Long skeletonOwnerOma; + /** The structure of the bone. */ + private Structure boneStructure; + /** Bone's name. */ + private String boneName; + /** The bone's flag. */ + private int flag; + /** The bone's matrix in world space. */ + private Matrix4f globalBoneMatrix; + /** The bone's matrix in the model space. */ + private Matrix4f boneMatrixInModelSpace; + /** The parent context. */ + private BoneContext parent; + /** The children of this context. */ + private List children = new ArrayList(); + /** Created bone (available after calling 'buildBone' method). */ + private Bone bone; + /** The length of the bone. */ + private float length; + + /** + * Constructor. Creates the basic set of bone's data. + * + * @param armatureObjectOMA + * the OMA of the bone's armature object + * @param boneStructure + * the bone's structure + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when problem with blender data reading + * occurs + */ + public BoneContext(Long armatureObjectOMA, Structure boneStructure, BlenderContext blenderContext) throws BlenderFileException { + this(boneStructure, armatureObjectOMA, null, blenderContext); + } + + /** + * Constructor. Creates the basic set of bone's data. + * + * @param boneStructure + * the bone's structure + * @param armatureObjectOMA + * the OMA of the bone's armature object + * @param parent + * bone's parent (null if the bone is the root bone) + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when problem with blender data reading + * occurs + */ + private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, BlenderContext blenderContext) throws BlenderFileException { + this.parent = parent; + this.blenderContext = blenderContext; + this.boneStructure = boneStructure; + this.armatureObjectOMA = armatureObjectOMA; + boneName = boneStructure.getFieldValue("name").toString(); + flag = ((Number) boneStructure.getFieldValue("flag")).intValue(); + length = ((Number) boneStructure.getFieldValue("length")).floatValue(); + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + + // first get the bone matrix in its armature space + globalBoneMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis()); + // then make sure it is rotated in a proper way to fit the jme bone transformation conventions + globalBoneMatrix.multLocal(BONE_ARMATURE_TRANSFORMATION_MATRIX); + + Spatial armature = (Spatial) objectHelper.toObject(blenderContext.getFileBlock(armatureObjectOMA).getStructure(blenderContext), blenderContext); + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + Matrix4f armatureWorldMatrix = constraintHelper.toMatrix(armature.getWorldTransform(), new Matrix4f()); + + // and now compute the final bone matrix in world space + globalBoneMatrix = armatureWorldMatrix.mult(globalBoneMatrix); + + // create the children + List childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(); + for (Structure child : childbase) { + children.add(new BoneContext(child, armatureObjectOMA, this, blenderContext)); + } + + blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this); + } + + /** + * This method builds the bone. It recursively builds the bone's children. + * + * @param bones + * a list of bones where the newly created bone will be added + * @param skeletonOwnerOma + * the spatial of the object that will own the skeleton + * @param blenderContext + * the blender context + * @return newly created bone + */ + public Bone buildBone(List bones, Long skeletonOwnerOma, BlenderContext blenderContext) { + this.skeletonOwnerOma = skeletonOwnerOma; + Long boneOMA = boneStructure.getOldMemoryAddress(); + bone = new Bone(boneName); + bones.add(bone); + blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone); + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + + Structure skeletonOwnerObjectStructure = (Structure) blenderContext.getLoadedFeature(skeletonOwnerOma, LoadedFeatureDataType.LOADED_STRUCTURE); + Matrix4f invertedObjectOwnerGlobalMatrix = objectHelper.getMatrix(skeletonOwnerObjectStructure, "imat", blenderContext.getBlenderKey().isFixUpAxis()); + if (objectHelper.isParent(skeletonOwnerOma, armatureObjectOMA)) { + boneMatrixInModelSpace = globalBoneMatrix.mult(invertedObjectOwnerGlobalMatrix); + } else { + boneMatrixInModelSpace = invertedObjectOwnerGlobalMatrix.mult(globalBoneMatrix); + } + + Matrix4f boneLocalMatrix = parent == null ? boneMatrixInModelSpace : parent.boneMatrixInModelSpace.invert().multLocal(boneMatrixInModelSpace); + + Vector3f poseLocation = parent == null || !this.is(CONNECTED_TO_PARENT) ? boneLocalMatrix.toTranslationVector() : new Vector3f(0, parent.length, 0); + Quaternion rotation = boneLocalMatrix.toRotationQuat().normalizeLocal(); + Vector3f scale = boneLocalMatrix.toScaleVector(); + + bone.setBindTransforms(poseLocation, rotation, scale); + for (BoneContext child : children) { + bone.addChild(child.buildBone(bones, skeletonOwnerOma, blenderContext)); + } + + return bone; + } + + /** + * @return built bone (available after calling 'buildBone' method) + */ + public Bone getBone() { + return bone; + } + + /** + * @return the old memory address of the bone + */ + public Long getBoneOma() { + return boneStructure.getOldMemoryAddress(); + } + + /** + * The method returns the length of the bone. + * If you want to use it for bone debugger take model space scale into account and do + * something like this: + * boneContext.getLength() * boneContext.getBone().getModelSpaceScale().y. + * Otherwise the bones might not look as they should in the bone debugger. + * @return the length of the bone + */ + public float getLength() { + return length; + } + + /** + * @return OMA of the bone's armature object + */ + public Long getArmatureObjectOMA() { + return armatureObjectOMA; + } + + /** + * @return the OMA of the model that owns the bone's skeleton + */ + public Long getSkeletonOwnerOma() { + return skeletonOwnerOma; + } + + /** + * @return the skeleton the bone of this context belongs to + */ + public Skeleton getSkeleton() { + return blenderContext.getSkeleton(armatureObjectOMA); + } + + /** + * Tells if the bone is of specified property defined by its flag. + * @param flagMask + * the mask of the flag (constants defined in this class) + * @return true if the bone IS of specified proeprty and false otherwise + */ + public boolean is(int flagMask) { + return (flag & flagMask) != 0; + } + + @Override + public String toString() { + return "BoneContext: " + boneName; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java new file mode 100644 index 000000000..a2b5e3708 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java @@ -0,0 +1,243 @@ +package com.jme3.scene.plugins.blender.animations; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.animation.BoneTrack; +import com.jme3.animation.SpatialTrack; +import com.jme3.animation.Track; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.curves.BezierCurve; + +/** + * This class is used to calculate bezier curves value for the given frames. The + * Ipo (interpolation object) consists of several b-spline curves (connected 3rd + * degree bezier curves) of a different type. + * + * @author Marcin Roguski + */ +public class Ipo { + private static final Logger LOGGER = Logger.getLogger(Ipo.class.getName()); + + public static final int AC_LOC_X = 1; + public static final int AC_LOC_Y = 2; + public static final int AC_LOC_Z = 3; + public static final int OB_ROT_X = 7; + public static final int OB_ROT_Y = 8; + public static final int OB_ROT_Z = 9; + public static final int AC_SIZE_X = 13; + public static final int AC_SIZE_Y = 14; + public static final int AC_SIZE_Z = 15; + public static final int AC_QUAT_W = 25; + public static final int AC_QUAT_X = 26; + public static final int AC_QUAT_Y = 27; + public static final int AC_QUAT_Z = 28; + + /** A list of bezier curves for this interpolation object. */ + private BezierCurve[] bezierCurves; + /** Each ipo contains one bone track. */ + private Track calculatedTrack; + /** This variable indicates if the Y asxis is the UP axis or not. */ + protected boolean fixUpAxis; + /** + * Depending on the blender version rotations are stored in degrees or + * radians so we need to know the version that is used. + */ + protected final int blenderVersion; + + /** + * Constructor. Stores the bezier curves. + * + * @param bezierCurves + * a table of bezier curves + * @param fixUpAxis + * indicates if the Y is the up axis or not + * @param blenderVersion + * the blender version that is currently used + */ + public Ipo(BezierCurve[] bezierCurves, boolean fixUpAxis, int blenderVersion) { + this.bezierCurves = bezierCurves; + this.fixUpAxis = fixUpAxis; + this.blenderVersion = blenderVersion; + } + + /** + * This method calculates the ipo value for the first curve. + * + * @param frame + * the frame for which the value is calculated + * @return calculated ipo value + */ + public float calculateValue(int frame) { + return this.calculateValue(frame, 0); + } + + /** + * This method calculates the ipo value for the curve of the specified + * index. Make sure you do not exceed the curves amount. Alway chech the + * amount of curves before calling this method. + * + * @param frame + * the frame for which the value is calculated + * @param curveIndex + * the index of the curve + * @return calculated ipo value + */ + public float calculateValue(int frame, int curveIndex) { + return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE); + } + + /** + * This method returns the frame where last bezier triple center point of + * the specified bezier curve is located. + * + * @return the frame number of the last defined bezier triple point for the + * specified ipo + */ + public int getLastFrame() { + int result = 1; + for (int i = 0; i < bezierCurves.length; ++i) { + int tempResult = bezierCurves[i].getLastFrame(); + if (tempResult > result) { + result = tempResult; + } + } + return result; + } + + /** + * This method calculates the value of the curves as a bone track between + * the specified frames. + * + * @param targetIndex + * the index of the target for which the method calculates the + * tracks IMPORTANT! Aet to -1 (or any negative number) if you + * want to load spatial animation. + * @param localTranslation + * the local translation of the object/bone that will be animated by + * the track + * @param localRotation + * the local rotation of the object/bone that will be animated by + * the track + * @param localScale + * the local scale of the object/bone that will be animated by + * the track + * @param startFrame + * the first frame of tracks (inclusive) + * @param stopFrame + * the last frame of the tracks (inclusive) + * @param fps + * frame rate (frames per second) + * @param spatialTrack + * this flag indicates if the track belongs to a spatial or to a + * bone; the difference is important because it appears that bones + * in blender have the same type of coordinate system (Y as UP) + * as jme while other features have different one (Z is UP) + * @return bone track for the specified bone + */ + public Track calculateTrack(int targetIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean spatialTrack) { + if (calculatedTrack == null) { + // preparing data for track + int framesAmount = stopFrame - startFrame; + float timeBetweenFrames = 1.0f / fps; + + float[] times = new float[framesAmount + 1]; + Vector3f[] translations = new Vector3f[framesAmount + 1]; + float[] translation = new float[] { localTranslation.x, localTranslation.y, localTranslation.z }; + Quaternion[] rotations = new Quaternion[framesAmount + 1]; + float[] quaternionRotation = new float[] { localRotation.getX(), localRotation.getY(), localRotation.getZ(), localRotation.getW(), }; + float[] objectRotation = localRotation.toAngles(null); + Vector3f[] scales = new Vector3f[framesAmount + 1]; + float[] scale = new float[] { localScale.x, localScale.y, localScale.z }; + float degreeToRadiansFactor = 1; + if (blenderVersion < 250) {// in blender earlier than 2.50 the values are stored in degrees + degreeToRadiansFactor *= FastMath.DEG_TO_RAD * 10;// the values in blender are divided by 10, so we need to mult it here + } + int yIndex = 1, zIndex = 2; + if (spatialTrack && fixUpAxis) { + yIndex = 2; + zIndex = 1; + } + + // calculating track data + for (int frame = startFrame; frame <= stopFrame; ++frame) { + int index = frame - startFrame; + times[index] = index * timeBetweenFrames;// start + (frame - 1) + // * timeBetweenFrames; + for (int j = 0; j < bezierCurves.length; ++j) { + double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE); + switch (bezierCurves[j].getType()) { + // LOCATION + case AC_LOC_X: + translation[0] = (float) value; + break; + case AC_LOC_Y: + if (fixUpAxis && value != 0) { + value = -value; + } + translation[yIndex] = (float) value; + break; + case AC_LOC_Z: + translation[zIndex] = (float) value; + break; + + // ROTATION (used with object animation) + case OB_ROT_X: + objectRotation[0] = (float) value * degreeToRadiansFactor; + break; + case OB_ROT_Y: + if (fixUpAxis && value != 0) { + value = -value; + } + objectRotation[yIndex] = (float) value * degreeToRadiansFactor; + break; + case OB_ROT_Z: + objectRotation[zIndex] = (float) value * degreeToRadiansFactor; + break; + + // SIZE + case AC_SIZE_X: + scale[0] = (float) value; + break; + case AC_SIZE_Y: + scale[fixUpAxis ? 2 : 1] = (float) value; + break; + case AC_SIZE_Z: + scale[fixUpAxis ? 1 : 2] = (float) value; + break; + + // QUATERNION ROTATION (used with bone animation) + case AC_QUAT_W: + quaternionRotation[3] = (float) value; + break; + case AC_QUAT_X: + quaternionRotation[0] = (float) value; + break; + case AC_QUAT_Y: + if (fixUpAxis && value != 0) { + value = -value; + } + quaternionRotation[yIndex] = (float) value; + break; + case AC_QUAT_Z: + quaternionRotation[zIndex] = (float) value; + break; + default: + LOGGER.log(Level.WARNING, "Unknown ipo curve type: {0}.", bezierCurves[j].getType()); + } + } + translations[index] = localRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2])); + rotations[index] = spatialTrack ? new Quaternion().fromAngles(objectRotation) : new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]); + scales[index] = new Vector3f(scale[0], scale[1], scale[2]); + } + if (spatialTrack) { + calculatedTrack = new SpatialTrack(times, translations, rotations, scales); + } else { + calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales); + } + } + return calculatedTrack; + } +} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/IpoHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/IpoHelper.java new file mode 100644 index 000000000..260a59575 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/IpoHelper.java @@ -0,0 +1,194 @@ +package com.jme3.scene.plugins.blender.animations; + +import java.util.List; +import java.util.logging.Logger; + +import com.jme3.animation.BoneTrack; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.curves.BezierCurve; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.BlenderInputStream; +import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * This class helps to compute values from interpolation curves for features + * like animation or constraint influence. The curves are 3rd degree bezier + * curves. + * + * @author Marcin Roguski + */ +public class IpoHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(IpoHelper.class.getName()); + + /** + * This constructor parses the given blender version and stores the result. + * Some functionalities may differ in different blender versions. + * + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public IpoHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + /** + * This method creates an ipo object used for interpolation calculations. + * + * @param ipoStructure + * the structure with ipo definition + * @param blenderContext + * the blender context + * @return the ipo object + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public Ipo fromIpoStructure(Structure ipoStructure, BlenderContext blenderContext) throws BlenderFileException { + Structure curvebase = (Structure) ipoStructure.getFieldValue("curve"); + + // preparing bezier curves + Ipo result = null; + List curves = curvebase.evaluateListBase();// IpoCurve + if (curves.size() > 0) { + BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; + int frame = 0; + for (Structure curve : curves) { + Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); + List bezTriples = pBezTriple.fetchData(); + int type = ((Number) curve.getFieldValue("adrcode")).intValue(); + bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); + } + curves.clear(); + result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); + blenderContext.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result); + } + return result; + } + + /** + * This method creates an ipo object used for interpolation calculations. It + * should be called for blender version 2.50 and higher. + * + * @param actionStructure + * the structure with action definition + * @param blenderContext + * the blender context + * @return the ipo object + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public Ipo fromAction(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { + Ipo result = null; + List curves = ((Structure) actionStructure.getFieldValue("curves")).evaluateListBase();// FCurve + if (curves.size() > 0) { + BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; + int frame = 0; + for (Structure curve : curves) { + Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); + List bezTriples = pBezTriple.fetchData(); + int type = this.getCurveType(curve, blenderContext); + bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); + } + curves.clear(); + result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); + } + return result; + } + + /** + * This method returns the type of the ipo curve. + * + * @param structure + * the structure must contain the 'rna_path' field and + * 'array_index' field (the type is not important here) + * @param blenderContext + * the blender context + * @return the type of the curve + */ + public int getCurveType(Structure structure, BlenderContext blenderContext) { + // reading rna path first + BlenderInputStream bis = blenderContext.getInputStream(); + int currentPosition = bis.getPosition(); + Pointer pRnaPath = (Pointer) structure.getFieldValue("rna_path"); + FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pRnaPath.getOldMemoryAddress()); + bis.setPosition(dataFileBlock.getBlockPosition()); + String rnaPath = bis.readString(); + bis.setPosition(currentPosition); + int arrayIndex = ((Number) structure.getFieldValue("array_index")).intValue(); + + // determining the curve type + if (rnaPath.endsWith("location")) { + return Ipo.AC_LOC_X + arrayIndex; + } + if (rnaPath.endsWith("rotation_quaternion")) { + return Ipo.AC_QUAT_W + arrayIndex; + } + if (rnaPath.endsWith("scale")) { + return Ipo.AC_SIZE_X + arrayIndex; + } + if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) { + return Ipo.OB_ROT_X + arrayIndex; + } + LOGGER.warning("Unknown curve rna path: " + rnaPath); + return -1; + } + + /** + * This method creates an ipo with only a single value. No track type is + * specified so do not use it for calculating tracks. + * + * @param constValue + * the value of this ipo + * @return constant ipo + */ + public Ipo fromValue(float constValue) { + return new ConstIpo(constValue); + } + + /** + * Ipo constant curve. This is a curve with only one value and no specified + * type. This type of ipo cannot be used to calculate tracks. It should only + * be used to calculate single value for a given frame. + * + * @author Marcin Roguski (Kaelthas) + */ + private class ConstIpo extends Ipo { + + /** The constant value of this ipo. */ + private float constValue; + + /** + * Constructor. Stores the constant value of this ipo. + * + * @param constValue + * the constant value of this ipo + */ + public ConstIpo(float constValue) { + super(null, false, 0);// the version is not important here + this.constValue = constValue; + } + + @Override + public float calculateValue(int frame) { + return constValue; + } + + @Override + public float calculateValue(int frame, int curveIndex) { + return constValue; + } + + @Override + public BoneTrack calculateTrack(int boneIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) { + throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!"); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java new file mode 100644 index 000000000..c5e1f0192 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java @@ -0,0 +1,147 @@ +package com.jme3.scene.plugins.blender.cameras; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.math.FastMath; +import com.jme3.renderer.Camera; +import com.jme3.scene.CameraNode; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * A class that is used to load cameras into the scene. + * @author Marcin Roguski + */ +public class CameraHelper extends AbstractBlenderHelper { + + private static final Logger LOGGER = Logger.getLogger(CameraHelper.class.getName()); + protected static final int DEFAULT_CAM_WIDTH = 640; + protected static final int DEFAULT_CAM_HEIGHT = 480; + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in + * different blender versions. + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public CameraHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + /** + * This method converts the given structure to jme camera. + * + * @param structure + * camera structure + * @return jme camera object + * @throws BlenderFileException + * an exception is thrown when there are problems with the + * blender file + */ + public CameraNode toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + if (blenderVersion >= 250) { + return this.toCamera250(structure, blenderContext.getSceneStructure()); + } else { + return this.toCamera249(structure); + } + } + + /** + * This method converts the given structure to jme camera. Should be used form blender 2.5+. + * + * @param structure + * camera structure + * @param sceneStructure + * scene structure + * @return jme camera object + * @throws BlenderFileException + * an exception is thrown when there are problems with the + * blender file + */ + private CameraNode toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException { + int width = DEFAULT_CAM_WIDTH; + int height = DEFAULT_CAM_HEIGHT; + if (sceneStructure != null) { + Structure renderData = (Structure) sceneStructure.getFieldValue("r"); + width = ((Number) renderData.getFieldValue("xsch")).shortValue(); + height = ((Number) renderData.getFieldValue("ysch")).shortValue(); + } + Camera camera = new Camera(width, height); + int type = ((Number) structure.getFieldValue("type")).intValue(); + if (type != 0 && type != 1) { + LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type); + type = 0; + } + // type==0 - perspective; type==1 - orthographic; perspective is used as default + camera.setParallelProjection(type == 1); + float aspect = width / (float) height; + float fovY; // Vertical field of view in degrees + float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue(); + float clipend = ((Number) structure.getFieldValue("clipend")).floatValue(); + if (type == 0) { + // Convert lens MM to vertical degrees in fovY, see Blender rna_Camera_angle_get() + // Default sensor size prior to 2.60 was 32. + float sensor = 32.0f; + boolean sensorVertical = false; + Number sensorFit = (Number) structure.getFieldValue("sensor_fit"); + if (sensorFit != null) { + // If sensor_fit is vert (2), then sensor_y is used + sensorVertical = sensorFit.byteValue() == 2; + String sensorName = "sensor_x"; + if (sensorVertical) { + sensorName = "sensor_y"; + } + sensor = ((Number) structure.getFieldValue(sensorName)).floatValue(); + } + float focalLength = ((Number) structure.getFieldValue("lens")).floatValue(); + float fov = 2.0f * FastMath.atan((sensor / 2.0f) / focalLength); + if (sensorVertical) { + fovY = fov * FastMath.RAD_TO_DEG; + } else { + // Convert fov from horizontal to vertical + fovY = 2.0f * FastMath.atan(FastMath.tan(fov / 2.0f) / aspect) * FastMath.RAD_TO_DEG; + } + } else { + // This probably is not correct. + fovY = ((Number) structure.getFieldValue("ortho_scale")).floatValue(); + } + camera.setFrustumPerspective(fovY, aspect, clipsta, clipend); + return new CameraNode(null, camera); + } + + /** + * This method converts the given structure to jme camera. Should be used form blender 2.49. + * + * @param structure + * camera structure + * @return jme camera object + * @throws BlenderFileException + * an exception is thrown when there are problems with the + * blender file + */ + private CameraNode toCamera249(Structure structure) throws BlenderFileException { + Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT); + int type = ((Number) structure.getFieldValue("type")).intValue(); + if (type != 0 && type != 1) { + LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type); + type = 0; + } + // type==0 - perspective; type==1 - orthographic; perspective is used as default + camera.setParallelProjection(type == 1); + float aspect = 0; + float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue(); + float clipend = ((Number) structure.getFieldValue("clipend")).floatValue(); + if (type == 0) { + aspect = ((Number) structure.getFieldValue("lens")).floatValue(); + } else { + aspect = ((Number) structure.getFieldValue("ortho_scale")).floatValue(); + } + camera.setFrustumPerspective(aspect, camera.getWidth() / camera.getHeight(), clipsta, clipend); + return new CameraNode(null, camera); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java new file mode 100644 index 000000000..284cb7da4 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java @@ -0,0 +1,73 @@ +package com.jme3.scene.plugins.blender.constraints; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.animations.ArmatureHelper; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.animations.Ipo; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * Constraint applied on the bone. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class BoneConstraint extends Constraint { + private static final Logger LOGGER = Logger.getLogger(BoneConstraint.class.getName()); + + /** + * The bone constraint constructor. + * + * @param constraintStructure + * the constraint's structure + * @param ownerOMA + * the OMA of the bone that owns the constraint + * @param influenceIpo + * the influence interpolation curve + * @param blenderContext + * the blender context + * @throws BlenderFileException + * exception thrown when problems with blender file occur + */ + public BoneConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { + super(constraintStructure, ownerOMA, influenceIpo, blenderContext); + } + + @Override + public boolean validate() { + if (targetOMA != null) { + Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); + if (nodeTarget == null) { + LOGGER.log(Level.WARNING, "Cannot find target for constraint: {0}.", name); + return false; + } + // the second part of the if expression verifies if the found node + // (if any) is an armature node + if (blenderContext.getMarkerValue(ArmatureHelper.ARMATURE_NODE_MARKER, nodeTarget) != null) { + if (subtargetName.trim().isEmpty()) { + LOGGER.log(Level.WARNING, "No bone target specified for constraint: {0}.", name); + return false; + } + // if the target is not an object node then it is an Armature, + // so make sure the bone is in the current skeleton + BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); + if (targetOMA.longValue() != boneContext.getArmatureObjectOMA().longValue()) { + LOGGER.log(Level.WARNING, "Bone constraint {0} must target bone in the its own skeleton! Targeting bone in another skeleton is not supported!", name); + return false; + } + } + } + return true; + } + + @Override + public void apply(int frame) { + super.apply(frame); + blenderContext.getBoneContext(ownerOMA).getBone().updateWorldVectors(); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java new file mode 100644 index 000000000..99f5ac4b1 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java @@ -0,0 +1,166 @@ +package com.jme3.scene.plugins.blender.constraints; + +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.math.Transform; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.Ipo; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefinition; +import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefinitionFactory; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * The implementation of a constraint. + * + * @author Marcin Roguski (Kaelthas) + */ +public abstract class Constraint { + private static final Logger LOGGER = Logger.getLogger(Constraint.class.getName()); + + /** The name of this constraint. */ + protected final String name; + /** Indicates if the constraint is already baked or not. */ + protected boolean baked; + + protected Space ownerSpace; + protected final ConstraintDefinition constraintDefinition; + protected Long ownerOMA; + + protected Long targetOMA; + protected Space targetSpace; + protected String subtargetName; + + /** The ipo object defining influence. */ + protected final Ipo ipo; + /** The blender context. */ + protected final BlenderContext blenderContext; + protected final ConstraintHelper constraintHelper; + + /** + * This constructor creates the constraint instance. + * + * @param constraintStructure + * the constraint's structure (bConstraint clss in blender 2.49). + * @param ownerOMA + * the old memory address of the constraint owner + * @param ownerType + * the type of the constraint owner + * @param influenceIpo + * the ipo curve of the influence factor + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public Constraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { + this.blenderContext = blenderContext; + name = constraintStructure.getFieldValue("name").toString(); + Pointer pData = (Pointer) constraintStructure.getFieldValue("data"); + if (pData.isNotNull()) { + Structure data = pData.fetchData().get(0); + constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, ownerOMA, blenderContext); + Pointer pTar = (Pointer) data.getFieldValue("tar"); + if (pTar != null && pTar.isNotNull()) { + targetOMA = pTar.getOldMemoryAddress(); + targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue()); + Object subtargetValue = data.getFieldValue("subtarget"); + if (subtargetValue != null) {// not all constraint data have the + // subtarget field + subtargetName = subtargetValue.toString(); + } + } + } else { + // Null constraint has no data, so create it here + constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, null, blenderContext); + } + ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue()); + ipo = influenceIpo; + this.ownerOMA = ownerOMA; + constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + LOGGER.log(Level.INFO, "Created constraint: {0} with definition: {1}", new Object[] { name, constraintDefinition }); + } + + /** + * @return true if the constraint is implemented and false + * otherwise + */ + public boolean isImplemented() { + return constraintDefinition == null ? true : constraintDefinition.isImplemented(); + } + + /** + * @return the name of the constraint type, similar to the constraint name + * used in Blender + */ + public String getConstraintTypeName() { + return constraintDefinition.getConstraintTypeName(); + } + + /** + * @return the OMAs of the features whose transform had been altered beside the constraint owner + */ + public Set getAlteredOmas() { + return constraintDefinition.getAlteredOmas(); + } + + /** + * Performs validation before baking. Checks factors that can prevent + * constraint from baking that could not be checked during constraint + * loading. + */ + public abstract boolean validate(); + + /** + * Applies the constraint to owner (and in some cases can alter other bones of the skeleton). + * @param frame + * the frame of the animation + */ + public void apply(int frame) { + Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null; + constraintDefinition.bake(ownerSpace, targetSpace, targetTransform, ipo.calculateValue(frame)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (name == null ? 0 : name.hashCode()); + result = prime * result + (ownerOMA == null ? 0 : ownerOMA.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (this.getClass() != obj.getClass()) { + return false; + } + Constraint other = (Constraint) obj; + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (ownerOMA == null) { + if (other.ownerOMA != null) { + return false; + } + } else if (!ownerOMA.equals(other.ownerOMA)) { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java new file mode 100644 index 000000000..97b6889ff --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java @@ -0,0 +1,478 @@ +package com.jme3.scene.plugins.blender.constraints; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.animations.ArmatureHelper; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.animations.Ipo; +import com.jme3.scene.plugins.blender.animations.IpoHelper; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.objects.ObjectHelper; +import com.jme3.util.TempVars; + +/** + * This class should be used for constraint calculations. + * + * @author Marcin Roguski (Kaelthas) + */ +public class ConstraintHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName()); + + private static final Quaternion POS_PARLOC_SPACE_QUATERNION = new Quaternion(new float[] { FastMath.HALF_PI, 0, 0 }); + private static final Quaternion NEG_PARLOC_SPACE_QUATERNION = new Quaternion(new float[] { -FastMath.HALF_PI, 0, 0 }); + + /** + * Helper constructor. + * + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public ConstraintHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + /** + * This method reads constraints for for the given structure. The + * constraints are loaded only once for object/bone. + * + * @param objectStructure + * the structure we read constraint's for + * @param blenderContext + * the blender context + * @throws BlenderFileException + */ + public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.fine("Loading constraints."); + // reading influence ipos for the constraints + IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); + Map> constraintsIpos = new HashMap>(); + Pointer pActions = (Pointer) objectStructure.getFieldValue("action"); + if (pActions.isNotNull()) { + List actions = pActions.fetchData(); + for (Structure action : actions) { + Structure chanbase = (Structure) action.getFieldValue("chanbase"); + List actionChannels = chanbase.evaluateListBase(); + for (Structure actionChannel : actionChannels) { + Map ipos = new HashMap(); + Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels"); + List constraintChannels = constChannels.evaluateListBase(); + for (Structure constraintChannel : constraintChannels) { + Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo"); + if (pIpo.isNotNull()) { + String constraintName = constraintChannel.getFieldValue("name").toString(); + Ipo ipo = ipoHelper.fromIpoStructure(pIpo.fetchData().get(0), blenderContext); + ipos.put(constraintName, ipo); + } + } + String actionName = actionChannel.getFieldValue("name").toString(); + constraintsIpos.put(actionName, ipos); + } + } + } + + // loading constraints connected with the object's bones + Pointer pPose = (Pointer) objectStructure.getFieldValue("pose"); + if (pPose.isNotNull()) { + List poseChannels = ((Structure) pPose.fetchData().get(0).getFieldValue("chanbase")).evaluateListBase(); + for (Structure poseChannel : poseChannels) { + List constraintsList = new ArrayList(); + Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress()); + + // the name is read directly from structure because bone might + // not yet be loaded + String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString(); + List constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(); + for (Structure constraint : constraints) { + String constraintName = constraint.getFieldValue("name").toString(); + Map ipoMap = constraintsIpos.get(name); + Ipo ipo = ipoMap == null ? null : ipoMap.get(constraintName); + if (ipo == null) { + float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); + ipo = ipoHelper.fromValue(enforce); + } + constraintsList.add(new BoneConstraint(constraint, boneOMA, ipo, blenderContext)); + } + blenderContext.addConstraints(boneOMA, constraintsList); + } + } + + // loading constraints connected with the object itself + List constraints = ((Structure) objectStructure.getFieldValue("constraints")).evaluateListBase(); + if (constraints != null && constraints.size() > 0) { + Pointer pData = (Pointer) objectStructure.getFieldValue("data"); + String dataType = pData.isNotNull() ? pData.fetchData().get(0).getType() : null; + List constraintsList = new ArrayList(constraints.size()); + + for (Structure constraint : constraints) { + String constraintName = constraint.getFieldValue("name").toString(); + String objectName = objectStructure.getName(); + + Map objectConstraintsIpos = constraintsIpos.get(objectName); + Ipo ipo = objectConstraintsIpos != null ? objectConstraintsIpos.get(constraintName) : null; + if (ipo == null) { + float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); + ipo = ipoHelper.fromValue(enforce); + } + + constraintsList.add(this.createConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext)); + } + blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList); + } + } + + /** + * This method creates a proper constraint object depending on the object's + * data type. Supported data types:
  • Mesh
  • Armature
  • Camera
  • + * Lamp Bone constraints are created in a different place. + * + * @param dataType + * the type of the object's data + * @param constraintStructure + * the constraint structure + * @param ownerOMA + * the owner OMA + * @param influenceIpo + * the influence interpolation curve + * @param blenderContext + * the blender context + * @return constraint object for the required type + * @throws BlenderFileException + * thrown when problems with blender file occured + */ + private Constraint createConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { + if (dataType == null || "Mesh".equalsIgnoreCase(dataType) || "Camera".equalsIgnoreCase(dataType) || "Lamp".equalsIgnoreCase(dataType)) { + return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext); + } else if ("Armature".equalsIgnoreCase(dataType)) { + return new SkeletonConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext); + } else { + throw new IllegalArgumentException("Unsupported data type for applying constraints: " + dataType); + } + } + + /** + * The method bakes all available and valid constraints. + * + * @param blenderContext + * the blender context + */ + public void bakeConstraints(BlenderContext blenderContext) { + List simulationRootNodes = new ArrayList(); + for (Constraint constraint : blenderContext.getAllConstraints()) { + boolean constraintUsed = false; + for (SimulationNode node : simulationRootNodes) { + if (node.contains(constraint)) { + constraintUsed = true; + break; + } + } + + if (!constraintUsed) { + if (constraint instanceof BoneConstraint) { + BoneContext boneContext = blenderContext.getBoneContext(constraint.ownerOMA); + simulationRootNodes.add(new SimulationNode(boneContext.getArmatureObjectOMA(), blenderContext)); + } else if (constraint instanceof SpatialConstraint) { + Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); + while (spatial.getParent() != null) { + spatial = spatial.getParent(); + } + simulationRootNodes.add(new SimulationNode((Long) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, spatial), blenderContext)); + } else { + throw new IllegalStateException("Unsupported constraint type: " + constraint); + } + } + } + + for (SimulationNode node : simulationRootNodes) { + node.simulate(); + } + } + + /** + * The method retreives the transform from a feature in a given space. + * + * @param oma + * the OMA of the feature (spatial or armature node) + * @param subtargetName + * the feature's subtarget (bone in a case of armature's node) + * @param space + * the space the transform is evaluated to + * @return thensform of a feature in a given space + */ + public Transform getTransform(Long oma, String subtargetName, Space space) { + Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE); + boolean isArmature = blenderContext.getMarkerValue(ArmatureHelper.ARMATURE_NODE_MARKER, feature) != null; + if (isArmature) { + blenderContext.getSkeleton(oma).updateWorldVectors(); + BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName); + Bone bone = targetBoneContext.getBone(); + + if (bone.getParent() == null && (space == Space.CONSTRAINT_SPACE_LOCAL || space == Space.CONSTRAINT_SPACE_PARLOCAL)) { + space = Space.CONSTRAINT_SPACE_POSE; + } + + TempVars tempVars = TempVars.get();// use readable names of the matrices so that the code is more clear + Transform result; + switch (space) { + case CONSTRAINT_SPACE_WORLD: + Spatial model = (Spatial) blenderContext.getLoadedFeature(targetBoneContext.getSkeletonOwnerOma(), LoadedFeatureDataType.LOADED_FEATURE); + Matrix4f boneModelMatrix = this.toMatrix(bone.getModelSpacePosition(), bone.getModelSpaceRotation(), bone.getModelSpaceScale(), tempVars.tempMat4); + Matrix4f modelWorldMatrix = this.toMatrix(model.getWorldTransform(), tempVars.tempMat42); + Matrix4f boneMatrixInWorldSpace = modelWorldMatrix.multLocal(boneModelMatrix); + result = new Transform(boneMatrixInWorldSpace.toTranslationVector(), boneMatrixInWorldSpace.toRotationQuat(), boneMatrixInWorldSpace.toScaleVector()); + break; + case CONSTRAINT_SPACE_LOCAL: + assert bone.getParent() != null : "CONSTRAINT_SPACE_LOCAL should be evaluated as CONSTRAINT_SPACE_POSE if the bone has no parent!"; + result = new Transform(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale()); + break; + case CONSTRAINT_SPACE_POSE: + Matrix4f boneWorldMatrix = this.toMatrix(this.getTransform(oma, subtargetName, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat4); + Matrix4f armatureInvertedWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat42).invertLocal(); + Matrix4f bonePoseMatrix = armatureInvertedWorldMatrix.multLocal(boneWorldMatrix); + result = new Transform(bonePoseMatrix.toTranslationVector(), bonePoseMatrix.toRotationQuat(), bonePoseMatrix.toScaleVector()); + break; + case CONSTRAINT_SPACE_PARLOCAL: + Matrix4f parentLocalMatrix = tempVars.tempMat4; + if (bone.getParent() != null) { + Bone parent = bone.getParent(); + this.toMatrix(parent.getLocalPosition(), parent.getLocalRotation(), parent.getLocalScale(), parentLocalMatrix); + } else { + parentLocalMatrix.loadIdentity(); + } + Matrix4f boneLocalMatrix = this.toMatrix(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale(), tempVars.tempMat42); + Matrix4f resultMatrix = parentLocalMatrix.multLocal(boneLocalMatrix); + + Vector3f loc = resultMatrix.toTranslationVector(); + Quaternion rot = resultMatrix.toRotationQuat().normalizeLocal().multLocal(NEG_PARLOC_SPACE_QUATERNION); + Vector3f scl = resultMatrix.toScaleVector(); + result = new Transform(loc, rot, scl); + break; + default: + throw new IllegalStateException("Unknown space type: " + space); + } + tempVars.release(); + return result; + } else { + switch (space) { + case CONSTRAINT_SPACE_LOCAL: + return feature.getLocalTransform(); + case CONSTRAINT_SPACE_WORLD: + return feature.getWorldTransform(); + case CONSTRAINT_SPACE_PARLOCAL: + case CONSTRAINT_SPACE_POSE: + throw new IllegalStateException("Nodes can have only Local and World spaces applied!"); + default: + throw new IllegalStateException("Unknown space type: " + space); + } + } + } + + /** + * Applies transform to a feature (bone or spatial). Computations transform + * the given transformation from the given space to the feature's local + * space. + * + * @param oma + * the OMA of the feature we apply transformation to + * @param subtargetName + * the name of the feature's subtarget (bone in case of armature) + * @param space + * the space in which the given transform is to be applied + * @param transform + * the transform we apply + */ + public void applyTransform(Long oma, String subtargetName, Space space, Transform transform) { + Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE); + boolean isArmature = blenderContext.getMarkerValue(ArmatureHelper.ARMATURE_NODE_MARKER, feature) != null; + if (isArmature) { + Skeleton skeleton = blenderContext.getSkeleton(oma); + BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName); + Bone bone = targetBoneContext.getBone(); + + if (bone.getParent() == null && (space == Space.CONSTRAINT_SPACE_LOCAL || space == Space.CONSTRAINT_SPACE_PARLOCAL)) { + space = Space.CONSTRAINT_SPACE_POSE; + } + + TempVars tempVars = TempVars.get(); + switch (space) { + case CONSTRAINT_SPACE_LOCAL: + assert bone.getParent() != null : "CONSTRAINT_SPACE_LOCAL should be evaluated as CONSTRAINT_SPACE_POSE if the bone has no parent!"; + bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); + break; + case CONSTRAINT_SPACE_WORLD: { + Matrix4f boneMatrixInWorldSpace = this.toMatrix(transform, tempVars.tempMat4); + Matrix4f modelWorldMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42); + Matrix4f boneMatrixInModelSpace = modelWorldMatrix.invertLocal().multLocal(boneMatrixInWorldSpace); + Bone parent = bone.getParent(); + if (parent != null) { + Matrix4f parentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4); + boneMatrixInModelSpace = parentMatrixInModelSpace.invertLocal().multLocal(boneMatrixInModelSpace); + } + bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector()); + break; + } + case CONSTRAINT_SPACE_POSE: { + Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat4); + Matrix4f boneMatrixInWorldSpace = armatureWorldMatrix.multLocal(this.toMatrix(transform, tempVars.tempMat42)); + Matrix4f invertedModelMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42).invertLocal(); + Matrix4f boneMatrixInModelSpace = invertedModelMatrix.multLocal(boneMatrixInWorldSpace); + Bone parent = bone.getParent(); + if (parent != null) { + Matrix4f parentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4); + boneMatrixInModelSpace = parentMatrixInModelSpace.invertLocal().multLocal(boneMatrixInModelSpace); + } + bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector()); + break; + } + case CONSTRAINT_SPACE_PARLOCAL: + Matrix4f parentLocalInverseMatrix = tempVars.tempMat4; + if (bone.getParent() != null) { + this.toMatrix(bone.getParent().getLocalPosition(), bone.getParent().getLocalRotation(), bone.getParent().getLocalScale(), parentLocalInverseMatrix); + parentLocalInverseMatrix.invertLocal(); + } else { + parentLocalInverseMatrix.loadIdentity(); + } + Matrix4f m = this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale(), tempVars.tempMat42); + Matrix4f result = parentLocalInverseMatrix.multLocal(m); + Vector3f loc = result.toTranslationVector(); + Quaternion rot = result.toRotationQuat().normalizeLocal().multLocal(POS_PARLOC_SPACE_QUATERNION); + Vector3f scl = result.toScaleVector(); + bone.setBindTransforms(loc, rot, scl); + break; + default: + tempVars.release(); + throw new IllegalStateException("Invalid space type for target object: " + space.toString()); + } + tempVars.release(); + skeleton.updateWorldVectors(); + } else { + switch (space) { + case CONSTRAINT_SPACE_LOCAL: + feature.getLocalTransform().set(transform); + break; + case CONSTRAINT_SPACE_WORLD: + if (feature.getParent() == null) { + feature.setLocalTransform(transform); + } else { + Transform parentWorldTransform = feature.getParent().getWorldTransform(); + + TempVars tempVars = TempVars.get(); + Matrix4f parentInverseMatrix = this.toMatrix(parentWorldTransform, tempVars.tempMat4).invertLocal(); + Matrix4f m = this.toMatrix(transform, tempVars.tempMat42); + m = m.multLocal(parentInverseMatrix); + tempVars.release(); + + transform.setTranslation(m.toTranslationVector()); + transform.setRotation(m.toRotationQuat()); + transform.setScale(m.toScaleVector()); + + feature.setLocalTransform(transform); + } + break; + default: + throw new IllegalStateException("Invalid space type for spatial object: " + space.toString()); + } + } + } + + /** + * Converts given transform to the matrix. + * + * @param transform + * the transform to be converted + * @param store + * the matrix where the result will be stored + * @return the store matrix + */ + public Matrix4f toMatrix(Transform transform, Matrix4f store) { + if (transform != null) { + return this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale(), store); + } + store.loadIdentity(); + return store; + } + + /** + * Converts given transformation parameters into the matrix. + * + * @param position + * the position of the feature + * @param rotation + * the rotation of the feature + * @param scale + * the scale of the feature + * @param store + * the matrix where the result will be stored + * @return the store matrix + */ + private Matrix4f toMatrix(Vector3f position, Quaternion rotation, Vector3f scale, Matrix4f store) { + store.loadIdentity(); + store.setTranslation(position); + store.setRotationQuaternion(rotation); + store.setScale(scale); + return store; + } + + /** + * The space of target or owner transformation. + * + * @author Marcin Roguski (Kaelthas) + */ + public static enum Space { + /** A transformation of the bone or spatial in the world space. */ + CONSTRAINT_SPACE_WORLD, + /** + * For spatial it is the transformation in its parent space or in WORLD space if it has no parent. + * For bone it is a transformation in its bone parent space or in armature space if it has no parent. + */ + CONSTRAINT_SPACE_LOCAL, + /** + * This space IS NOT applicable for spatials. + * For bone it is a transformation in the blender's armature object space. + */ + CONSTRAINT_SPACE_POSE, + + CONSTRAINT_SPACE_PARLOCAL; + + /** + * This method returns the enum instance when given the appropriate + * value from the blend file. + * + * @param c + * the blender's value of the space modifier + * @return the scape enum instance + */ + public static Space valueOf(byte c) { + switch (c) { + case 0: + return CONSTRAINT_SPACE_WORLD; + case 1: + return CONSTRAINT_SPACE_LOCAL; + case 2: + return CONSTRAINT_SPACE_POSE; + case 3: + return CONSTRAINT_SPACE_PARLOCAL; + default: + throw new IllegalArgumentException("Value: " + c + " cannot be converted to Space enum instance!"); + } + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java new file mode 100644 index 000000000..f656150fa --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java @@ -0,0 +1,451 @@ +package com.jme3.scene.plugins.blender.constraints; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.animation.AnimChannel; +import com.jme3.animation.AnimControl; +import com.jme3.animation.Animation; +import com.jme3.animation.Bone; +import com.jme3.animation.BoneTrack; +import com.jme3.animation.Skeleton; +import com.jme3.animation.SpatialTrack; +import com.jme3.animation.Track; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.animations.ArmatureHelper; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.objects.ObjectHelper; +import com.jme3.util.TempVars; + +/** + * A node that represents either spatial or bone in constraint simulation. The + * node is applied its translation, rotation and scale for each frame of its + * animation. Then the constraints are applied that will eventually alter it. + * After that the feature's transformation is stored in VirtualTrack which is + * converted to new bone or spatial track at the very end. + * + * @author Marcin Roguski (Kaelthas) + */ +public class SimulationNode { + private static final Logger LOGGER = Logger.getLogger(SimulationNode.class.getName()); + + /** The blender context. */ + private BlenderContext blenderContext; + /** The name of the node (for debugging purposes). */ + private String name; + /** A list of children for the node (either bones or child spatials). */ + private List children = new ArrayList(); + /** A list of constraints that the current node has. */ + private List constraints; + /** A list of node's animations. */ + private List animations; + + /** The nodes spatial (if null then the boneContext should be set). */ + private Spatial spatial; + /** The skeleton of the bone (not null if the node simulated the bone). */ + private Skeleton skeleton; + /** Animation controller for the node's feature. */ + private AnimControl animControl; + + /** + * The star transform of a spatial. Needed to properly reset the spatial to + * its start position. + */ + private Transform spatialStartTransform; + /** Star transformations for bones. Needed to properly reset the bones. */ + private Map boneStartTransforms; + + /** + * Builds the nodes tree for the given feature. The feature (bone or + * spatial) is found by its OMA. The feature must be a root bone or a root + * spatial. + * + * @param featureOMA + * the OMA of either bone or spatial + * @param blenderContext + * the blender context + */ + public SimulationNode(Long featureOMA, BlenderContext blenderContext) { + this(featureOMA, blenderContext, true); + } + + /** + * Creates the node for the feature. + * + * @param featureOMA + * the OMA of either bone or spatial + * @param blenderContext + * the blender context + * @param rootNode + * indicates if the feature is a root bone or root spatial or not + */ + private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) { + this.blenderContext = blenderContext; + Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedFeatureDataType.LOADED_FEATURE); + if (blenderContext.getMarkerValue(ArmatureHelper.ARMATURE_NODE_MARKER, spatial) != null) { + skeleton = blenderContext.getSkeleton(featureOMA); + + Node nodeWithAnimationControl = blenderContext.getControlledNode(skeleton); + animControl = nodeWithAnimationControl.getControl(AnimControl.class); + + boneStartTransforms = new HashMap(); + for (int i = 0; i < skeleton.getBoneCount(); ++i) { + Bone bone = skeleton.getBone(i); + boneStartTransforms.put(bone, new Transform(bone.getWorldBindPosition(), bone.getWorldBindRotation(), bone.getWorldBindScale())); + } + } else { + if (rootNode && spatial.getParent() != null) { + throw new IllegalStateException("Given spatial must be a root node!"); + } + this.spatial = spatial; + spatialStartTransform = spatial.getLocalTransform().clone(); + } + + name = '>' + spatial.getName() + '<'; + + constraints = this.findConstraints(featureOMA, blenderContext); + if (constraints == null) { + constraints = new ArrayList(); + } + + // add children nodes + if (skeleton != null) { + // bone with index 0 is a root bone and should not be considered + // here + for (int i = 1; i < skeleton.getBoneCount(); ++i) { + BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(i)); + List boneConstraints = this.findConstraints(boneContext.getBoneOma(), blenderContext); + if (boneConstraints != null) { + constraints.addAll(boneConstraints); + } + } + + // each bone of the skeleton has the same anim data applied + BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(1)); + Long boneOma = boneContext.getBoneOma(); + animations = blenderContext.getAnimData(boneOma) == null ? null : blenderContext.getAnimData(boneOma).anims; + } else { + animations = blenderContext.getAnimData(featureOMA) == null ? null : blenderContext.getAnimData(featureOMA).anims; + for (Spatial child : spatial.getChildren()) { + if (child instanceof Node) { + children.add(new SimulationNode((Long) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, child), blenderContext, false)); + } + } + } + + LOGGER.info("Removing invalid constraints."); + List validConstraints = new ArrayList(constraints.size()); + for (Constraint constraint : constraints) { + if (constraint.validate()) { + validConstraints.add(constraint); + } else { + LOGGER.log(Level.WARNING, "Constraint {0} is invalid and will not be applied.", constraint.name); + } + } + constraints = validConstraints; + } + + /** + * Tells if the node already contains the given constraint (so that it is + * not applied twice). + * + * @param constraint + * the constraint to be checked + * @return true if the constraint already is stored in the node and + * false otherwise + */ + public boolean contains(Constraint constraint) { + boolean result = false; + if (constraints != null && constraints.size() > 0) { + for (Constraint c : constraints) { + if (c.equals(constraint)) { + return true; + } + } + } + return result; + } + + /** + * Resets the node's feature to its starting transformation. + */ + private void reset() { + if (spatial != null) { + spatial.setLocalTransform(spatialStartTransform); + for (SimulationNode child : children) { + child.reset(); + } + } else if (skeleton != null) { + for (Entry entry : boneStartTransforms.entrySet()) { + Transform t = entry.getValue(); + entry.getKey().setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale()); + } + skeleton.reset(); + } + } + + /** + * Simulates the spatial node. + */ + private void simulateSpatial() { + if (constraints != null && constraints.size() > 0) { + boolean applyStaticConstraints = true; + if (animations != null) { + for (Animation animation : animations) { + float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation); + int maxFrame = (int) animationTimeBoundaries[0]; + float maxTime = animationTimeBoundaries[1]; + + VirtualTrack vTrack = new VirtualTrack(maxFrame, maxTime); + for (Track track : animation.getTracks()) { + for (int frame = 0; frame < maxFrame; ++frame) { + spatial.setLocalTranslation(((SpatialTrack) track).getTranslations()[frame]); + spatial.setLocalRotation(((SpatialTrack) track).getRotations()[frame]); + spatial.setLocalScale(((SpatialTrack) track).getScales()[frame]); + + for (Constraint constraint : constraints) { + constraint.apply(frame); + vTrack.setTransform(frame, spatial.getLocalTransform()); + } + } + Track newTrack = vTrack.getAsSpatialTrack(); + if (newTrack != null) { + animation.removeTrack(track); + animation.addTrack(newTrack); + } + applyStaticConstraints = false; + } + } + } + + // if there are no animations then just constraint the static + // object's transformation + if (applyStaticConstraints) { + for (Constraint constraint : constraints) { + constraint.apply(0); + } + } + } + + for (SimulationNode child : children) { + child.simulate(); + } + } + + /** + * Simulates the bone node. + */ + private void simulateSkeleton() { + if (constraints != null && constraints.size() > 0) { + Set alteredOmas = new HashSet(); + + if (animations != null) { + TempVars vars = TempVars.get(); + AnimChannel animChannel = animControl.createChannel(); + for (Animation animation : animations) { + float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation); + int maxFrame = (int) animationTimeBoundaries[0]; + float maxTime = animationTimeBoundaries[1]; + + Map tracks = new HashMap(); + Map previousTransforms = this.getInitialTransforms(); + for (int frame = 0; frame < maxFrame; ++frame) { + // this MUST be done here, otherwise setting next frame of animation will + // lead to possible errors + this.reset(); + + // first set proper time for all bones in all the tracks ... + for (Track track : animation.getTracks()) { + float time = ((BoneTrack) track).getTimes()[frame]; + track.setTime(time, 1, animControl, animChannel, vars); + skeleton.updateWorldVectors(); + } + + + // ... and then apply constraints from the root bone to the last child ... + for (Bone rootBone : skeleton.getRoots()) { + if(skeleton.getBoneIndex(rootBone) > 0) { + //ommit the 0 - indexed root bone as it is the bone added by importer + this.applyConstraints(rootBone, alteredOmas, frame); + } + } + + // ... add virtual tracks if neccessary, for bones that were altered but had no tracks before ... + for (Long boneOMA : alteredOmas) { + BoneContext boneContext = blenderContext.getBoneContext(boneOMA); + int boneIndex = skeleton.getBoneIndex(boneContext.getBone()); + if (!tracks.containsKey(boneIndex)) { + tracks.put(boneIndex, new VirtualTrack(maxFrame, maxTime)); + } + } + alteredOmas.clear(); + + // ... and fill in another frame in the result track + for (Entry trackEntry : tracks.entrySet()) { + Integer boneIndex = trackEntry.getKey(); + Bone bone = skeleton.getBone(boneIndex); + + // take the initial transform of a bone and its virtual track + Transform previousTransform = previousTransforms.get(boneIndex); + VirtualTrack vTrack = trackEntry.getValue(); + + Vector3f bonePositionDifference = bone.getLocalPosition().subtract(previousTransform.getTranslation()); + Quaternion boneRotationDifference = bone.getLocalRotation().mult(previousTransform.getRotation().inverse()).normalizeLocal(); + Vector3f boneScaleDifference = bone.getLocalScale().divide(previousTransform.getScale()); + if (frame > 0) { + bonePositionDifference = vTrack.translations.get(frame - 1).add(bonePositionDifference); + boneRotationDifference = vTrack.rotations.get(frame - 1).mult(boneRotationDifference); + boneScaleDifference = vTrack.scales.get(frame - 1).mult(boneScaleDifference); + } + vTrack.setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference)); + + previousTransform.setTranslation(bone.getLocalPosition()); + previousTransform.setRotation(bone.getLocalRotation()); + previousTransform.setScale(bone.getLocalScale()); + } + } + + for (Entry trackEntry : tracks.entrySet()) { + Track newTrack = trackEntry.getValue().getAsBoneTrack(trackEntry.getKey()); + if (newTrack != null) { + boolean trackReplaced = false; + for (Track track : animation.getTracks()) { + if (((BoneTrack) track).getTargetBoneIndex() == trackEntry.getKey().intValue()) { + animation.removeTrack(track); + animation.addTrack(newTrack); + trackReplaced = true; + break; + } + } + if (!trackReplaced) { + animation.addTrack(newTrack); + } + } + } + } + vars.release(); + animControl.clearChannels(); + this.reset(); + } + } + } + + /** + * Applies constraints to the given bone and its children. + * The goal is to apply constraint from root bone to the last child. + * @param bone + * the bone whose constraints will be applied + * @param alteredOmas + * the set of OMAS of the altered bones (is populated if necessary) + * @param frame + * the current frame of the animation + */ + private void applyConstraints(Bone bone, Set alteredOmas, int frame) { + BoneContext boneContext = blenderContext.getBoneContext(bone); + List constraints = this.findConstraints(boneContext.getBoneOma(), blenderContext); + if (constraints != null && constraints.size() > 0) { + for (Constraint constraint : constraints) { + constraint.apply(frame); + if (constraint.getAlteredOmas() != null) { + alteredOmas.addAll(constraint.getAlteredOmas()); + } + alteredOmas.add(boneContext.getBoneOma()); + } + } + for (Bone child : bone.getChildren()) { + this.applyConstraints(child, alteredOmas, frame); + } + } + + /** + * Simulates the node. + */ + public void simulate() { + this.reset(); + if (spatial != null) { + this.simulateSpatial(); + } else { + this.simulateSkeleton(); + } + } + + /** + * Computes the maximum frame and time for the animation. Different tracks + * can have different lengths so here the maximum one is being found. + * + * @param animation + * the animation + * @return maximum frame and time of the animation + */ + private float[] computeAnimationTimeBoundaries(Animation animation) { + int maxFrame = Integer.MIN_VALUE; + float maxTime = Float.MIN_VALUE; + for (Track track : animation.getTracks()) { + if (track instanceof BoneTrack) { + maxFrame = Math.max(maxFrame, ((BoneTrack) track).getTranslations().length); + maxTime = Math.max(maxTime, ((BoneTrack) track).getTimes()[((BoneTrack) track).getTimes().length - 1]); + } else if (track instanceof SpatialTrack) { + maxFrame = Math.max(maxFrame, ((SpatialTrack) track).getTranslations().length); + maxTime = Math.max(maxTime, ((SpatialTrack) track).getTimes()[((SpatialTrack) track).getTimes().length - 1]); + } else { + throw new IllegalStateException("Unsupported track type for simuation: " + track); + } + } + return new float[] { maxFrame, maxTime }; + } + + /** + * Finds constraints for the node's features. + * + * @param ownerOMA + * the feature's OMA + * @param blenderContext + * the blender context + * @return a list of feature's constraints or empty list if none were found + */ + private List findConstraints(Long ownerOMA, BlenderContext blenderContext) { + List result = new ArrayList(); + List constraints = blenderContext.getConstraints(ownerOMA); + if(constraints != null) { + for (Constraint constraint : constraints) { + if (constraint.isImplemented() && constraint.validate()) { + result.add(constraint); + } else { + LOGGER.log(Level.WARNING, "Constraint named: ''{0}'' of type ''{1}'' is not implemented and will NOT be applied!", new Object[] { constraint.name, constraint.getConstraintTypeName() }); + } + } + } + return result.size() > 0 ? result : null; + } + + /** + * Creates the initial transforms for all bones in the skelketon. + * @return the map where the key is the bone index and the value us the bone's initial transformation + */ + private Map getInitialTransforms() { + Map result = new HashMap(); + for (int i = 0; i < skeleton.getBoneCount(); ++i) { + Bone bone = skeleton.getBone(i); + result.put(i, new Transform(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale())); + } + return result; + } + + @Override + public String toString() { + return name; + } +} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java new file mode 100644 index 000000000..98d5562b1 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java @@ -0,0 +1,35 @@ +package com.jme3.scene.plugins.blender.constraints; + +import java.util.logging.Logger; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.Ipo; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * Constraint applied on the skeleton. This constraint is here only to make the + * application not crash when loads constraints applied to armature. But + * skeleton movement is not supported by jme so the constraint will never be + * applied. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class SkeletonConstraint extends Constraint { + private static final Logger LOGGER = Logger.getLogger(SkeletonConstraint.class.getName()); + + public SkeletonConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { + super(constraintStructure, ownerOMA, influenceIpo, blenderContext); + } + + @Override + public boolean validate() { + LOGGER.warning("Constraints for skeleton are not supported."); + return false; + } + + @Override + public void apply(int frame) { + LOGGER.warning("Applying constraints to skeleton is not supported."); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java new file mode 100644 index 000000000..887986c4a --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java @@ -0,0 +1,27 @@ +package com.jme3.scene.plugins.blender.constraints; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.animations.Ipo; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * Constraint applied on the spatial objects. This includes: nodes, cameras + * nodes and light nodes. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class SpatialConstraint extends Constraint { + public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { + super(constraintStructure, ownerOMA, influenceIpo, blenderContext); + } + + @Override + public boolean validate() { + if (targetOMA != null) { + return blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) != null; + } + return true; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java new file mode 100644 index 000000000..944a3fab8 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java @@ -0,0 +1,146 @@ +package com.jme3.scene.plugins.blender.constraints; + +import java.util.ArrayList; + +import com.jme3.animation.BoneTrack; +import com.jme3.animation.SpatialTrack; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; + +/** + * A virtual track that stores computed frames after constraints are applied. + * Not all the frames need to be inserted. If there are lacks then the class + * will fill the gaps. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class VirtualTrack { + /** The last frame for the track. */ + public int maxFrame; + /** The max time for the track. */ + public float maxTime; + /** Translations of the track. */ + public ArrayList translations; + /** Rotations of the track. */ + public ArrayList rotations; + /** Scales of the track. */ + public ArrayList scales; + + /** + * Constructs the object storing the maximum frame and time. + * + * @param maxFrame + * the last frame for the track + * @param maxTime + * the max time for the track + */ + public VirtualTrack(int maxFrame, float maxTime) { + this.maxFrame = maxFrame; + this.maxTime = maxTime; + } + + /** + * Sets the transform for the given frame. + * + * @param frameIndex + * the frame for which the transform will be set + * @param transform + * the transformation to be set + */ + public void setTransform(int frameIndex, Transform transform) { + if (translations == null) { + translations = this.createList(Vector3f.ZERO, frameIndex); + } + this.append(translations, Vector3f.ZERO, frameIndex - translations.size()); + translations.add(transform.getTranslation().clone()); + + if (rotations == null) { + rotations = this.createList(Quaternion.IDENTITY, frameIndex); + } + this.append(rotations, Quaternion.IDENTITY, frameIndex - rotations.size()); + rotations.add(transform.getRotation().clone()); + + if (scales == null) { + scales = this.createList(Vector3f.UNIT_XYZ, frameIndex); + } + this.append(scales, Vector3f.UNIT_XYZ, frameIndex - scales.size()); + scales.add(transform.getScale().clone()); + } + + /** + * Returns the track as a bone track. + * + * @param targetBoneIndex + * the bone index + * @return the bone track + */ + public BoneTrack getAsBoneTrack(int targetBoneIndex) { + if (translations == null && rotations == null && scales == null) { + return null; + } + return new BoneTrack(targetBoneIndex, this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame])); + } + + /** + * Returns the track as a spatial track. + * + * @return the spatial track + */ + public SpatialTrack getAsSpatialTrack() { + if (translations == null && rotations == null && scales == null) { + return null; + } + return new SpatialTrack(this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame])); + } + + /** + * The method creates times for the track based on the given maximum values. + * + * @return the times for the track + */ + private float[] createTimes() { + float[] times = new float[maxFrame]; + float dT = maxTime / (float) maxFrame; + float t = 0; + for (int i = 0; i < maxFrame; ++i) { + times[i] = t; + t += dT; + } + return times; + } + + /** + * Helper method that creates a list of a given size filled with given + * elements. + * + * @param element + * the element to be put into the list + * @param count + * the list size + * @return the list + */ + private ArrayList createList(T element, int count) { + ArrayList result = new ArrayList(count); + for (int i = 0; i < count; ++i) { + result.add(element); + } + return result; + } + + /** + * Appends the element to the given list. + * + * @param list + * the list where the element will be appended + * @param element + * the element to be appended + * @param count + * how many times the element will be appended + */ + private void append(ArrayList list, T element, int count) { + for (int i = 0; i < count; ++i) { + list.add(element); + } + } +} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java new file mode 100644 index 000000000..7c51c8204 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java @@ -0,0 +1,135 @@ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import java.util.Set; + +import com.jme3.animation.Bone; +import com.jme3.math.Transform; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * A base class for all constraint definitions. + * + * @author Marcin Roguski (Kaelthas) + */ +public abstract class ConstraintDefinition { + protected ConstraintHelper constraintHelper; + /** Constraints flag. Used to load user's options applied to the constraint. */ + protected int flag; + /** The constraint's owner. Loaded during runtime. */ + private Object owner; + /** The blender context. */ + protected BlenderContext blenderContext; + /** The constraint's owner OMA. */ + protected Long ownerOMA; + /** Stores the OMA addresses of all features whose transform had been altered beside the constraint owner. */ + protected Set alteredOmas; + + /** + * Loads a constraint definition based on the constraint definition + * structure. + * + * @param constraintData + * the constraint definition structure + * @param ownerOMA + * the constraint's owner OMA + * @param blenderContext + * the blender context + */ + public ConstraintDefinition(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + if (constraintData != null) {// Null constraint has no data + Number flag = (Number) constraintData.getFieldValue("flag"); + if (flag != null) { + this.flag = flag.intValue(); + } + } + this.blenderContext = blenderContext; + constraintHelper = (ConstraintHelper) (blenderContext == null ? null : blenderContext.getHelper(ConstraintHelper.class)); + this.ownerOMA = ownerOMA; + } + + /** + * This method is here because we have no guarantee that the owner is loaded + * when constraint is being created. So use it to get the owner when it is + * needed for computations. + * + * @return the owner of the constraint or null if none is set + */ + protected Object getOwner() { + if (ownerOMA != null && owner == null) { + owner = blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); + if (owner == null) { + throw new IllegalStateException("Cannot load constraint's owner for constraint type: " + this.getClass().getName()); + } + } + return owner; + } + + /** + * The method gets the owner's transformation. The owner can be either bone or spatial. + * @param ownerSpace + * the space in which the computed transformation is given + * @return the constraint owner's transformation + */ + protected Transform getOwnerTransform(Space ownerSpace) { + if (this.getOwner() instanceof Bone) { + BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); + return constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace); + } + return constraintHelper.getTransform(ownerOMA, null, ownerSpace); + } + + /** + * The method applies the given transformation to the owner. + * @param ownerTransform + * the transformation to apply to the owner + * @param ownerSpace + * the space that defines which owner's transformation (ie. global, local, etc. will be set) + */ + protected void applyOwnerTransform(Transform ownerTransform, Space ownerSpace) { + if (this.getOwner() instanceof Bone) { + BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); + constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform); + } else { + constraintHelper.applyTransform(ownerOMA, null, ownerSpace, ownerTransform); + } + } + + /** + * @return true if the definition is implemented and false + * otherwise + */ + public boolean isImplemented() { + return true; + } + + /** + * @return a list of all OMAs of the features that the constraint had altered beside its owner + */ + public Set getAlteredOmas() { + return alteredOmas; + } + + /** + * @return the type name of the constraint + */ + public abstract String getConstraintTypeName(); + + /** + * Bakes the constraint for the current feature (bone or spatial) position. + * + * @param ownerSpace + * the space where owner transform will be evaluated in + * @param targetSpace + * the space where target transform will be evaluated in + * @param targetTransform + * the target transform used by some of the constraints + * @param influence + * the influence of the constraint (from range <0; 1>) + */ + public abstract void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence); +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java new file mode 100644 index 000000000..5185d1646 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java @@ -0,0 +1,77 @@ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import com.jme3.animation.Bone; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * This class represents 'Dist limit' constraint type in blender. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ConstraintDefinitionDistLimit extends ConstraintDefinition { + private static final int LIMITDIST_INSIDE = 0; + private static final int LIMITDIST_OUTSIDE = 1; + private static final int LIMITDIST_ONSURFACE = 2; + + protected int mode; + protected float dist; + + public ConstraintDefinitionDistLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); + mode = ((Number) constraintData.getFieldValue("mode")).intValue(); + dist = ((Number) constraintData.getFieldValue("dist")).floatValue(); + } + + @Override + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && + blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) { + // distance limit does not work on bones who are connected to their parent + return; + } + + Transform ownerTransform = this.getOwnerTransform(ownerSpace); + + Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation()); + float currentDistance = v.length(); + switch (mode) { + case LIMITDIST_INSIDE: + if (currentDistance >= dist) { + v.normalizeLocal(); + v.multLocal(dist + (currentDistance - dist) * (1.0f - influence)); + ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation())); + } + break; + case LIMITDIST_ONSURFACE: + if (currentDistance > dist) { + v.normalizeLocal(); + v.multLocal(dist + (currentDistance - dist) * (1.0f - influence)); + ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation())); + } else if (currentDistance < dist) { + v.normalizeLocal().multLocal(dist * influence); + ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v)); + } + break; + case LIMITDIST_OUTSIDE: + if (currentDistance <= dist) { + v = targetTransform.getTranslation().subtract(ownerTransform.getTranslation()).normalizeLocal().multLocal(dist * influence); + ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v)); + } + break; + default: + throw new IllegalStateException("Unknown distance limit constraint mode: " + mode); + } + + this.applyOwnerTransform(ownerTransform, ownerSpace); + } + + @Override + public String getConstraintTypeName() { + return "Limit distance"; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java new file mode 100644 index 000000000..080881ea1 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Structure; + +public class ConstraintDefinitionFactory { + private static final Map> CONSTRAINT_CLASSES = new HashMap>(); + static { + CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class); + CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class); + CONSTRAINT_CLASSES.put("bLocLimitConstraint", ConstraintDefinitionLocLimit.class); + CONSTRAINT_CLASSES.put("bNullConstraint", ConstraintDefinitionNull.class); + CONSTRAINT_CLASSES.put("bRotateLikeConstraint", ConstraintDefinitionRotLike.class); + CONSTRAINT_CLASSES.put("bRotLimitConstraint", ConstraintDefinitionRotLimit.class); + CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class); + CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class); + CONSTRAINT_CLASSES.put("bKinematicConstraint", ConstraintDefinitionIK.class); + } + + private static final Map UNSUPPORTED_CONSTRAINTS = new HashMap(); + static { + UNSUPPORTED_CONSTRAINTS.put("bActionConstraint", "Action"); + UNSUPPORTED_CONSTRAINTS.put("bChildOfConstraint", "Child of"); + UNSUPPORTED_CONSTRAINTS.put("bClampToConstraint", "Clamp to"); + UNSUPPORTED_CONSTRAINTS.put("bFollowPathConstraint", "Follow path"); + UNSUPPORTED_CONSTRAINTS.put("bLockTrackConstraint", "Lock track"); + UNSUPPORTED_CONSTRAINTS.put("bMinMaxConstraint", "Min max"); + UNSUPPORTED_CONSTRAINTS.put("bPythonConstraint", "Python/Script"); + UNSUPPORTED_CONSTRAINTS.put("bRigidBodyJointConstraint", "Rigid body joint"); + UNSUPPORTED_CONSTRAINTS.put("bShrinkWrapConstraint", "Shrinkwrap"); + UNSUPPORTED_CONSTRAINTS.put("bStretchToConstraint", "Stretch to"); + UNSUPPORTED_CONSTRAINTS.put("bTransformConstraint", "Transform"); + // Blender 2.50+ + UNSUPPORTED_CONSTRAINTS.put("bSplineIKConstraint", "Spline inverse kinematics"); + UNSUPPORTED_CONSTRAINTS.put("bDampTrackConstraint", "Damp track"); + UNSUPPORTED_CONSTRAINTS.put("bPivotConstraint", "Pivot"); + // Blender 2.56+ + UNSUPPORTED_CONSTRAINTS.put("bTrackToConstraint", "Track to"); + UNSUPPORTED_CONSTRAINTS.put("bSameVolumeConstraint", "Same volume"); + UNSUPPORTED_CONSTRAINTS.put("bTransLikeConstraint", "Trans like"); + // Blender 2.62+ + UNSUPPORTED_CONSTRAINTS.put("bCameraSolverConstraint", "Camera solver"); + UNSUPPORTED_CONSTRAINTS.put("bObjectSolverConstraint", "Object solver"); + UNSUPPORTED_CONSTRAINTS.put("bFollowTrackConstraint", "Follow track"); + } + + /** + * This method creates the constraint instance. + * + * @param constraintStructure + * the constraint's structure (bConstraint clss in blender 2.49). + * If the value is null the NullConstraint is created. + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, Long ownerOMA, BlenderContext blenderContext) throws BlenderFileException { + if (constraintStructure == null) { + return new ConstraintDefinitionNull(null, ownerOMA, blenderContext); + } + String constraintClassName = constraintStructure.getType(); + Class constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName); + if (constraintDefinitionClass != null) { + try { + return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, blenderContext); + } catch (IllegalArgumentException e) { + throw new BlenderFileException(e.getLocalizedMessage(), e); + } catch (SecurityException e) { + throw new BlenderFileException(e.getLocalizedMessage(), e); + } catch (InstantiationException e) { + throw new BlenderFileException(e.getLocalizedMessage(), e); + } catch (IllegalAccessException e) { + throw new BlenderFileException(e.getLocalizedMessage(), e); + } catch (InvocationTargetException e) { + throw new BlenderFileException(e.getLocalizedMessage(), e); + } + } else { + String constraintName = UNSUPPORTED_CONSTRAINTS.get(constraintClassName); + if (constraintName != null) { + return new UnsupportedConstraintDefinition(constraintName); + } else { + throw new BlenderFileException("Unknown constraint type: " + constraintClassName); + } + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java new file mode 100644 index 000000000..ac172f8e5 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java @@ -0,0 +1,117 @@ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import com.jme3.animation.Bone; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.file.Structure; + +public class ConstraintDefinitionIK extends ConstraintDefinition { + + private static final int FLAG_POSITION = 0x20; + + /** The number of affected bones. Zero means that all parent bones of the current bone should take part in baking. */ + private int bonesAffected; + private float chainLength; + private BoneContext[] bones; + private boolean needToCompute = true; + + public ConstraintDefinitionIK(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); + bonesAffected = ((Number) constraintData.getFieldValue("rootbone")).intValue(); + + if ((flag & FLAG_POSITION) == 0) { + needToCompute = false; + } + + if (needToCompute) { + alteredOmas = new HashSet(); + } + } + + @Override + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + if (needToCompute && influence != 0) { + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + BoneContext[] boneContexts = this.getBones(); + float b = chainLength; + Quaternion boneWorldRotation = new Quaternion(); + + for (int i = 0; i < boneContexts.length; ++i) { + Bone bone = boneContexts[i].getBone(); + + bone.updateWorldVectors(); + Transform boneWorldTransform = constraintHelper.getTransform(boneContexts[i].getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD); + + Vector3f head = boneWorldTransform.getTranslation(); + Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneContexts[i].getLength()))); + + Vector3f vectorA = tail.subtract(head); + float a = vectorA.length(); + vectorA.normalizeLocal(); + + Vector3f vectorC = targetTransform.getTranslation().subtract(head); + float c = vectorC.length(); + vectorC.normalizeLocal(); + + b -= a; + float theta = 0; + + if (c >= a + b) { + theta = vectorA.angleBetween(vectorC); + } else if (c <= FastMath.abs(a - b) && i < boneContexts.length - 1) { + theta = vectorA.angleBetween(vectorC) - FastMath.HALF_PI; + } else { + theta = vectorA.angleBetween(vectorC) - FastMath.acos(-(b * b - a * a - c * c) / (2 * a * c)); + } + + theta *= influence; + + if (theta != 0) { + Vector3f vectorR = vectorA.cross(vectorC); + boneWorldRotation.fromAngleAxis(theta, vectorR); + boneWorldTransform.getRotation().multLocal(boneWorldRotation); + constraintHelper.applyTransform(boneContexts[i].getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneWorldTransform); + } + + bone.updateWorldVectors(); + alteredOmas.add(boneContexts[i].getBoneOma()); + } + } + } + + @Override + public String getConstraintTypeName() { + return "Inverse kinematics"; + } + + /** + * @return the bone contexts of all bones that will be used in this constraint computations + */ + private BoneContext[] getBones() { + if (bones == null) { + List bones = new ArrayList(); + Bone bone = (Bone) this.getOwner(); + while (bone != null) { + BoneContext boneContext = blenderContext.getBoneContext(bone); + bones.add(0, boneContext); + chainLength += boneContext.getLength(); + if (bonesAffected != 0 && bones.size() >= bonesAffected) { + break; + } + bone = bone.getParent(); + } + this.bones = bones.toArray(new BoneContext[bones.size()]); + } + return bones; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java new file mode 100644 index 000000000..e2516e658 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java @@ -0,0 +1,96 @@ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import com.jme3.animation.Bone; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * This class represents 'Loc like' constraint type in blender. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ConstraintDefinitionLocLike extends ConstraintDefinition { + private static final int LOCLIKE_X = 0x01; + private static final int LOCLIKE_Y = 0x02; + private static final int LOCLIKE_Z = 0x04; + // protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in + // blender + private static final int LOCLIKE_X_INVERT = 0x10; + private static final int LOCLIKE_Y_INVERT = 0x20; + private static final int LOCLIKE_Z_INVERT = 0x40; + private static final int LOCLIKE_OFFSET = 0x80; + + public ConstraintDefinitionLocLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); + if (blenderContext.getBlenderKey().isFixUpAxis()) { + // swapping Y and X limits flag in the bitwise flag + int y = flag & LOCLIKE_Y; + int invY = flag & LOCLIKE_Y_INVERT; + int z = flag & LOCLIKE_Z; + int invZ = flag & LOCLIKE_Z_INVERT; + // clear the other flags to swap them + flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET; + + flag |= y << 1; + flag |= invY << 1; + flag |= z >> 1; + flag |= invZ >> 1; + } + } + + @Override + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && + blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) { + // location copy does not work on bones who are connected to their parent + return; + } + + Transform ownerTransform = this.getOwnerTransform(ownerSpace); + + Vector3f ownerLocation = ownerTransform.getTranslation(); + Vector3f targetLocation = targetTransform.getTranslation(); + + Vector3f startLocation = ownerTransform.getTranslation().clone(); + Vector3f offset = Vector3f.ZERO; + if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original location to the copied location + offset = startLocation; + } + + if ((flag & LOCLIKE_X) != 0) { + ownerLocation.x = targetLocation.x; + if ((flag & LOCLIKE_X_INVERT) != 0) { + ownerLocation.x = -ownerLocation.x; + } + } + if ((flag & LOCLIKE_Y) != 0) { + ownerLocation.y = targetLocation.y; + if ((flag & LOCLIKE_Y_INVERT) != 0) { + ownerLocation.y = -ownerLocation.y; + } + } + if ((flag & LOCLIKE_Z) != 0) { + ownerLocation.z = targetLocation.z; + if ((flag & LOCLIKE_Z_INVERT) != 0) { + ownerLocation.z = -ownerLocation.z; + } + } + ownerLocation.addLocal(offset); + + if (influence < 1.0f) { + startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); + ownerLocation.addLocal(startLocation); + } + + this.applyOwnerTransform(ownerTransform, ownerSpace); + } + + @Override + public String getConstraintTypeName() { + return "Copy location"; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java new file mode 100644 index 000000000..615fb104b --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java @@ -0,0 +1,95 @@ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import com.jme3.animation.Bone; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * This class represents 'Loc limit' constraint type in blender. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ConstraintDefinitionLocLimit extends ConstraintDefinition { + private static final int LIMIT_XMIN = 0x01; + private static final int LIMIT_XMAX = 0x02; + private static final int LIMIT_YMIN = 0x04; + private static final int LIMIT_YMAX = 0x08; + private static final int LIMIT_ZMIN = 0x10; + private static final int LIMIT_ZMAX = 0x20; + + protected float[][] limits = new float[3][2]; + + public ConstraintDefinitionLocLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); + if (blenderContext.getBlenderKey().isFixUpAxis()) { + limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); + limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); + limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); + limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); + + // swapping Y and X limits flag in the bitwise flag + int ymin = flag & LIMIT_YMIN; + int ymax = flag & LIMIT_YMAX; + int zmin = flag & LIMIT_ZMIN; + int zmax = flag & LIMIT_ZMAX; + flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap + // them + flag |= ymin << 2; + flag |= ymax << 2; + flag |= zmin >> 2; + flag |= zmax >> 2; + } else { + limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); + limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); + limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); + limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); + } + } + + @Override + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && + blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) { + // location limit does not work on bones who are connected to their parent + return; + } + + Transform ownerTransform = this.getOwnerTransform(ownerSpace); + + Vector3f translation = ownerTransform.getTranslation(); + + if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) { + translation.x -= (translation.x - limits[0][0]) * influence; + } + if ((flag & LIMIT_XMAX) != 0 && translation.x > limits[0][1]) { + translation.x -= (translation.x - limits[0][1]) * influence; + } + if ((flag & LIMIT_YMIN) != 0 && translation.y < limits[1][0]) { + translation.y -= (translation.y - limits[1][0]) * influence; + } + if ((flag & LIMIT_YMAX) != 0 && translation.y > limits[1][1]) { + translation.y -= (translation.y - limits[1][1]) * influence; + } + if ((flag & LIMIT_ZMIN) != 0 && translation.z < limits[2][0]) { + translation.z -= (translation.z - limits[2][0]) * influence; + } + if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) { + translation.z -= (translation.z - limits[2][1]) * influence; + } + + this.applyOwnerTransform(ownerTransform, ownerSpace); + } + + @Override + public String getConstraintTypeName() { + return "Limit location"; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java new file mode 100644 index 000000000..6744a7884 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java @@ -0,0 +1,28 @@ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import com.jme3.math.Transform; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * This class represents 'Null' constraint type in blender. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ConstraintDefinitionNull extends ConstraintDefinition { + + public ConstraintDefinitionNull(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); + } + + @Override + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + // null constraint does nothing so no need to implement this one + } + + @Override + public String getConstraintTypeName() { + return "Null"; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java new file mode 100644 index 000000000..24a91bfb9 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java @@ -0,0 +1,78 @@ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * This class represents 'Rot like' constraint type in blender. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ConstraintDefinitionRotLike extends ConstraintDefinition { + private static final int ROTLIKE_X = 0x01; + private static final int ROTLIKE_Y = 0x02; + private static final int ROTLIKE_Z = 0x04; + private static final int ROTLIKE_X_INVERT = 0x10; + private static final int ROTLIKE_Y_INVERT = 0x20; + private static final int ROTLIKE_Z_INVERT = 0x40; + private static final int ROTLIKE_OFFSET = 0x80; + + private transient float[] ownerAngles = new float[3]; + private transient float[] targetAngles = new float[3]; + + public ConstraintDefinitionRotLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); + } + + @Override + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + Transform ownerTransform = this.getOwnerTransform(ownerSpace); + + Quaternion ownerRotation = ownerTransform.getRotation(); + ownerAngles = ownerRotation.toAngles(ownerAngles); + targetAngles = targetTransform.getRotation().toAngles(targetAngles); + + Quaternion startRotation = ownerRotation.clone(); + Quaternion offset = Quaternion.IDENTITY; + if ((flag & ROTLIKE_OFFSET) != 0) {// we add the original rotation to + // the copied rotation + offset = startRotation; + } + + if ((flag & ROTLIKE_X) != 0) { + ownerAngles[0] = targetAngles[0]; + if ((flag & ROTLIKE_X_INVERT) != 0) { + ownerAngles[0] = -ownerAngles[0]; + } + } + if ((flag & ROTLIKE_Y) != 0) { + ownerAngles[1] = targetAngles[1]; + if ((flag & ROTLIKE_Y_INVERT) != 0) { + ownerAngles[1] = -ownerAngles[1]; + } + } + if ((flag & ROTLIKE_Z) != 0) { + ownerAngles[2] = targetAngles[2]; + if ((flag & ROTLIKE_Z_INVERT) != 0) { + ownerAngles[2] = -ownerAngles[2]; + } + } + ownerRotation.fromAngles(ownerAngles).multLocal(offset); + + if (influence < 1.0f) { + // startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); + // ownerLocation.addLocal(startLocation); + // TODO + } + + this.applyOwnerTransform(ownerTransform, ownerSpace); + } + + @Override + public String getConstraintTypeName() { + return "Copy rotation"; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java new file mode 100644 index 000000000..55a8f8147 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java @@ -0,0 +1,119 @@ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import com.jme3.math.FastMath; +import com.jme3.math.Transform; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * This class represents 'Rot limit' constraint type in blender. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ConstraintDefinitionRotLimit extends ConstraintDefinition { + private static final int LIMIT_XROT = 0x01; + private static final int LIMIT_YROT = 0x02; + private static final int LIMIT_ZROT = 0x04; + + private transient float[][] limits = new float[3][2]; + private transient float[] angles = new float[3]; + + public ConstraintDefinitionRotLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); + if (blenderContext.getBlenderKey().isFixUpAxis()) { + limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); + limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); + limits[2][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[2][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); + limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); + + // swapping Y and X limits flag in the bitwise flag + int limitY = flag & LIMIT_YROT; + int limitZ = flag & LIMIT_ZROT; + flag &= LIMIT_XROT;// clear the other flags to swap them + flag |= limitY << 1; + flag |= limitZ >> 1; + } else { + limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); + limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); + limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); + limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); + } + + // until blender 2.49 the rotations values were stored in degrees + if (blenderContext.getBlenderVersion() <= 249) { + for (int i = 0; i < 3; ++i) { + limits[i][0] *= FastMath.DEG_TO_RAD; + limits[i][1] *= FastMath.DEG_TO_RAD; + } + } + + // make sure that the limits are always in range [0, 2PI) + // TODO: left it here because it is essential to make sure all cases + // work poperly + // but will do it a little bit later ;) + /* + * for (int i = 0; i < 3; ++i) { for (int j = 0; j < 2; ++j) { int + * multFactor = (int)Math.abs(limits[i][j] / FastMath.TWO_PI) ; if + * (limits[i][j] < 0) { limits[i][j] += FastMath.TWO_PI * (multFactor + + * 1); } else { limits[i][j] -= FastMath.TWO_PI * multFactor; } } //make + * sure the lower limit is not greater than the upper one + * if(limits[i][0] > limits[i][1]) { float temp = limits[i][0]; + * limits[i][0] = limits[i][1]; limits[i][1] = temp; } } + */ + } + + @Override + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + Transform ownerTransform = this.getOwnerTransform(ownerSpace); + + ownerTransform.getRotation().toAngles(angles); + // make sure that the rotations are always in range [0, 2PI) + // TODO: same comment as in constructor + /* + * for (int i = 0; i < 3; ++i) { int multFactor = + * (int)Math.abs(angles[i] / FastMath.TWO_PI) ; if(angles[i] < 0) { + * angles[i] += FastMath.TWO_PI * (multFactor + 1); } else { angles[i] + * -= FastMath.TWO_PI * multFactor; } } + */ + if ((flag & LIMIT_XROT) != 0) { + float difference = 0.0f; + if (angles[0] < limits[0][0]) { + difference = (angles[0] - limits[0][0]) * influence; + } else if (angles[0] > limits[0][1]) { + difference = (angles[0] - limits[0][1]) * influence; + } + angles[0] -= difference; + } + if ((flag & LIMIT_YROT) != 0) { + float difference = 0.0f; + if (angles[1] < limits[1][0]) { + difference = (angles[1] - limits[1][0]) * influence; + } else if (angles[1] > limits[1][1]) { + difference = (angles[1] - limits[1][1]) * influence; + } + angles[1] -= difference; + } + if ((flag & LIMIT_ZROT) != 0) { + float difference = 0.0f; + if (angles[2] < limits[2][0]) { + difference = (angles[2] - limits[2][0]) * influence; + } else if (angles[2] > limits[2][1]) { + difference = (angles[2] - limits[2][1]) * influence; + } + angles[2] -= difference; + } + ownerTransform.getRotation().fromAngles(angles); + + this.applyOwnerTransform(ownerTransform, ownerSpace); + } + + @Override + public String getConstraintTypeName() { + return "Limit rotation"; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java new file mode 100644 index 000000000..2b86a5c86 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java @@ -0,0 +1,64 @@ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * This class represents 'Size like' constraint type in blender. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ConstraintDefinitionSizeLike extends ConstraintDefinition { + private static final int SIZELIKE_X = 0x01; + private static final int SIZELIKE_Y = 0x02; + private static final int SIZELIKE_Z = 0x04; + private static final int LOCLIKE_OFFSET = 0x80; + + public ConstraintDefinitionSizeLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); + if (blenderContext.getBlenderKey().isFixUpAxis()) { + // swapping Y and X limits flag in the bitwise flag + int y = flag & SIZELIKE_Y; + int z = flag & SIZELIKE_Z; + flag &= SIZELIKE_X | LOCLIKE_OFFSET;// clear the other flags to swap + // them + flag |= y << 1; + flag |= z >> 1; + } + } + + @Override + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + Transform ownerTransform = this.getOwnerTransform(ownerSpace); + + Vector3f ownerScale = ownerTransform.getScale(); + Vector3f targetScale = targetTransform.getScale(); + + Vector3f offset = Vector3f.ZERO; + if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original scale to the + // copied scale + offset = ownerScale.clone(); + } + + if ((flag & SIZELIKE_X) != 0) { + ownerScale.x = targetScale.x * influence + (1.0f - influence) * ownerScale.x; + } + if ((flag & SIZELIKE_Y) != 0) { + ownerScale.y = targetScale.y * influence + (1.0f - influence) * ownerScale.y; + } + if ((flag & SIZELIKE_Z) != 0) { + ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z; + } + ownerScale.addLocal(offset); + + this.applyOwnerTransform(ownerTransform, ownerSpace); + } + + @Override + public String getConstraintTypeName() { + return "Copy scale"; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java new file mode 100644 index 000000000..564f77e70 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java @@ -0,0 +1,86 @@ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * This class represents 'Size limit' constraint type in blender. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ConstraintDefinitionSizeLimit extends ConstraintDefinition { + private static final int LIMIT_XMIN = 0x01; + private static final int LIMIT_XMAX = 0x02; + private static final int LIMIT_YMIN = 0x04; + private static final int LIMIT_YMAX = 0x08; + private static final int LIMIT_ZMIN = 0x10; + private static final int LIMIT_ZMAX = 0x20; + + protected transient float[][] limits = new float[3][2]; + + public ConstraintDefinitionSizeLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); + if (blenderContext.getBlenderKey().isFixUpAxis()) { + limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); + limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); + limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); + limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); + + // swapping Y and X limits flag in the bitwise flag + int ymin = flag & LIMIT_YMIN; + int ymax = flag & LIMIT_YMAX; + int zmin = flag & LIMIT_ZMIN; + int zmax = flag & LIMIT_ZMAX; + flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap + // them + flag |= ymin << 2; + flag |= ymax << 2; + flag |= zmin >> 2; + flag |= zmax >> 2; + } else { + limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); + limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); + limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); + limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); + } + } + + @Override + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + Transform ownerTransform = this.getOwnerTransform(ownerSpace); + + Vector3f scale = ownerTransform.getScale(); + if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) { + scale.x -= (scale.x - limits[0][0]) * influence; + } + if ((flag & LIMIT_XMAX) != 0 && scale.x > limits[0][1]) { + scale.x -= (scale.x - limits[0][1]) * influence; + } + if ((flag & LIMIT_YMIN) != 0 && scale.y < limits[1][0]) { + scale.y -= (scale.y - limits[1][0]) * influence; + } + if ((flag & LIMIT_YMAX) != 0 && scale.y > limits[1][1]) { + scale.y -= (scale.y - limits[1][1]) * influence; + } + if ((flag & LIMIT_ZMIN) != 0 && scale.z < limits[2][0]) { + scale.z -= (scale.z - limits[2][0]) * influence; + } + if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) { + scale.z -= (scale.z - limits[2][1]) * influence; + } + + this.applyOwnerTransform(ownerTransform, ownerSpace); + } + + @Override + public String getConstraintTypeName() { + return "Limit scale"; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java new file mode 100644 index 000000000..3002cfb88 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java @@ -0,0 +1,34 @@ +package com.jme3.scene.plugins.blender.constraints.definitions; + +import com.jme3.math.Transform; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; + +/** + * This class represents a constraint that is defined by blender but not + * supported by either importer ot jme. It only wirtes down a warning when + * baking is called. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class UnsupportedConstraintDefinition extends ConstraintDefinition { + private String typeName; + + public UnsupportedConstraintDefinition(String typeName) { + super(null, null, null); + this.typeName = typeName; + } + + @Override + public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { + } + + @Override + public boolean isImplemented() { + return false; + } + + @Override + public String getConstraintTypeName() { + return typeName; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java new file mode 100644 index 000000000..3034e1789 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java @@ -0,0 +1,146 @@ +package com.jme3.scene.plugins.blender.curves; + +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.Structure; +import java.util.ArrayList; +import java.util.List; + +/** + * A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize + * floating point operations errors. + * @author Marcin Roguski (Kaelthas) + */ +public class BezierCurve { + + public static final int X_VALUE = 0; + public static final int Y_VALUE = 1; + public static final int Z_VALUE = 2; + /** + * The type of the curve. Describes the data it modifies. + * Used in ipos calculations. + */ + private int type; + /** The dimension of the curve. */ + private int dimension; + /** A table of the bezier points. */ + private float[][][] bezierPoints; + /** Array that stores a radius for each bezier triple. */ + private float[] radiuses; + + @SuppressWarnings("unchecked") + public BezierCurve(final int type, final List bezTriples, final int dimension) { + if (dimension != 2 && dimension != 3) { + throw new IllegalArgumentException("The dimension of the curve should be 2 or 3!"); + } + this.type = type; + this.dimension = dimension; + // first index of the bezierPoints table has the length of triples amount + // the second index points to a table od three points of a bezier triple (handle, point, handle) + // the third index specifies the coordinates of the specific point in a bezier triple + bezierPoints = new float[bezTriples.size()][3][dimension]; + radiuses = new float[bezTriples.size()]; + int i = 0, j, k; + for (Structure bezTriple : bezTriples) { + DynamicArray vec = (DynamicArray) bezTriple.getFieldValue("vec"); + for (j = 0; j < 3; ++j) { + for (k = 0; k < dimension; ++k) { + bezierPoints[i][j][k] = vec.get(j, k).floatValue(); + } + } + radiuses[i++] = ((Number) bezTriple.getFieldValue("radius")).floatValue(); + } + } + + /** + * This method evaluates the data for the specified frame. The Y value is returned. + * @param frame + * the frame for which the value is being calculated + * @param valuePart + * this param specifies wheather we should return the X, Y or Z part of the result value; it should have + * one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result + * Z_VALUE - the Z factor of the result + * @return the value of the curve + */ + public float evaluate(int frame, int valuePart) { + for (int i = 0; i < bezierPoints.length - 1; ++i) { + if (frame >= bezierPoints[i][1][0] && frame <= bezierPoints[i + 1][1][0]) { + float t = (frame - bezierPoints[i][1][0]) / (bezierPoints[i + 1][1][0] - bezierPoints[i][1][0]); + float oneMinusT = 1.0f - t; + float oneMinusT2 = oneMinusT * oneMinusT; + float t2 = t * t; + return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t; + } + } + if (frame < bezierPoints[0][1][0]) { + return bezierPoints[0][1][1]; + } else { // frame>bezierPoints[bezierPoints.length-1][1][0] + return bezierPoints[bezierPoints.length - 1][1][1]; + } + } + + /** + * This method returns the frame where last bezier triple center point of the bezier curve is located. + * @return the frame number of the last defined bezier triple point for the curve + */ + public int getLastFrame() { + return (int) bezierPoints[bezierPoints.length - 1][1][0]; + } + + /** + * This method returns the type of the bezier curve. The type describes the parameter that this curve modifies + * (ie. LocationX or rotationW of the feature). + * @return the type of the bezier curve + */ + public int getType() { + return type; + } + + /** + * The method returns the radius for the required bezier triple. + * + * @param bezierTripleIndex + * index of the bezier triple + * @return radius of the required bezier triple + */ + public float getRadius(int bezierTripleIndex) { + return radiuses[bezierTripleIndex]; + } + + /** + * This method returns a list of control points for this curve. + * @return a list of control points for this curve. + */ + public List getControlPoints() { + List controlPoints = new ArrayList(bezierPoints.length * 3); + for (int i = 0; i < bezierPoints.length; ++i) { + controlPoints.add(new Vector3f(bezierPoints[i][0][0], bezierPoints[i][0][1], bezierPoints[i][0][2])); + controlPoints.add(new Vector3f(bezierPoints[i][1][0], bezierPoints[i][1][1], bezierPoints[i][1][2])); + controlPoints.add(new Vector3f(bezierPoints[i][2][0], bezierPoints[i][2][1], bezierPoints[i][2][2])); + } + return controlPoints; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Bezier curve: ").append(type).append('\n'); + for (int i = 0; i < bezierPoints.length; ++i) { + sb.append(this.toStringBezTriple(i)).append('\n'); + } + return sb.toString(); + } + + /** + * This method converts the bezier triple of a specified index into text. + * @param tripleIndex + * index of the triple + * @return text representation of the triple + */ + private String toStringBezTriple(int tripleIndex) { + if (this.dimension == 2) { + return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ")]"; + } else { + return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ", " + bezierPoints[tripleIndex][0][2] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ", " + bezierPoints[tripleIndex][1][2] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ", " + bezierPoints[tripleIndex][2][2] + ")]"; + } + } +} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesHelper.java new file mode 100644 index 000000000..3e889368f --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesHelper.java @@ -0,0 +1,838 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.curves; + +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.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.logging.Logger; + +import com.jme3.material.Material; +import com.jme3.material.RenderState.FaceCullMode; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Spline; +import com.jme3.math.Spline.SplineType; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.BlenderInputStream; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialContext; +import com.jme3.scene.plugins.blender.materials.MaterialHelper; +import com.jme3.scene.plugins.blender.objects.Properties; +import com.jme3.scene.shape.Curve; +import com.jme3.scene.shape.Surface; +import com.jme3.util.BufferUtils; + +/** + * A class that is used in mesh calculations. + * + * @author Marcin Roguski (Kaelthas) + */ +public class CurvesHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(CurvesHelper.class.getName()); + + /** Minimum basis U function degree for NURBS curves and surfaces. */ + protected int minimumBasisUFunctionDegree = 4; + /** Minimum basis V function degree for NURBS curves and surfaces. */ + protected int minimumBasisVFunctionDegree = 4; + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in + * different blender versions. + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public CurvesHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + /** + * This method converts given curve structure into a list of geometries representing the curve. The list is used here because on object + * can have several separate curves. + * @param curveStructure + * the curve structure + * @param blenderContext + * the blender context + * @return a list of geometries repreenting a single curve object + * @throws BlenderFileException + */ + public List toCurve(Structure curveStructure, BlenderContext blenderContext) throws BlenderFileException { + String name = curveStructure.getName(); + int flag = ((Number) curveStructure.getFieldValue("flag")).intValue(); + boolean is3D = (flag & 0x01) != 0; + boolean isFront = (flag & 0x02) != 0 && !is3D; + boolean isBack = (flag & 0x04) != 0 && !is3D; + if (isFront) { + LOGGER.warning("No front face in curve implemented yet!");// TODO: implement front face + } + if (isBack) { + LOGGER.warning("No back face in curve implemented yet!");// TODO: implement back face + } + + // reading nurbs (and sorting them by material) + List nurbStructures = ((Structure) curveStructure.getFieldValue("nurb")).evaluateListBase(); + Map> nurbs = new HashMap>(); + for (Structure nurb : nurbStructures) { + Number matNumber = (Number) nurb.getFieldValue("mat_nr"); + List nurbList = nurbs.get(matNumber); + if (nurbList == null) { + nurbList = new ArrayList(); + nurbs.put(matNumber, nurbList); + } + nurbList.add(nurb); + } + + // getting materials + MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + MaterialContext[] materialContexts = materialHelper.getMaterials(curveStructure, blenderContext); + Material defaultMaterial = null; + if (materialContexts != null) { + for (MaterialContext materialContext : materialContexts) { + materialContext.setFaceCullMode(FaceCullMode.Off); + } + } else { + defaultMaterial = blenderContext.getDefaultMaterial().clone(); + defaultMaterial.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off); + } + + // getting or creating bevel object + List bevelObject = null; + Pointer pBevelObject = (Pointer) curveStructure.getFieldValue("bevobj"); + if (pBevelObject.isNotNull()) { + Pointer pBevelStructure = (Pointer) pBevelObject.fetchData().get(0).getFieldValue("data"); + Structure bevelStructure = pBevelStructure.fetchData().get(0); + bevelObject = this.toCurve(bevelStructure, blenderContext); + } else { + int bevResol = ((Number) curveStructure.getFieldValue("bevresol")).intValue(); + float extrude = ((Number) curveStructure.getFieldValue("ext1")).floatValue(); + float bevelDepth = ((Number) curveStructure.getFieldValue("ext2")).floatValue(); + if (bevelDepth > 0.0f) { + float handlerLength = bevelDepth / 2.0f; + + List conrtolPoints = new ArrayList(extrude > 0.0f ? 19 : 13); + if (extrude > 0.0f) { + conrtolPoints.add(new Vector3f(-bevelDepth, 0, extrude)); + conrtolPoints.add(new Vector3f(-bevelDepth, 0, -handlerLength + extrude)); + conrtolPoints.add(new Vector3f(-bevelDepth, 0, handlerLength - extrude)); + } + + conrtolPoints.add(new Vector3f(-bevelDepth, 0, -extrude)); + conrtolPoints.add(new Vector3f(-bevelDepth, 0, -handlerLength - extrude)); + + conrtolPoints.add(new Vector3f(-handlerLength, 0, -bevelDepth - extrude)); + conrtolPoints.add(new Vector3f(0, 0, -bevelDepth - extrude)); + conrtolPoints.add(new Vector3f(handlerLength, 0, -bevelDepth - extrude)); + + if (extrude > 0.0f) { + conrtolPoints.add(new Vector3f(bevelDepth, 0, -extrude - handlerLength)); + conrtolPoints.add(new Vector3f(bevelDepth, 0, -extrude)); + conrtolPoints.add(new Vector3f(bevelDepth, 0, -extrude + handlerLength)); + } + + conrtolPoints.add(new Vector3f(bevelDepth, 0, extrude - handlerLength)); + conrtolPoints.add(new Vector3f(bevelDepth, 0, extrude)); + conrtolPoints.add(new Vector3f(bevelDepth, 0, extrude + handlerLength)); + + conrtolPoints.add(new Vector3f(handlerLength, 0, bevelDepth + extrude)); + conrtolPoints.add(new Vector3f(0, 0, bevelDepth + extrude)); + conrtolPoints.add(new Vector3f(-handlerLength, 0, bevelDepth + extrude)); + + conrtolPoints.add(new Vector3f(-bevelDepth, 0, handlerLength + extrude)); + conrtolPoints.add(new Vector3f(-bevelDepth, 0, extrude)); + + Spline bevelSpline = new Spline(SplineType.Bezier, conrtolPoints, 0, false); + Curve bevelCurve = new Curve(bevelSpline, bevResol); + bevelObject = new ArrayList(1); + bevelObject.add(new Geometry("", bevelCurve)); + } else if (extrude > 0.0f) { + Spline bevelSpline = new Spline(SplineType.Linear, new Vector3f[] { new Vector3f(0, 0, -extrude), new Vector3f(0, 0, extrude) }, 1, false); + Curve bevelCurve = new Curve(bevelSpline, bevResol); + bevelObject = new ArrayList(1); + bevelObject.add(new Geometry("", bevelCurve)); + } + } + + // getting taper object + Spline taperObject = null; + Pointer pTaperObject = (Pointer) curveStructure.getFieldValue("taperobj"); + if (bevelObject != null && pTaperObject.isNotNull()) { + Pointer pTaperStructure = (Pointer) pTaperObject.fetchData().get(0).getFieldValue("data"); + Structure taperStructure = pTaperStructure.fetchData().get(0); + taperObject = this.loadTaperObject(taperStructure); + } + + Vector3f loc = this.getLoc(curveStructure); + // creating the result curves + List result = new ArrayList(nurbs.size()); + for (Entry> nurbEntry : nurbs.entrySet()) { + for (Structure nurb : nurbEntry.getValue()) { + int type = ((Number) nurb.getFieldValue("type")).intValue(); + List nurbGeoms = null; + if ((type & 0x01) != 0) {// Bezier curve + nurbGeoms = this.loadBezierCurve(loc, nurb, bevelObject, taperObject, blenderContext); + } else if ((type & 0x04) != 0) {// NURBS + nurbGeoms = this.loadNurb(loc, nurb, bevelObject, taperObject, blenderContext); + } + if (nurbGeoms != null) {// setting the name and assigning materials + for (Geometry nurbGeom : nurbGeoms) { + if (materialContexts != null) { + materialContexts[nurbEntry.getKey().intValue()].applyMaterial(nurbGeom, curveStructure.getOldMemoryAddress(), null, blenderContext); + } else { + nurbGeom.setMaterial(defaultMaterial); + } + nurbGeom.setName(name); + result.add(nurbGeom); + } + } + } + } + + // reading custom properties + if (blenderContext.getBlenderKey().isLoadObjectProperties() && result.size() > 0) { + Properties properties = this.loadProperties(curveStructure, blenderContext); + // the loaded property is a group property, so we need to get each value and set it to every geometry of the curve + if (properties != null && properties.getValue() != null) { + for(Geometry geom : result) { + this.applyProperties(geom, properties); + } + } + } + + return result; + } + + /** + * This method loads the bezier curve. + * @param loc + * the translation of the curve + * @param nurb + * the nurb structure + * @param bevelObject + * the bevel object + * @param taperObject + * the taper object + * @param blenderContext + * the blender context + * @return a list of geometries representing the curves + * @throws BlenderFileException + * an exception is thrown when there are problems with the blender file + */ + protected List loadBezierCurve(Vector3f loc, Structure nurb, List bevelObject, Spline taperObject, BlenderContext blenderContext) throws BlenderFileException { + Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt"); + List result = new ArrayList(); + if (pBezierTriple.isNotNull()) { + boolean smooth = (((Number) nurb.getFlatFieldValue("flag")).intValue() & 0x01) != 0; + int resolution = ((Number) nurb.getFieldValue("resolu")).intValue(); + boolean cyclic = (((Number) nurb.getFieldValue("flagu")).intValue() & 0x01) != 0; + + // creating the curve object + BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(), 3); + List controlPoints = bezierCurve.getControlPoints(); + if (fixUpAxis) { + for (Vector3f v : controlPoints) { + float y = v.y; + v.y = v.z; + v.z = -y; + } + } + + if (bevelObject != null && taperObject == null) {// create taper object using the scales of the bezier triple + int triplesCount = controlPoints.size() / 3; + List taperControlPoints = new ArrayList(triplesCount); + for (int i = 0; i < triplesCount; ++i) { + taperControlPoints.add(new Vector3f(controlPoints.get(i * 3 + 1).x, bezierCurve.getRadius(i), 0)); + } + taperObject = new Spline(SplineType.Linear, taperControlPoints, 0, false); + } + + if (cyclic) { + // copy the first three points at the end + for (int i = 0; i < 3; ++i) { + controlPoints.add(controlPoints.get(i)); + } + } + // removing the first and last handles + controlPoints.remove(0); + controlPoints.remove(controlPoints.size() - 1); + + // creating curve + Spline spline = new Spline(SplineType.Bezier, controlPoints, 0, false); + Curve curve = new Curve(spline, resolution); + if (bevelObject == null) {// creating a normal curve + Geometry curveGeometry = new Geometry(null, curve); + result.add(curveGeometry); + // TODO: use front and back flags; surface excluding algorithm for bezier circles should be added + } else {// creating curve with bevel and taper shape + result = this.applyBevelAndTaper(curve, bevelObject, taperObject, smooth, blenderContext); + } + } + return result; + } + + /** + * This method loads the NURBS curve or surface. + * @param loc + * object's location + * @param nurb + * the NURBS data structure + * @param bevelObject + * the bevel object to be applied + * @param taperObject + * the taper object to be applied + * @param blenderContext + * the blender context + * @return a list of geometries that represents the loaded NURBS curve or surface + * @throws BlenderFileException + * an exception is throw when problems with blender loaded data occurs + */ + @SuppressWarnings("unchecked") + protected List loadNurb(Vector3f loc, Structure nurb, List bevelObject, Spline taperObject, BlenderContext blenderContext) throws BlenderFileException { + // loading the knots + List[] knots = new List[2]; + Pointer[] pKnots = new Pointer[] { (Pointer) nurb.getFieldValue("knotsu"), (Pointer) nurb.getFieldValue("knotsv") }; + for (int i = 0; i < knots.length; ++i) { + if (pKnots[i].isNotNull()) { + FileBlockHeader fileBlockHeader = blenderContext.getFileBlock(pKnots[i].getOldMemoryAddress()); + BlenderInputStream blenderInputStream = blenderContext.getInputStream(); + blenderInputStream.setPosition(fileBlockHeader.getBlockPosition()); + int knotsAmount = fileBlockHeader.getCount() * fileBlockHeader.getSize() / 4; + knots[i] = new ArrayList(knotsAmount); + for (int j = 0; j < knotsAmount; ++j) { + knots[i].add(Float.valueOf(blenderInputStream.readFloat())); + } + } + } + + // loading the flags and orders (basis functions degrees) + int flagU = ((Number) nurb.getFieldValue("flagu")).intValue(); + int flagV = ((Number) nurb.getFieldValue("flagv")).intValue(); + int orderU = ((Number) nurb.getFieldValue("orderu")).intValue(); + int orderV = ((Number) nurb.getFieldValue("orderv")).intValue(); + + // loading control points and their weights + int pntsU = ((Number) nurb.getFieldValue("pntsu")).intValue(); + int pntsV = ((Number) nurb.getFieldValue("pntsv")).intValue(); + List bPoints = ((Pointer) nurb.getFieldValue("bp")).fetchData(); + List> controlPoints = new ArrayList>(pntsV); + for (int i = 0; i < pntsV; ++i) { + List uControlPoints = new ArrayList(pntsU); + for (int j = 0; j < pntsU; ++j) { + DynamicArray vec = (DynamicArray) bPoints.get(j + i * pntsU).getFieldValue("vec"); + if (fixUpAxis) { + uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(2).floatValue(), -vec.get(1).floatValue(), vec.get(3).floatValue())); + } else { + uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(1).floatValue(), vec.get(2).floatValue(), vec.get(3).floatValue())); + } + } + if ((flagU & 0x01) != 0) { + for (int k = 0; k < orderU - 1; ++k) { + uControlPoints.add(uControlPoints.get(k)); + } + } + controlPoints.add(uControlPoints); + } + if ((flagV & 0x01) != 0) { + for (int k = 0; k < orderV - 1; ++k) { + controlPoints.add(controlPoints.get(k)); + } + } + + int resolu = ((Number) nurb.getFieldValue("resolu")).intValue() + 1; + List result; + if (knots[1] == null) {// creating the curve + Spline nurbSpline = new Spline(controlPoints.get(0), knots[0]); + Curve nurbCurve = new Curve(nurbSpline, resolu); + if (bevelObject != null) { + result = this.applyBevelAndTaper(nurbCurve, bevelObject, taperObject, true, blenderContext);// TODO: smooth + } else { + result = new ArrayList(1); + Geometry nurbGeometry = new Geometry("", nurbCurve); + result.add(nurbGeometry); + } + } else {// creating the nurb surface + int resolv = ((Number) nurb.getFieldValue("resolv")).intValue() + 1; + Surface nurbSurface = Surface.createNurbsSurface(controlPoints, knots, resolu, resolv, orderU, orderV); + Geometry nurbGeometry = new Geometry("", nurbSurface); + result = new ArrayList(1); + result.add(nurbGeometry); + } + return result; + } + + /** + * The method computes the taper scale on the given point on the curve. + * + * @param taper + * the taper object that defines the scale + * @param percent + * the percent of the 'road' along the curve + * @return scale on the pointed place along the curve + */ + protected float getTaperScale(Spline taper, float percent) { + if (taper == null) { + return 1;// return scale = 1 if no taper is applied + } + percent = FastMath.clamp(percent, 0, 1); + List segmentLengths = taper.getSegmentsLength(); + float percentLength = taper.getTotalLength() * percent; + float partLength = 0; + int i; + for (i = 0; i < segmentLengths.size(); ++i) { + partLength += segmentLengths.get(i); + if (partLength > percentLength) { + partLength -= segmentLengths.get(i); + percentLength -= partLength; + percent = percentLength / segmentLengths.get(i); + break; + } + } + // do not cross the line :) + if (percent >= 1) { + percent = 1; + --i; + } + if (taper.getType() == SplineType.Bezier) { + i *= 3; + } + return taper.interpolate(percent, i, null).y; + } + + /** + * This method applies bevel and taper objects to the curve. + * @param curve + * the curve we apply the objects to + * @param bevelObject + * the bevel object + * @param taperObject + * the taper object + * @param smooth + * the smooth flag + * @param blenderContext + * the blender context + * @return a list of geometries representing the beveled and/or tapered curve + */ + protected List applyBevelAndTaper(Curve curve, List bevelObject, Spline taperObject, boolean smooth, BlenderContext blenderContext) { + Vector3f[] curvePoints = BufferUtils.getVector3Array(curve.getFloatBuffer(Type.Position)); + Vector3f subtractResult = new Vector3f(); + float curveLength = curve.getLength(); + + FloatBuffer[] vertexBuffers = new FloatBuffer[bevelObject.size()]; + FloatBuffer[] normalBuffers = new FloatBuffer[bevelObject.size()]; + IndexBuffer[] indexBuffers = new IndexBuffer[bevelObject.size()]; + for (int geomIndex = 0; geomIndex < bevelObject.size(); ++geomIndex) { + Mesh mesh = bevelObject.get(geomIndex).getMesh(); + Vector3f[] positions = BufferUtils.getVector3Array(mesh.getFloatBuffer(Type.Position)); + Vector3f[] bevelPoints = this.transformToFirstLineOfBevelPoints(positions, curvePoints[0], curvePoints[1]); + + List bevels = new ArrayList(curvePoints.length); + bevels.add(bevelPoints); + + vertexBuffers[geomIndex] = BufferUtils.createFloatBuffer(bevelPoints.length * 3 * curvePoints.length * (smooth ? 1 : 6)); + for (int i = 1; i < curvePoints.length - 1; ++i) { + bevelPoints = this.transformBevel(bevelPoints, curvePoints[i - 1], curvePoints[i], curvePoints[i + 1]); + bevels.add(bevelPoints); + } + bevelPoints = this.transformBevel(bevelPoints, curvePoints[curvePoints.length - 2], curvePoints[curvePoints.length - 1], null); + bevels.add(bevelPoints); + + if (bevels.size() > 2) { + // changing the first and last bevel so that they are parallel to their neighbours (blender works this way) + // notice this implicates that the distances of every corresponding point in th two bevels must be identical and + // equal to the distance between the points on curve that define the bevel position + // so instead doing complicated rotations on each point we will simply properly translate each of them + + int[][] pointIndexes = new int[][] { { 0, 1 }, { curvePoints.length - 1, curvePoints.length - 2 } }; + for (int[] indexes : pointIndexes) { + float distance = curvePoints[indexes[1]].subtract(curvePoints[indexes[0]], subtractResult).length(); + Vector3f[] bevel = bevels.get(indexes[0]); + Vector3f[] nextBevel = bevels.get(indexes[1]); + for (int i = 0; i < bevel.length; ++i) { + float d = bevel[i].subtract(nextBevel[i], subtractResult).length(); + subtractResult.normalizeLocal().multLocal(distance - d); + bevel[i].addLocal(subtractResult); + } + } + } + + // apply scales to the bevels + float lengthAlongCurve = 0; + for (int i = 0; i < curvePoints.length; ++i) { + if (i > 0) { + lengthAlongCurve += curvePoints[i].subtract(curvePoints[i - 1], subtractResult).length(); + } + float taperScale = this.getTaperScale(taperObject, i == 0 ? 0 : lengthAlongCurve / curveLength); + this.applyScale(bevels.get(i), curvePoints[i], taperScale); + } + + if (smooth) {// add everything to the buffer + for (Vector3f[] bevel : bevels) { + for (Vector3f d : bevel) { + vertexBuffers[geomIndex].put(d.x); + vertexBuffers[geomIndex].put(d.y); + vertexBuffers[geomIndex].put(d.z); + } + } + } else {// add vertices to the buffer duplicating them so that every vertex belongs only to a single triangle + for (int i = 0; i < curvePoints.length - 1; ++i) { + for (int j = 0; j < bevelPoints.length - 1; ++j) { + // first triangle + vertexBuffers[geomIndex].put(bevels.get(i)[j].x); + vertexBuffers[geomIndex].put(bevels.get(i)[j].y); + vertexBuffers[geomIndex].put(bevels.get(i)[j].z); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].x); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].y); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].z); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].x); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].y); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].z); + + // second triangle + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].x); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].y); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].z); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j + 1].x); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j + 1].y); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j + 1].z); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].x); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].y); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].z); + } + } + } + + indexBuffers[geomIndex] = this.generateIndexes(bevelPoints.length, curvePoints.length, smooth); + normalBuffers[geomIndex] = this.generateNormals(indexBuffers[geomIndex], vertexBuffers[geomIndex], smooth); + } + + // creating and returning the result + List result = new ArrayList(vertexBuffers.length); + Float oneReferenceToCurveLength = new Float(curveLength);// its important for array modifier to use one reference here + for (int i = 0; i < vertexBuffers.length; ++i) { + Mesh mesh = new Mesh(); + mesh.setBuffer(Type.Position, 3, vertexBuffers[i]); + if (indexBuffers[i].getBuffer() instanceof IntBuffer) { + mesh.setBuffer(Type.Index, 3, (IntBuffer) indexBuffers[i].getBuffer()); + } else { + mesh.setBuffer(Type.Index, 3, (ShortBuffer) indexBuffers[i].getBuffer()); + } + mesh.setBuffer(Type.Normal, 3, normalBuffers[i]); + Geometry g = new Geometry("g" + i, mesh); + g.setUserData("curveLength", oneReferenceToCurveLength); + g.updateModelBound(); + result.add(g); + } + return result; + } + + /** + * the method applies scale for the given bevel points. The points table is + * being modified so expect ypur result there. + * + * @param points + * the bevel points + * @param centerPoint + * the center point of the bevel + * @param scale + * the scale to be applied + */ + private void applyScale(Vector3f[] points, Vector3f centerPoint, float scale) { + Vector3f taperScaleVector = new Vector3f(); + for (Vector3f p : points) { + taperScaleVector.set(centerPoint).subtractLocal(p).multLocal(1 - scale); + p.addLocal(taperScaleVector); + } + } + + /** + * The method generates normal buffer for the created mesh of the curve. + * + * @param indexes + * the indexes of the mesh points + * @param points + * the mesh's points + * @param smooth + * the flag indicating if the result is to be smooth or solid + * @return normals buffer for the mesh + */ + private FloatBuffer generateNormals(IndexBuffer indexes, FloatBuffer points, boolean smooth) { + Map normalMap = new TreeMap(); + Vector3f[] allVerts = BufferUtils.getVector3Array(points); + + for (int i = 0; i < indexes.size(); i += 3) { + int index1 = indexes.get(i); + int index2 = indexes.get(i + 1); + int index3 = indexes.get(i + 2); + + Vector3f n = FastMath.computeNormal(allVerts[index1], allVerts[index2], allVerts[index3]); + this.addNormal(n, normalMap, smooth, index1, index2, index3); + } + + FloatBuffer normals = BufferUtils.createFloatBuffer(normalMap.size() * 3); + for (Entry entry : normalMap.entrySet()) { + normals.put(entry.getValue().x); + normals.put(entry.getValue().y); + normals.put(entry.getValue().z); + } + return normals; + } + + /** + * The amount of faces in the final mesh is the amount of edges in the bevel + * curve (which is less by 1 than its number of vertices) multiplied by 2 + * (because each edge has two faces assigned on both sides) and multiplied + * by the amount of bevel curve repeats which is equal to the amount of + * vertices on the target curve finally we need to subtract the bevel edges + * amount 2 times because the border edges have only one face attached and + * at last multiply everything by 3 because each face needs 3 indexes to be + * described + * + * @param bevelShapeVertexCount + * amount of points in bevel shape + * @param bevelRepeats + * amount of bevel shapes along the curve + * @param smooth + * the smooth flag + * @return index buffer for the mesh + */ + private IndexBuffer generateIndexes(int bevelShapeVertexCount, int bevelRepeats, boolean smooth) { + int putIndex = 0; + if (smooth) { + int indexBufferSize = (bevelRepeats - 1) * (bevelShapeVertexCount - 1) * 6; + IndexBuffer result = IndexBuffer.createIndexBuffer(indexBufferSize, indexBufferSize); + + for (int i = 0; i < bevelRepeats - 1; ++i) { + for (int j = 0; j < bevelShapeVertexCount - 1; ++j) { + result.put(putIndex++, i * bevelShapeVertexCount + j); + result.put(putIndex++, i * bevelShapeVertexCount + j + 1); + result.put(putIndex++, (i + 1) * bevelShapeVertexCount + j); + + result.put(putIndex++, i * bevelShapeVertexCount + j + 1); + result.put(putIndex++, (i + 1) * bevelShapeVertexCount + j + 1); + result.put(putIndex++, (i + 1) * bevelShapeVertexCount + j); + } + } + return result; + } else { + // every pair of bevel vertices belongs to two triangles + // we have the same amount of pairs as the amount of vertices in bevel + // so the amount of triangles is: bevelShapeVertexCount * 2 * (bevelRepeats - 1) + // and this gives the amount of vertices in non smooth shape as below ... + int indexBufferSize = bevelShapeVertexCount * bevelRepeats * 6;// 6 = 2 * 3 where 2 is stated above and 3 is the count of vertices for each triangle + IndexBuffer result = IndexBuffer.createIndexBuffer(indexBufferSize, indexBufferSize); + for (int i = 0; i < indexBufferSize; ++i) { + result.put(putIndex++, i); + } + return result; + } + } + + /** + * The method transforms the bevel along the curve. + * + * @param bevel + * the bevel to be transformed + * @param prevPos + * previous curve point + * @param currPos + * current curve point (here the center of the new bevel will be + * set) + * @param nextPos + * next curve point + * @return points of transformed bevel + */ + private Vector3f[] transformBevel(Vector3f[] bevel, Vector3f prevPos, Vector3f currPos, Vector3f nextPos) { + bevel = bevel.clone(); + + // currPos and directionVector define the line in 3D space + Vector3f directionVector = prevPos != null ? currPos.subtract(prevPos) : nextPos.subtract(currPos); + directionVector.normalizeLocal(); + + // plane is described by equation: Ax + By + Cz + D = 0 where planeNormal = [A, B, C] and D = -(Ax + By + Cz) + Vector3f planeNormal = null; + if (prevPos != null) { + planeNormal = currPos.subtract(prevPos).normalizeLocal(); + if (nextPos != null) { + planeNormal.addLocal(nextPos.subtract(currPos).normalizeLocal()).normalizeLocal(); + } + } else { + planeNormal = nextPos.subtract(currPos).normalizeLocal(); + } + float D = -planeNormal.dot(currPos);// D = -(Ax + By + Cz) + + // now we need to compute paralell cast of each bevel point on the plane, the leading line is already known + // parametric equation of a line: x = px + vx * t; y = py + vy * t; z = pz + vz * t + // where p = currPos and v = directionVector + // using x, y and z in plane equation we get value of 't' that will allow us to compute the point where plane and line cross + float temp = planeNormal.dot(directionVector); + for (int i = 0; i < bevel.length; ++i) { + float t = -(planeNormal.dot(bevel[i]) + D) / temp; + if (fixUpAxis) { + bevel[i] = new Vector3f(bevel[i].x + directionVector.x * t, bevel[i].y + directionVector.y * t, bevel[i].z + directionVector.z * t); + } else { + bevel[i] = new Vector3f(bevel[i].x + directionVector.x * t, -bevel[i].z + directionVector.z * t, bevel[i].y + directionVector.y * t); + } + } + return bevel; + } + + /** + * This method transforms the first line of the bevel points positioning it + * on the first point of the curve. + * + * @param startingLinePoints + * the vbevel shape points + * @param firstCurvePoint + * the first curve's point + * @param secondCurvePoint + * the second curve's point + * @return points of transformed bevel + */ + private Vector3f[] transformToFirstLineOfBevelPoints(Vector3f[] startingLinePoints, Vector3f firstCurvePoint, Vector3f secondCurvePoint) { + Vector3f planeNormal = secondCurvePoint.subtract(firstCurvePoint).normalizeLocal(); + + float angle = FastMath.acos(planeNormal.dot(Vector3f.UNIT_Y)); + planeNormal.crossLocal(Vector3f.UNIT_Y).normalizeLocal();// planeNormal is the rotation axis now + Quaternion pointRotation = new Quaternion(); + pointRotation.fromAngleAxis(angle, planeNormal); + + Matrix4f m = new Matrix4f(); + m.setRotationQuaternion(pointRotation); + m.setTranslation(firstCurvePoint); + + float[] temp = new float[] { 0, 0, 0, 1 }; + Vector3f[] verts = new Vector3f[startingLinePoints.length]; + for (int j = 0; j < verts.length; ++j) { + temp[0] = startingLinePoints[j].x; + temp[1] = startingLinePoints[j].y; + temp[2] = startingLinePoints[j].z; + temp = m.mult(temp);// the result is stored in the array + if (fixUpAxis) { + verts[j] = new Vector3f(temp[0], -temp[2], temp[1]); + } else { + verts[j] = new Vector3f(temp[0], temp[1], temp[2]); + } + } + return verts; + } + + /** + * The method adds a normal to the given map. Depending in the smooth factor + * it is either merged with the revious normal or not. + * + * @param normalToAdd + * the normal vector to be added + * @param normalMap + * the normal map where we add vectors + * @param smooth + * the smooth flag + * @param indexes + * the indexes of the normals + */ + private void addNormal(Vector3f normalToAdd, Map normalMap, boolean smooth, int... indexes) { + for (int index : indexes) { + Vector3f n = normalMap.get(index); + if (!smooth || n == null) { + normalMap.put(index, normalToAdd.clone()); + } else { + n.addLocal(normalToAdd).normalizeLocal(); + } + } + } + + /** + * This method loads the taper object. + * + * @param taperStructure + * the taper structure + * @return the taper object + * @throws BlenderFileException + */ + protected Spline loadTaperObject(Structure taperStructure) throws BlenderFileException { + // reading nurbs + List nurbStructures = ((Structure) taperStructure.getFieldValue("nurb")).evaluateListBase(); + for (Structure nurb : nurbStructures) { + Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt"); + if (pBezierTriple.isNotNull()) { + // creating the curve object + BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(), 3); + List controlPoints = bezierCurve.getControlPoints(); + // removing the first and last handles + controlPoints.remove(0); + controlPoints.remove(controlPoints.size() - 1); + + // return the first taper curve that has more than 3 control points + if (controlPoints.size() > 3) { + return new Spline(SplineType.Bezier, controlPoints, 0, false); + } + } + } + return null; + } + + /** + * This method returns the translation of the curve. The UP axis is taken + * into account here. + * + * @param curveStructure + * the curve structure + * @return curve translation + */ + @SuppressWarnings("unchecked") + protected Vector3f getLoc(Structure curveStructure) { + DynamicArray locArray = (DynamicArray) curveStructure.getFieldValue("loc"); + if (fixUpAxis) { + return new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), -locArray.get(2).floatValue()); + } else { + return new Vector3f(locArray.get(0).floatValue(), locArray.get(2).floatValue(), locArray.get(1).floatValue()); + } + } +} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/BlenderFileException.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/BlenderFileException.java new file mode 100644 index 000000000..4c25a52da --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/BlenderFileException.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.file; + +/** + * This exception is thrown when blend file data is somehow invalid. + * @author Marcin Roguski + */ +public class BlenderFileException extends Exception { + + private static final long serialVersionUID = 7573482836437866767L; + + /** + * Constructor. Creates an exception with no description. + */ + public BlenderFileException() { + // this constructor has no message + } + + /** + * Constructor. Creates an exception containing the given message. + * @param message + * the message describing the problem that occured + */ + public BlenderFileException(String message) { + super(message); + } + + /** + * Constructor. Creates an exception that is based upon other thrown object. It contains the whole stacktrace then. + * @param throwable + * an exception/error that occured + */ + public BlenderFileException(Throwable throwable) { + super(throwable); + } + + /** + * Constructor. Creates an exception with both a message and stacktrace. + * @param message + * the message describing the problem that occured + * @param throwable + * an exception/error that occured + */ + public BlenderFileException(String message, Throwable throwable) { + super(message, throwable); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/BlenderInputStream.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/BlenderInputStream.java new file mode 100644 index 000000000..94a84cd5e --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/BlenderInputStream.java @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.file; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; +import java.util.zip.GZIPInputStream; + +/** + * An input stream with random access to data. + * @author Marcin Roguski + */ +public class BlenderInputStream extends InputStream { + + private static final Logger LOGGER = Logger.getLogger(BlenderInputStream.class.getName()); + /** The default size of the blender buffer. */ + private static final int DEFAULT_BUFFER_SIZE = 1048576; // 1MB + /** + * Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes and '-' means 8 bytes. + */ + private int pointerSize; + /** + * Type of byte ordering used; 'v' means little endian and 'V' means big endian. + */ + private char endianess; + /** Version of Blender the file was created in; '248' means version 2.48. */ + private String versionNumber; + /** The buffer we store the read data to. */ + protected byte[] cachedBuffer; + /** The total size of the stored data. */ + protected int size; + /** The current position of the read cursor. */ + protected int position; + + /** + * Constructor. The input stream is stored and used to read data. + * @param inputStream + * the stream we read data from + * @throws BlenderFileException + * this exception is thrown if the file header has some invalid data + */ + public BlenderInputStream(InputStream inputStream) throws BlenderFileException { + // the size value will canche while reading the file; the available() method cannot be counted on + try { + size = inputStream.available(); + } catch (IOException e) { + size = 0; + } + if (size <= 0) { + size = BlenderInputStream.DEFAULT_BUFFER_SIZE; + } + + // buffered input stream is used here for much faster file reading + BufferedInputStream bufferedInputStream; + if (inputStream instanceof BufferedInputStream) { + bufferedInputStream = (BufferedInputStream) inputStream; + } else { + bufferedInputStream = new BufferedInputStream(inputStream); + } + + try { + this.readStreamToCache(bufferedInputStream); + } catch (IOException e) { + throw new BlenderFileException("Problems occured while caching the file!", e); + } finally { + try { + inputStream.close(); + } catch (IOException e) { + LOGGER.warning("Unable to close stream with blender file."); + } + } + + try { + this.readFileHeader(); + } catch (BlenderFileException e) {// the file might be packed, don't panic, try one more time ;) + this.decompressFile(); + position = 0; + this.readFileHeader(); + } + } + + /** + * This method reads the whole stream into a buffer. + * @param inputStream + * the stream to read the file data from + * @throws IOException + * an exception is thrown when data read from the stream is invalid or there are problems with i/o + * operations + */ + private void readStreamToCache(InputStream inputStream) throws IOException { + int data = inputStream.read(); + cachedBuffer = new byte[size]; + size = 0;// this will count the actual size + while (data != -1) { + if (size >= cachedBuffer.length) {// widen the cached array + byte[] newBuffer = new byte[cachedBuffer.length + (cachedBuffer.length >> 1)]; + System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length); + cachedBuffer = newBuffer; + } + cachedBuffer[size++] = (byte) data; + data = inputStream.read(); + } + } + + /** + * This method is used when the blender file is gzipped. It decompresses the data and stores it back into the + * cachedBuffer field. + */ + private void decompressFile() { + GZIPInputStream gis = null; + try { + gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer)); + this.readStreamToCache(gis); + } catch (IOException e) { + throw new IllegalStateException("IO errors occured where they should NOT! " + "The data is already buffered at this point!", e); + } finally { + try { + if (gis != null) { + gis.close(); + } + } catch (IOException e) { + LOGGER.warning(e.getMessage()); + } + } + } + + /** + * This method loads the header from the given stream during instance creation. + * @param inputStream + * the stream we read the header from + * @throws BlenderFileException + * this exception is thrown if the file header has some invalid data + */ + private void readFileHeader() throws BlenderFileException { + byte[] identifier = new byte[7]; + int bytesRead = this.readBytes(identifier); + if (bytesRead != 7) { + throw new BlenderFileException("Error reading header identifier. Only " + bytesRead + " bytes read and there should be 7!"); + } + String strIdentifier = new String(identifier); + if (!"BLENDER".equals(strIdentifier)) { + throw new BlenderFileException("Wrong file identifier: " + strIdentifier + "! Should be 'BLENDER'!"); + } + char pointerSizeSign = (char) this.readByte(); + if (pointerSizeSign == '-') { + pointerSize = 8; + } else if (pointerSizeSign == '_') { + pointerSize = 4; + } else { + throw new BlenderFileException("Invalid pointer size character! Should be '_' or '-' and there is: " + pointerSizeSign); + } + endianess = (char) this.readByte(); + if (endianess != 'v' && endianess != 'V') { + throw new BlenderFileException("Unknown endianess value! 'v' or 'V' expected and found: " + endianess); + } + byte[] versionNumber = new byte[3]; + bytesRead = this.readBytes(versionNumber); + if (bytesRead != 3) { + throw new BlenderFileException("Error reading version numberr. Only " + bytesRead + " bytes read and there should be 3!"); + } + this.versionNumber = new String(versionNumber); + } + + @Override + public int read() throws IOException { + return this.readByte(); + } + + /** + * This method reads 1 byte from the stream. + * It works just in the way the read method does. + * It just not throw an exception because at this moment the whole file + * is loaded into buffer, so no need for IOException to be thrown. + * @return a byte from the stream (1 bytes read) + */ + public int readByte() { + return cachedBuffer[position++] & 0xFF; + } + + /** + * This method reads a bytes number big enough to fill the table. + * It does not throw exceptions so it is for internal use only. + * @param bytes + * an array to be filled with data + * @return number of read bytes (a length of array actually) + */ + private int readBytes(byte[] bytes) { + for (int i = 0; i < bytes.length; ++i) { + bytes[i] = (byte) this.readByte(); + } + return bytes.length; + } + + /** + * This method reads 2-byte number from the stream. + * @return a number from the stream (2 bytes read) + */ + public int readShort() { + int part1 = this.readByte(); + int part2 = this.readByte(); + if (endianess == 'v') { + return (part2 << 8) + part1; + } else { + return (part1 << 8) + part2; + } + } + + /** + * This method reads 4-byte number from the stream. + * @return a number from the stream (4 bytes read) + */ + public int readInt() { + int part1 = this.readByte(); + int part2 = this.readByte(); + int part3 = this.readByte(); + int part4 = this.readByte(); + if (endianess == 'v') { + return (part4 << 24) + (part3 << 16) + (part2 << 8) + part1; + } else { + return (part1 << 24) + (part2 << 16) + (part3 << 8) + part4; + } + } + + /** + * This method reads 4-byte floating point number (float) from the stream. + * @return a number from the stream (4 bytes read) + */ + public float readFloat() { + int intValue = this.readInt(); + return Float.intBitsToFloat(intValue); + } + + /** + * This method reads 8-byte number from the stream. + * @return a number from the stream (8 bytes read) + */ + public long readLong() { + long part1 = this.readInt(); + long part2 = this.readInt(); + long result = -1; + if (endianess == 'v') { + result = part2 << 32 | part1; + } else { + result = part1 << 32 | part2; + } + return result; + } + + /** + * This method reads 8-byte floating point number (double) from the stream. + * @return a number from the stream (8 bytes read) + */ + public double readDouble() { + long longValue = this.readLong(); + return Double.longBitsToDouble(longValue); + } + + /** + * This method reads the pointer value. Depending on the pointer size defined in the header, the stream reads either + * 4 or 8 bytes of data. + * @return the pointer value + */ + public long readPointer() { + if (pointerSize == 4) { + return this.readInt(); + } + return this.readLong(); + } + + /** + * This method reads the string. It assumes the string is terminated with zero in the stream. + * @return the string read from the stream + */ + public String readString() { + StringBuilder stringBuilder = new StringBuilder(); + int data = this.readByte(); + while (data != 0) { + stringBuilder.append((char) data); + data = this.readByte(); + } + return stringBuilder.toString(); + } + + /** + * This method sets the current position of the read cursor. + * @param position + * the position of the read cursor + */ + public void setPosition(int position) { + this.position = position; + } + + /** + * This method returns the position of the read cursor. + * @return the position of the read cursor + */ + public int getPosition() { + return position; + } + + /** + * This method returns the blender version number where the file was created. + * @return blender version number + */ + public String getVersionNumber() { + return versionNumber; + } + + /** + * This method returns the size of the pointer. + * @return the size of the pointer + */ + public int getPointerSize() { + return pointerSize; + } + + /** + * This method aligns cursor position forward to a given amount of bytes. + * @param bytesAmount + * the byte amount to which we aligh the cursor + */ + public void alignPosition(int bytesAmount) { + if (bytesAmount <= 0) { + throw new IllegalArgumentException("Alignment byte number shoulf be positivbe!"); + } + long move = position % bytesAmount; + if (move > 0) { + position += bytesAmount - move; + } + } + + @Override + public void close() throws IOException { + // this method is unimplemented because some loaders (ie. TGALoader) tend close the stream given from the outside + // because the images can be stored directly in the blender file then this stream is properly positioned and given to the loader + // to read the image file, that is why we do not want it to be closed before the reading is done + // and anyway this stream is only a cached buffer, so it does not hold any open connection to anything + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/DnaBlockData.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/DnaBlockData.java new file mode 100644 index 000000000..35ffaa5db --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/DnaBlockData.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.file; + +import com.jme3.scene.plugins.blender.BlenderContext; + +import java.util.HashMap; +import java.util.Map; + +/** + * The data block containing the description of the file. + * @author Marcin Roguski (Kaelthas) + */ +public class DnaBlockData { + + private static final int SDNA_ID = 'S' << 24 | 'D' << 16 | 'N' << 8 | 'A'; // SDNA + private static final int NAME_ID = 'N' << 24 | 'A' << 16 | 'M' << 8 | 'E'; // NAME + private static final int TYPE_ID = 'T' << 24 | 'Y' << 16 | 'P' << 8 | 'E'; // TYPE + private static final int TLEN_ID = 'T' << 24 | 'L' << 16 | 'E' << 8 | 'N'; // TLEN + private static final int STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C'; // STRC + /** Structures available inside the file. */ + private final Structure[] structures; + /** A map that helps finding a structure by type. */ + private final Map structuresMap; + + /** + * Constructor. Loads the block from the given stream during instance creation. + * @param inputStream + * the stream we read the block from + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is throw if the blend file is invalid or somehow corrupted + */ + public DnaBlockData(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException { + int identifier; + + // reading 'SDNA' identifier + identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); + + if (identifier != SDNA_ID) { + throw new BlenderFileException("Invalid identifier! '" + this.toString(SDNA_ID) + "' expected and found: " + this.toString(identifier)); + } + + // reading names + identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); + if (identifier != NAME_ID) { + throw new BlenderFileException("Invalid identifier! '" + this.toString(NAME_ID) + "' expected and found: " + this.toString(identifier)); + } + int amount = inputStream.readInt(); + if (amount <= 0) { + throw new BlenderFileException("The names amount number should be positive!"); + } + String[] names = new String[amount]; + for (int i = 0; i < amount; ++i) { + names[i] = inputStream.readString(); + } + + // reding types + inputStream.alignPosition(4); + identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); + if (identifier != TYPE_ID) { + throw new BlenderFileException("Invalid identifier! '" + this.toString(TYPE_ID) + "' expected and found: " + this.toString(identifier)); + } + amount = inputStream.readInt(); + if (amount <= 0) { + throw new BlenderFileException("The types amount number should be positive!"); + } + String[] types = new String[amount]; + for (int i = 0; i < amount; ++i) { + types[i] = inputStream.readString(); + } + + // reading lengths + inputStream.alignPosition(4); + identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); + if (identifier != TLEN_ID) { + throw new BlenderFileException("Invalid identifier! '" + this.toString(TLEN_ID) + "' expected and found: " + this.toString(identifier)); + } + int[] lengths = new int[amount];// theamount is the same as int types + for (int i = 0; i < amount; ++i) { + lengths[i] = inputStream.readShort(); + } + + // reading structures + inputStream.alignPosition(4); + identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); + if (identifier != STRC_ID) { + throw new BlenderFileException("Invalid identifier! '" + this.toString(STRC_ID) + "' expected and found: " + this.toString(identifier)); + } + amount = inputStream.readInt(); + if (amount <= 0) { + throw new BlenderFileException("The structures amount number should be positive!"); + } + structures = new Structure[amount]; + structuresMap = new HashMap(amount); + for (int i = 0; i < amount; ++i) { + structures[i] = new Structure(inputStream, names, types, blenderContext); + if (structuresMap.containsKey(structures[i].getType())) { + throw new BlenderFileException("Blend file seems to be corrupted! The type " + structures[i].getType() + " is defined twice!"); + } + structuresMap.put(structures[i].getType(), structures[i]); + } + } + + /** + * This method returns the amount of the structures. + * @return the amount of the structures + */ + public int getStructuresCount() { + return structures.length; + } + + /** + * This method returns the structure of the given index. + * @param index + * the index of the structure + * @return the structure of the given index + */ + public Structure getStructure(int index) { + try { + return (Structure) structures[index].clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException("Structure should be clonable!!!", e); + } + } + + /** + * This method returns a structure of the given name. If the name does not exists then null is returned. + * @param name + * the name of the structure + * @return the required structure or null if the given name is inapropriate + */ + public Structure getStructure(String name) { + try { + return (Structure) structuresMap.get(name).clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + + /** + * This method indicates if the structure of the given name exists. + * @param name + * the name of the structure + * @return true if the structure exists and false otherwise + */ + public boolean hasStructure(String name) { + return structuresMap.containsKey(name); + } + + /** + * This method converts the given identifier code to string. + * @param code + * the code taht is to be converted + * @return the string value of the identifier + */ + private String toString(int code) { + char c1 = (char) ((code & 0xFF000000) >> 24); + char c2 = (char) ((code & 0xFF0000) >> 16); + char c3 = (char) ((code & 0xFF00) >> 8); + char c4 = (char) (code & 0xFF); + return String.valueOf(c1) + c2 + c3 + c4; + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder("=============== ").append(SDNA_ID).append('\n'); + for (Structure structure : structures) { + stringBuilder.append(structure.toString()).append('\n'); + } + return stringBuilder.append("===============").toString(); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/DynamicArray.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/DynamicArray.java new file mode 100644 index 000000000..d6d110054 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/DynamicArray.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.file; + +/** + * An array that can be dynamically modified/ + * @author Marcin Roguski + * @param + * the type of stored data in the array + */ +public class DynamicArray implements Cloneable { + + /** An array object that holds the required data. */ + private T[] array; + /** + * This table holds the sizes of dimetions of the dynamic table. It's length specifies the table dimension or a + * pointer level. For example: if tableSizes.length == 3 then it either specifies a dynamic table of fixed lengths: + * dynTable[a][b][c], where a,b,c are stored in the tableSizes table. + */ + private int[] tableSizes; + + /** + * Constructor. Builds an empty array of the specified sizes. + * @param tableSizes + * the sizes of the table + * @throws IllegalArgumentException + * an exception is thrown if one of the sizes is not a positive number + */ + public DynamicArray(int[] tableSizes, T[] data) { + this.tableSizes = tableSizes; + int totalSize = 1; + for (int size : tableSizes) { + if (size <= 0) { + throw new IllegalArgumentException("The size of the table must be positive!"); + } + totalSize *= size; + } + if (totalSize != data.length) { + throw new IllegalArgumentException("The size of the table does not match the size of the given data!"); + } + this.array = data; + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * This method returns a value on the specified position. The dimension of the table is not taken into + * consideration. + * @param position + * the position of the data + * @return required data + */ + public T get(int position) { + return array[position]; + } + + /** + * This method returns a value on the specified position in multidimensional array. Be careful not to exceed the + * table boundaries. Check the table's dimension first. + * @param position + * the position of the data indices of data position + * @return required data required data + */ + public T get(int... position) { + if (position.length != tableSizes.length) { + throw new ArrayIndexOutOfBoundsException("The table accepts " + tableSizes.length + " indexing number(s)!"); + } + int index = 0; + for (int i = 0; i < position.length - 1; ++i) { + index += position[i] * tableSizes[i + 1]; + } + index += position[position.length - 1]; + return array[index]; + } + + /** + * This method returns the total amount of data stored in the array. + * @return the total amount of data stored in the array + */ + public int getTotalSize() { + return array.length; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + if (array instanceof Character[]) {// in case of character array we convert it to String + for (int i = 0; i < array.length && (Character) array[i] != '\0'; ++i) {// strings are terminater with '0' + result.append(array[i]); + } + } else { + result.append('['); + for (int i = 0; i < array.length; ++i) { + result.append(array[i].toString()); + if (i + 1 < array.length) { + result.append(','); + } + } + result.append(']'); + } + return result.toString(); + } +} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Field.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Field.java new file mode 100644 index 000000000..81cfdcde8 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Field.java @@ -0,0 +1,327 @@ +package com.jme3.scene.plugins.blender.file; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Structure.DataType; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents a single field in the structure. It can be either a primitive type or a table or a reference to + * another structure. + * @author Marcin Roguski + */ +/* package */ +class Field implements Cloneable { + + private static final int NAME_LENGTH = 24; + private static final int TYPE_LENGTH = 16; + /** The blender context. */ + public BlenderContext blenderContext; + /** The type of the field. */ + public String type; + /** The name of the field. */ + public String name; + /** The value of the field. Filled during data reading. */ + public Object value; + /** This variable indicates the level of the pointer. */ + public int pointerLevel; + /** + * This variable determines the sizes of the array. If the value is null the n the field is not an array. + */ + public int[] tableSizes; + /** This variable indicates if the field is a function pointer. */ + public boolean function; + + /** + * Constructor. Saves the field data and parses its name. + * @param name + * the name of the field + * @param type + * the type of the field + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown if the names contain errors + */ + public Field(String name, String type, BlenderContext blenderContext) throws BlenderFileException { + this.type = type; + this.blenderContext = blenderContext; + this.parseField(new StringBuilder(name)); + } + + /** + * Copy constructor. Used in clone method. Copying is not full. The value in the new object is not set so that we + * have a clead empty copy of the filed to fill with data. + * @param field + * the object that we copy + */ + private Field(Field field) { + type = field.type; + name = field.name; + blenderContext = field.blenderContext; + pointerLevel = field.pointerLevel; + if (field.tableSizes != null) { + tableSizes = field.tableSizes.clone(); + } + function = field.function; + } + + @Override + public Object clone() throws CloneNotSupportedException { + return new Field(this); + } + + /** + * This method fills the field wth data read from the input stream. + * @param blenderInputStream + * the stream we read data from + * @throws BlenderFileException + * an exception is thrown when the blend file is somehow invalid or corrupted + */ + public void fill(BlenderInputStream blenderInputStream) throws BlenderFileException { + int dataToRead = 1; + if (tableSizes != null && tableSizes.length > 0) { + for (int size : tableSizes) { + if (size <= 0) { + throw new BlenderFileException("The field " + name + " has invalid table size: " + size); + } + dataToRead *= size; + } + } + DataType dataType = pointerLevel == 0 ? DataType.getDataType(type, blenderContext) : DataType.POINTER; + switch (dataType) { + case POINTER: + if (dataToRead == 1) { + Pointer pointer = new Pointer(pointerLevel, function, blenderContext); + pointer.fill(blenderInputStream); + value = pointer; + } else { + Pointer[] data = new Pointer[dataToRead]; + for (int i = 0; i < dataToRead; ++i) { + Pointer pointer = new Pointer(pointerLevel, function, blenderContext); + pointer.fill(blenderInputStream); + data[i] = pointer; + } + value = new DynamicArray(tableSizes, data); + } + break; + case CHARACTER: + // character is also stored as a number, because sometimes the new blender version uses + // other number type instead of character as a field type + // and characters are very often used as byte number stores instead of real chars + if (dataToRead == 1) { + value = Byte.valueOf((byte) blenderInputStream.readByte()); + } else { + Character[] data = new Character[dataToRead]; + for (int i = 0; i < dataToRead; ++i) { + data[i] = Character.valueOf((char) blenderInputStream.readByte()); + } + value = new DynamicArray(tableSizes, data); + } + break; + case SHORT: + if (dataToRead == 1) { + value = Integer.valueOf(blenderInputStream.readShort()); + } else { + Number[] data = new Number[dataToRead]; + for (int i = 0; i < dataToRead; ++i) { + data[i] = Integer.valueOf(blenderInputStream.readShort()); + } + value = new DynamicArray(tableSizes, data); + } + break; + case INTEGER: + if (dataToRead == 1) { + value = Integer.valueOf(blenderInputStream.readInt()); + } else { + Number[] data = new Number[dataToRead]; + for (int i = 0; i < dataToRead; ++i) { + data[i] = Integer.valueOf(blenderInputStream.readInt()); + } + value = new DynamicArray(tableSizes, data); + } + break; + case LONG: + if (dataToRead == 1) { + value = Long.valueOf(blenderInputStream.readLong()); + } else { + Number[] data = new Number[dataToRead]; + for (int i = 0; i < dataToRead; ++i) { + data[i] = Long.valueOf(blenderInputStream.readLong()); + } + value = new DynamicArray(tableSizes, data); + } + break; + case FLOAT: + if (dataToRead == 1) { + value = Float.valueOf(blenderInputStream.readFloat()); + } else { + Number[] data = new Number[dataToRead]; + for (int i = 0; i < dataToRead; ++i) { + data[i] = Float.valueOf(blenderInputStream.readFloat()); + } + value = new DynamicArray(tableSizes, data); + } + break; + case DOUBLE: + if (dataToRead == 1) { + value = Double.valueOf(blenderInputStream.readDouble()); + } else { + Number[] data = new Number[dataToRead]; + for (int i = 0; i < dataToRead; ++i) { + data[i] = Double.valueOf(blenderInputStream.readDouble()); + } + value = new DynamicArray(tableSizes, data); + } + break; + case VOID: + break; + case STRUCTURE: + if (dataToRead == 1) { + Structure structure = blenderContext.getDnaBlockData().getStructure(type); + structure.fill(blenderContext.getInputStream()); + value = structure; + } else { + Structure[] data = new Structure[dataToRead]; + for (int i = 0; i < dataToRead; ++i) { + Structure structure = blenderContext.getDnaBlockData().getStructure(type); + structure.fill(blenderContext.getInputStream()); + data[i] = structure; + } + value = new DynamicArray(tableSizes, data); + } + break; + default: + throw new IllegalStateException("Unimplemented filling of type: " + type); + } + } + + /** + * This method parses the field name to determine how the field should be used. + * @param nameBuilder + * the name of the field (given as StringBuilder) + * @throws BlenderFileException + * this exception is thrown if the names contain errors + */ + private void parseField(StringBuilder nameBuilder) throws BlenderFileException { + this.removeWhitespaces(nameBuilder); + // veryfying if the name is a pointer + int pointerIndex = nameBuilder.indexOf("*"); + while (pointerIndex >= 0) { + ++pointerLevel; + nameBuilder.deleteCharAt(pointerIndex); + pointerIndex = nameBuilder.indexOf("*"); + } + // veryfying if the name is a function pointer + if (nameBuilder.indexOf("(") >= 0) { + function = true; + this.removeCharacter(nameBuilder, '('); + this.removeCharacter(nameBuilder, ')'); + } else { + // veryfying if the name is a table + int tableStartIndex = 0; + List lengths = new ArrayList(3);// 3 dimensions will be enough in most cases + do { + tableStartIndex = nameBuilder.indexOf("["); + if (tableStartIndex > 0) { + int tableStopIndex = nameBuilder.indexOf("]"); + if (tableStopIndex < 0) { + throw new BlenderFileException("Invalid structure name: " + name); + } + try { + lengths.add(Integer.valueOf(nameBuilder.substring(tableStartIndex + 1, tableStopIndex))); + } catch (NumberFormatException e) { + throw new BlenderFileException("Invalid structure name caused by invalid table length: " + name, e); + } + nameBuilder.delete(tableStartIndex, tableStopIndex + 1); + } + } while (tableStartIndex > 0); + if (!lengths.isEmpty()) { + tableSizes = new int[lengths.size()]; + for (int i = 0; i < tableSizes.length; ++i) { + tableSizes[i] = lengths.get(i).intValue(); + } + } + } + name = nameBuilder.toString(); + } + + /** + * This method removes the required character from the text. + * @param text + * the text we remove characters from + * @param toRemove + * the character to be removed + */ + private void removeCharacter(StringBuilder text, char toRemove) { + for (int i = 0; i < text.length(); ++i) { + if (text.charAt(i) == toRemove) { + text.deleteCharAt(i); + --i; + } + } + } + + /** + * This method removes all whitespaces from the text. + * @param text + * the text we remove whitespaces from + */ + private void removeWhitespaces(StringBuilder text) { + for (int i = 0; i < text.length(); ++i) { + if (Character.isWhitespace(text.charAt(i))) { + text.deleteCharAt(i); + --i; + } + } + } + + /** + * This method builds the full name of the field (with function, pointer and table indications). + * @return the full name of the field + */ + /*package*/ String getFullName() { + StringBuilder result = new StringBuilder(); + if (function) { + result.append('('); + } + for (int i = 0; i < pointerLevel; ++i) { + result.append('*'); + } + result.append(name); + if (tableSizes != null) { + for (int i = 0; i < tableSizes.length; ++i) { + result.append('[').append(tableSizes[i]).append(']'); + } + } + if (function) { + result.append(")()"); + } + return result.toString(); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(this.getFullName()); + + // insert appropriate amount of spaces to format the output corrently + int nameLength = result.length(); + result.append(' ');// at least one space is a must + for (int i = 1; i < NAME_LENGTH - nameLength; ++i) {// we start from i=1 because one space is already added + result.append(' '); + } + result.append(type); + nameLength = result.length(); + for (int i = 0; i < NAME_LENGTH + TYPE_LENGTH - nameLength; ++i) { + result.append(' '); + } + if (value instanceof Character) { + result.append(" = ").append((int) ((Character) value).charValue()); + } else { + result.append(" = ").append(value != null ? value.toString() : "null"); + } + return result.toString(); + } +} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java new file mode 100644 index 000000000..7bd634254 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.file; + +import com.jme3.scene.plugins.blender.BlenderContext; + +/** + * A class that holds the header data of a file block. The file block itself is not implemented. This class holds its + * start position in the stream and using this the structure can fill itself with the proper data. + * @author Marcin Roguski + */ +public class FileBlockHeader { + + public static final int BLOCK_TE00 = 'T' << 24 | 'E' << 16; // TE00 + public static final int BLOCK_ME00 = 'M' << 24 | 'E' << 16; // ME00 + public static final int BLOCK_SR00 = 'S' << 24 | 'R' << 16; // SR00 + public static final int BLOCK_CA00 = 'C' << 24 | 'A' << 16; // CA00 + public static final int BLOCK_LA00 = 'L' << 24 | 'A' << 16; // LA00 + public static final int BLOCK_OB00 = 'O' << 24 | 'B' << 16; // OB00 + public static final int BLOCK_MA00 = 'M' << 24 | 'A' << 16; // MA00 + public static final int BLOCK_SC00 = 'S' << 24 | 'C' << 16; // SC00 + public static final int BLOCK_WO00 = 'W' << 24 | 'O' << 16; // WO00 + public static final int BLOCK_TX00 = 'T' << 24 | 'X' << 16; // TX00 + public static final int BLOCK_IP00 = 'I' << 24 | 'P' << 16; // IP00 + public static final int BLOCK_AC00 = 'A' << 24 | 'C' << 16; // AC00 + public static final int BLOCK_GLOB = 'G' << 24 | 'L' << 16 | 'O' << 8 | 'B'; // GLOB + public static final int BLOCK_REND = 'R' << 24 | 'E' << 16 | 'N' << 8 | 'D'; // REND + public static final int BLOCK_DATA = 'D' << 24 | 'A' << 16 | 'T' << 8 | 'A'; // DATA + public static final int BLOCK_DNA1 = 'D' << 24 | 'N' << 16 | 'A' << 8 | '1'; // DNA1 + public static final int BLOCK_ENDB = 'E' << 24 | 'N' << 16 | 'D' << 8 | 'B'; // ENDB + /** Identifier of the file-block [4 bytes]. */ + private int code; + /** Total length of the data after the file-block-header [4 bytes]. */ + private int size; + /** + * Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer + * size)]. + */ + private long oldMemoryAddress; + /** Index of the SDNA structure [4 bytes]. */ + private int sdnaIndex; + /** Number of structure located in this file-block [4 bytes]. */ + private int count; + /** Start position of the block's data in the stream. */ + private int blockPosition; + + /** + * Constructor. Loads the block header from the given stream during instance creation. + * @param inputStream + * the stream we read the block header from + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the pointer size is neither 4 nor 8 + */ + public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException { + inputStream.alignPosition(4); + code = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); + size = inputStream.readInt(); + oldMemoryAddress = inputStream.readPointer(); + sdnaIndex = inputStream.readInt(); + count = inputStream.readInt(); + blockPosition = inputStream.getPosition(); + if (FileBlockHeader.BLOCK_DNA1 == code) { + blenderContext.setBlockData(new DnaBlockData(inputStream, blenderContext)); + } else { + inputStream.setPosition(blockPosition + size); + blenderContext.addFileBlockHeader(Long.valueOf(oldMemoryAddress), this); + } + } + + /** + * This method returns the structure described by the header filled with appropriate data. + * @param blenderContext + * the blender context + * @return structure filled with data + * @throws BlenderFileException + */ + public Structure getStructure(BlenderContext blenderContext) throws BlenderFileException { + blenderContext.getInputStream().setPosition(blockPosition); + Structure structure = blenderContext.getDnaBlockData().getStructure(sdnaIndex); + structure.fill(blenderContext.getInputStream()); + return structure; + } + + /** + * This method returns the code of this data block. + * @return the code of this data block + */ + public int getCode() { + return code; + } + + /** + * This method returns the size of the data stored in this block. + * @return the size of the data stored in this block + */ + public int getSize() { + return size; + } + + /** + * This method returns the sdna index. + * @return the sdna index + */ + public int getSdnaIndex() { + return sdnaIndex; + } + + /** + * This data returns the number of structure stored in the data block after this header. + * @return the number of structure stored in the data block after this header + */ + public int getCount() { + return count; + } + + /** + * This method returns the start position of the data block in the blend file stream. + * @return the start position of the data block + */ + public int getBlockPosition() { + return blockPosition; + } + + /** + * This method indicates if the block is the last block in the file. + * @return true if this block is the last one in the file nad false otherwise + */ + public boolean isLastBlock() { + return FileBlockHeader.BLOCK_ENDB == code; + } + + /** + * This method indicates if the block is the SDNA block. + * @return true if this block is the SDNA block and false otherwise + */ + public boolean isDnaBlock() { + return FileBlockHeader.BLOCK_DNA1 == code; + } + + @Override + public String toString() { + return "FILE BLOCK HEADER [" + this.codeToString(code) + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]"; + } + + /** + * This method transforms the coded bloch id into a string value. + * @param code + * the id of the block + * @return the string value of the block id + */ + protected String codeToString(int code) { + char c1 = (char) ((code & 0xFF000000) >> 24); + char c2 = (char) ((code & 0xFF0000) >> 16); + char c3 = (char) ((code & 0xFF00) >> 8); + char c4 = (char) (code & 0xFF); + return String.valueOf(c1) + c2 + c3 + c4; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Pointer.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Pointer.java new file mode 100644 index 000000000..c5a662325 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Pointer.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.file; + +import java.util.ArrayList; +import java.util.List; + +import com.jme3.scene.plugins.blender.BlenderContext; + +/** + * A class that represents a pointer of any level that can be stored in the file. + * @author Marcin Roguski + */ +public class Pointer { + + /** The blender context. */ + private BlenderContext blenderContext; + /** The level of the pointer. */ + private int pointerLevel; + /** The address in file it points to. */ + private long oldMemoryAddress; + /** This variable indicates if the field is a function pointer. */ + public boolean function; + + /** + * Constructr. Stores the basic data about the pointer. + * @param pointerLevel + * the level of the pointer + * @param function + * this variable indicates if the field is a function pointer + * @param blenderContext + * the repository f data; used in fetching the value that the pointer points + */ + public Pointer(int pointerLevel, boolean function, BlenderContext blenderContext) { + this.pointerLevel = pointerLevel; + this.function = function; + this.blenderContext = blenderContext; + } + + /** + * This method fills the pointer with its address value (it doesn't get the actual data yet. Use the 'fetch' method + * for this. + * @param inputStream + * the stream we read the pointer value from + */ + public void fill(BlenderInputStream inputStream) { + oldMemoryAddress = inputStream.readPointer(); + } + + /** + * This method fetches the data stored under the given address. + * @return the data read from the file + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + */ + public List fetchData() throws BlenderFileException { + if (oldMemoryAddress == 0) { + throw new NullPointerException("The pointer points to nothing!"); + } + List structures = null; + FileBlockHeader dataFileBlock = blenderContext.getFileBlock(oldMemoryAddress); + if (dataFileBlock == null) { + throw new BlenderFileException("No data stored for address: " + oldMemoryAddress + ". Make sure you did not open the newer blender file with older blender version."); + } + BlenderInputStream inputStream = blenderContext.getInputStream(); + if (pointerLevel > 1) { + int pointersAmount = dataFileBlock.getSize() / inputStream.getPointerSize() * dataFileBlock.getCount(); + for (int i = 0; i < pointersAmount; ++i) { + inputStream.setPosition(dataFileBlock.getBlockPosition() + inputStream.getPointerSize() * i); + long oldMemoryAddress = inputStream.readPointer(); + if (oldMemoryAddress != 0L) { + Pointer p = new Pointer(pointerLevel - 1, function, blenderContext); + p.oldMemoryAddress = oldMemoryAddress; + if (structures == null) { + structures = p.fetchData(); + } else { + structures.addAll(p.fetchData()); + } + } else { + // it is necessary to put null's if the pointer is null, ie. in materials array that is attached to the mesh, the index + // of the material is important, that is why we need null's to indicate that some materials' slots are empty + if (structures == null) { + structures = new ArrayList(); + } + structures.add(null); + } + } + } else { + inputStream.setPosition(dataFileBlock.getBlockPosition()); + structures = new ArrayList(dataFileBlock.getCount()); + for (int i = 0; i < dataFileBlock.getCount(); ++i) { + Structure structure = blenderContext.getDnaBlockData().getStructure(dataFileBlock.getSdnaIndex()); + structure.fill(blenderContext.getInputStream()); + structures.add(structure); + } + return structures; + } + return structures; + } + + /** + * This method indicates if this pointer points to a function. + * @return true if this is a function pointer and false otherwise + */ + public boolean isFunction() { + return function; + } + + /** + * This method indicates if this is a null-pointer or not. + * @return true if the pointer is null and false otherwise + */ + public boolean isNull() { + return oldMemoryAddress == 0; + } + + /** + * This method indicates if this is a null-pointer or not. + * @return true if the pointer is not null and false otherwise + */ + public boolean isNotNull() { + return oldMemoryAddress != 0; + } + + /** + * This method returns the old memory address of the structure pointed by the pointer. + * @return the old memory address of the structure pointed by the pointer + */ + public long getOldMemoryAddress() { + return oldMemoryAddress; + } + + @Override + public String toString() { + return oldMemoryAddress == 0 ? "{$null$}" : "{$" + oldMemoryAddress + "$}"; + } + + @Override + public int hashCode() { + return 31 + (int) (oldMemoryAddress ^ oldMemoryAddress >>> 32); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (this.getClass() != obj.getClass()) { + return false; + } + Pointer other = (Pointer) obj; + if (oldMemoryAddress != other.oldMemoryAddress) { + return false; + } + return true; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java new file mode 100644 index 000000000..af8c17f5b --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.file; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.jme3.scene.plugins.blender.BlenderContext; + +/** + * A class representing a single structure in the file. + * @author Marcin Roguski + */ +public class Structure implements Cloneable { + + /** The address of the block that fills the structure. */ + private transient Long oldMemoryAddress; + /** The type of the structure. */ + private String type; + /** + * The fields of the structure. Each field consists of a pair: name-type. + */ + private Field[] fields; + + /** + * Constructor that copies the data of the structure. + * @param structure + * the structure to copy. + * @throws CloneNotSupportedException + * this exception should never be thrown + */ + private Structure(Structure structure) throws CloneNotSupportedException { + type = structure.type; + fields = new Field[structure.fields.length]; + for (int i = 0; i < fields.length; ++i) { + fields[i] = (Field) structure.fields[i].clone(); + } + oldMemoryAddress = structure.oldMemoryAddress; + } + + /** + * Constructor. Loads the structure from the given stream during instance creation. + * @param inputStream + * the stream we read the structure from + * @param names + * the names from which the name of structure and its fields will be taken + * @param types + * the names of types for the structure + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception occurs if the amount of fields, defined in the file, is negative + */ + public Structure(BlenderInputStream inputStream, String[] names, String[] types, BlenderContext blenderContext) throws BlenderFileException { + int nameIndex = inputStream.readShort(); + type = types[nameIndex]; + int fieldsAmount = inputStream.readShort(); + if (fieldsAmount < 0) { + throw new BlenderFileException("The amount of fields of " + type + " structure cannot be negative!"); + } + if (fieldsAmount > 0) { + fields = new Field[fieldsAmount]; + for (int i = 0; i < fieldsAmount; ++i) { + int typeIndex = inputStream.readShort(); + nameIndex = inputStream.readShort(); + fields[i] = new Field(names[nameIndex], types[typeIndex], blenderContext); + } + } + oldMemoryAddress = Long.valueOf(-1L); + } + + /** + * This method fills the structure with data. + * @param inputStream + * the stream we read data from, its read cursor should be placed at the start position of the data for the + * structure + * @throws BlenderFileException + * an exception is thrown when the blend file is somehow invalid or corrupted + */ + public void fill(BlenderInputStream inputStream) throws BlenderFileException { + int position = inputStream.getPosition(); + inputStream.setPosition(position - 8 - inputStream.getPointerSize()); + oldMemoryAddress = Long.valueOf(inputStream.readPointer()); + inputStream.setPosition(position); + for (Field field : fields) { + field.fill(inputStream); + } + } + + /** + * This method returns the value of the filed with a given name. + * @param fieldName + * the name of the field + * @return the value of the field or null if no field with a given name is found + */ + public Object getFieldValue(String fieldName) { + for (Field field : fields) { + if (field.name.equalsIgnoreCase(fieldName)) { + return field.value; + } + } + return null; + } + + /** + * This method returns the value of the filed with a given name. The structure is considered to have flat fields + * only (no substructures). + * @param fieldName + * the name of the field + * @return the value of the field or null if no field with a given name is found + */ + public Object getFlatFieldValue(String fieldName) { + for (Field field : fields) { + Object value = field.value; + if (field.name.equalsIgnoreCase(fieldName)) { + return value; + } else if (value instanceof Structure) { + value = ((Structure) value).getFlatFieldValue(fieldName); + if (value != null) {// we can compare references here, since we use one static object as a NULL field value + return value; + } + } + } + return null; + } + + /** + * This methos should be used on structures that are of a 'ListBase' type. It creates a List of structures that are + * held by this structure within the blend file. + * @return a list of filled structures + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + * @throws IllegalArgumentException + * this exception is thrown if the type of the structure is not 'ListBase' + */ + public List evaluateListBase() throws BlenderFileException { + if (!"ListBase".equals(type)) { + throw new IllegalStateException("This structure is not of type: 'ListBase'"); + } + Pointer first = (Pointer) this.getFieldValue("first"); + Pointer last = (Pointer) this.getFieldValue("last"); + long currentAddress = 0; + long lastAddress = last.getOldMemoryAddress(); + List result = new LinkedList(); + while (currentAddress != lastAddress) { + currentAddress = first.getOldMemoryAddress(); + Structure structure = first.fetchData().get(0); + result.add(structure); + first = (Pointer) structure.getFlatFieldValue("next"); + } + return result; + } + + /** + * This method returns the type of the structure. + * @return the type of the structure + */ + public String getType() { + return type; + } + + /** + * This method returns the amount of fields for the current structure. + * @return the amount of fields for the current structure + */ + public int getFieldsAmount() { + return fields.length; + } + + /** + * This method returns the full field name of the given index. + * @param fieldIndex + * the index of the field + * @return the full field name of the given index + */ + public String getFieldFullName(int fieldIndex) { + return fields[fieldIndex].getFullName(); + } + + /** + * This method returns the field type of the given index. + * @param fieldIndex + * the index of the field + * @return the field type of the given index + */ + public String getFieldType(int fieldIndex) { + return fields[fieldIndex].type; + } + + /** + * This method returns the address of the structure. The strucutre should be filled with data otherwise an exception + * is thrown. + * @return the address of the feature stored in this structure + */ + public Long getOldMemoryAddress() { + if (oldMemoryAddress.longValue() == -1L) { + throw new IllegalStateException("Call the 'fill' method and fill the structure with data first!"); + } + return oldMemoryAddress; + } + + /** + * This method returns the name of the structure. If the structure has an ID field then the name is returned. + * Otherwise the name does not exists and the method returns null. + * @return the name of the structure read from the ID field or null + */ + public String getName() { + Object fieldValue = this.getFieldValue("ID"); + if (fieldValue instanceof Structure) { + Structure id = (Structure) fieldValue; + return id == null ? null : id.getFieldValue("name").toString().substring(2);// blender adds 2-charactes as a name prefix + } + return null; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder("struct ").append(type).append(" {\n"); + for (int i = 0; i < fields.length; ++i) { + result.append(fields[i].toString()).append('\n'); + } + return result.append('}').toString(); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return new Structure(this); + } + + /** + * This enum enumerates all known data types that can be found in the blend file. + * @author Marcin Roguski (Kaelthas) + */ + /* package */static enum DataType { + + CHARACTER, SHORT, INTEGER, LONG, FLOAT, DOUBLE, VOID, STRUCTURE, POINTER; + /** The map containing the known primary types. */ + private static final Map PRIMARY_TYPES = new HashMap(10); + + static { + PRIMARY_TYPES.put("char", CHARACTER); + PRIMARY_TYPES.put("uchar", CHARACTER); + PRIMARY_TYPES.put("short", SHORT); + PRIMARY_TYPES.put("ushort", SHORT); + PRIMARY_TYPES.put("int", INTEGER); + PRIMARY_TYPES.put("long", LONG); + PRIMARY_TYPES.put("ulong", LONG); + PRIMARY_TYPES.put("uint64_t", LONG); + PRIMARY_TYPES.put("float", FLOAT); + PRIMARY_TYPES.put("double", DOUBLE); + PRIMARY_TYPES.put("void", VOID); + } + + /** + * This method returns the data type that is appropriate to the given type name. WARNING! The type recognition + * is case sensitive! + * @param type + * the type name of the data + * @param blenderContext + * the blender context + * @return appropriate enum value to the given type name + * @throws BlenderFileException + * this exception is thrown if the given type name does not exist in the blend file + */ + public static DataType getDataType(String type, BlenderContext blenderContext) throws BlenderFileException { + DataType result = PRIMARY_TYPES.get(type); + if (result != null) { + return result; + } + if (blenderContext.getDnaBlockData().hasStructure(type)) { + return STRUCTURE; + } + throw new BlenderFileException("Unknown data type: " + type); + } + + /** + * @return a collection of known primary types names + */ + /* package */static Collection getKnownPrimaryTypesNames() { + return PRIMARY_TYPES.keySet(); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java new file mode 100644 index 000000000..1be4c4475 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java @@ -0,0 +1,186 @@ +package com.jme3.scene.plugins.blender.landscape; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.light.AmbientLight; +import com.jme3.light.Light; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.ColorBand; +import com.jme3.scene.plugins.blender.textures.CombinedTexture; +import com.jme3.scene.plugins.blender.textures.ImageUtils; +import com.jme3.scene.plugins.blender.textures.TextureHelper; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.TextureCubeMap; +import com.jme3.util.SkyFactory; + +/** + * The class that allows to load the following:
  • the ambient light of the scene
  • the sky of the scene (with or without texture) + * + * @author Marcin Roguski (Kaelthas) + */ +public class LandscapeHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(LandscapeHelper.class.getName()); + + private static final int SKYTYPE_BLEND = 1; + private static final int SKYTYPE_REAL = 2; + private static final int SKYTYPE_PAPER = 4; + + public LandscapeHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + /** + * Loads scene ambient light. + * @param worldStructure + * the world's blender structure + * @return the scene's ambient light + */ + public Light toAmbientLight(Structure worldStructure) { + LOGGER.fine("Loading ambient light."); + AmbientLight ambientLight = new AmbientLight(); + float ambr = ((Number) worldStructure.getFieldValue("ambr")).floatValue(); + float ambg = ((Number) worldStructure.getFieldValue("ambg")).floatValue(); + float ambb = ((Number) worldStructure.getFieldValue("ambb")).floatValue(); + ColorRGBA ambientLightColor = new ColorRGBA(ambr, ambg, ambb, 0.0f); + ambientLight.setColor(ambientLightColor); + LOGGER.log(Level.FINE, "Loaded ambient light: {0}.", ambientLightColor); + return ambientLight; + } + + /** + * Loads the background color. + * @param worldStructure + * the world's structure + * @return the horizon color of the world which is used as a background color. + */ + public ColorRGBA toBackgroundColor(Structure worldStructure) { + float horr = ((Number) worldStructure.getFieldValue("horr")).floatValue(); + float horg = ((Number) worldStructure.getFieldValue("horg")).floatValue(); + float horb = ((Number) worldStructure.getFieldValue("horb")).floatValue(); + return new ColorRGBA(horr, horg, horb, 1); + } + + /** + * Loads scene's sky. Sky can be plain or textured. + * If no sky type is selected in blender then no sky is loaded. + * @param worldStructure + * the world's structure + * @return the scene's sky + * @throws BlenderFileException + * blender exception is thrown when problems with blender file occur + */ + public Spatial toSky(Structure worldStructure) throws BlenderFileException { + int skytype = ((Number) worldStructure.getFieldValue("skytype")).intValue(); + if (skytype == 0) { + return null; + } + + LOGGER.fine("Loading sky."); + ColorRGBA horizontalColor = this.toBackgroundColor(worldStructure); + + float zenr = ((Number) worldStructure.getFieldValue("zenr")).floatValue(); + float zeng = ((Number) worldStructure.getFieldValue("zeng")).floatValue(); + float zenb = ((Number) worldStructure.getFieldValue("zenb")).floatValue(); + ColorRGBA zenithColor = new ColorRGBA(zenr, zeng, zenb, 1); + + // jutr for this case load generated textures wheather user had set it or not because those might be needed to properly load the sky + boolean loadGeneratedTextures = blenderContext.getBlenderKey().isLoadGeneratedTextures(); + blenderContext.getBlenderKey().setLoadGeneratedTextures(true); + + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + List loadedTextures = null; + try { + loadedTextures = textureHelper.readTextureData(worldStructure, new float[] { horizontalColor.r, horizontalColor.g, horizontalColor.b, horizontalColor.a }, true); + } finally { + blenderContext.getBlenderKey().setLoadGeneratedTextures(loadGeneratedTextures); + } + + TextureCubeMap texture = null; + if (loadedTextures != null && loadedTextures.size() > 0) { + if (loadedTextures.size() > 1) { + throw new IllegalStateException("There should be only one combined texture for sky!"); + } + CombinedTexture combinedTexture = loadedTextures.get(0); + texture = combinedTexture.generateSkyTexture(horizontalColor, zenithColor, blenderContext); + } else { + LOGGER.fine("Preparing colors for colorband."); + int colorbandType = ColorBand.IPO_CARDINAL; + List colorbandColors = new ArrayList(3); + colorbandColors.add(horizontalColor); + if ((skytype & SKYTYPE_BLEND) != 0) { + if ((skytype & SKYTYPE_PAPER) != 0) { + colorbandType = ColorBand.IPO_LINEAR; + } + if ((skytype & SKYTYPE_REAL) != 0) { + colorbandColors.add(0, zenithColor); + } + colorbandColors.add(zenithColor); + } + + int size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize(); + + List positions = new ArrayList(colorbandColors.size()); + positions.add(0); + if (colorbandColors.size() == 2) { + positions.add(size - 1); + } else if (colorbandColors.size() == 3) { + positions.add(size / 2); + positions.add(size - 1); + } + + LOGGER.fine("Generating sky texture."); + float[][] values = new ColorBand(colorbandType, colorbandColors, positions, size).computeValues(); + + Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6); + PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat()); + TexturePixel pixel = new TexturePixel(); + + LOGGER.fine("Creating side textures."); + int[] sideImagesIndexes = new int[] { 0, 1, 4, 5 }; + for (int i : sideImagesIndexes) { + for (int y = 0; y < size; ++y) { + pixel.red = values[y][0]; + pixel.green = values[y][1]; + pixel.blue = values[y][2]; + + for (int x = 0; x < size; ++x) { + pixelIO.write(image, i, pixel, x, y); + } + } + } + + LOGGER.fine("Creating top texture."); + pixelIO.read(image, 0, pixel, 0, image.getHeight() - 1); + for (int y = 0; y < size; ++y) { + for (int x = 0; x < size; ++x) { + pixelIO.write(image, 3, pixel, x, y); + } + } + + LOGGER.fine("Creating bottom texture."); + pixelIO.read(image, 0, pixel, 0, 0); + for (int y = 0; y < size; ++y) { + for (int x = 0; x < size; ++x) { + pixelIO.write(image, 2, pixel, x, y); + } + } + + texture = new TextureCubeMap(image); + } + + LOGGER.fine("Sky texture created. Creating sky."); + return SkyFactory.createSky(blenderContext.getAssetManager(), texture, false); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java new file mode 100644 index 000000000..914d6f6b1 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.lights; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.scene.LightNode; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * A class that is used in light calculations. + * @author Marcin Roguski + */ +public class LightHelper extends AbstractBlenderHelper { + + private static final Logger LOGGER = Logger.getLogger(LightHelper.class.getName()); + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in + * different blender versions. + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public LightHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + public LightNode toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (result != null) { + return result; + } + Light light = null; + int type = ((Number) structure.getFieldValue("type")).intValue(); + switch (type) { + case 0:// Lamp + light = new PointLight(); + float distance = ((Number) structure.getFieldValue("dist")).floatValue(); + ((PointLight) light).setRadius(distance); + break; + case 1:// Sun + LOGGER.log(Level.WARNING, "'Sun' lamp is not supported in jMonkeyEngine."); + break; + case 2:// Spot + light = new SpotLight(); + // range + ((SpotLight) light).setSpotRange(((Number) structure.getFieldValue("dist")).floatValue()); + // outer angle + float outerAngle = ((Number) structure.getFieldValue("spotsize")).floatValue() * FastMath.DEG_TO_RAD * 0.5f; + ((SpotLight) light).setSpotOuterAngle(outerAngle); + + // inner angle + float spotblend = ((Number) structure.getFieldValue("spotblend")).floatValue(); + spotblend = FastMath.clamp(spotblend, 0, 1); + float innerAngle = outerAngle * (1 - spotblend); + ((SpotLight) light).setSpotInnerAngle(innerAngle); + break; + case 3:// Hemi + LOGGER.log(Level.WARNING, "'Hemi' lamp is not supported in jMonkeyEngine."); + break; + case 4:// Area + light = new DirectionalLight(); + break; + default: + throw new BlenderFileException("Unknown light source type: " + type); + } + if (light != null) { + float r = ((Number) structure.getFieldValue("r")).floatValue(); + float g = ((Number) structure.getFieldValue("g")).floatValue(); + float b = ((Number) structure.getFieldValue("b")).floatValue(); + light.setColor(new ColorRGBA(r, g, b, 1.0f)); + result = new LightNode(null, light); + } + return result; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/IAlphaMask.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/IAlphaMask.java new file mode 100644 index 000000000..7fddd3d41 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/IAlphaMask.java @@ -0,0 +1,26 @@ +package com.jme3.scene.plugins.blender.materials; + +/** + * An interface used in calculating alpha mask during particles' texture calculations. + * @author Marcin Roguski (Kaelthas) + */ +/* package */interface IAlphaMask { + /** + * This method sets the size of the texture's image. + * @param width + * the width of the image + * @param height + * the height of the image + */ + void setImageSize(int width, int height); + + /** + * This method returns the alpha value for the specified texture position. + * @param x + * the X coordinate of the texture position + * @param y + * the Y coordinate of the texture position + * @return the alpha value for the specified texture position + */ + byte getAlpha(float x, float y); +} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java new file mode 100644 index 000000000..8132d038b --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java @@ -0,0 +1,327 @@ +package com.jme3.scene.plugins.blender.materials; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.material.Material; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.material.RenderState.FaceCullMode; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.Geometry; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialHelper.DiffuseShader; +import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader; +import com.jme3.scene.plugins.blender.textures.CombinedTexture; +import com.jme3.scene.plugins.blender.textures.TextureHelper; +import com.jme3.texture.Texture; +import com.jme3.util.BufferUtils; + +/** + * This class holds the data about the material. + * @author Marcin Roguski (Kaelthas) + */ +public final class MaterialContext { + private static final Logger LOGGER = Logger.getLogger(MaterialContext.class.getName()); + + // texture mapping types + public static final int MTEX_COL = 0x01; + public static final int MTEX_NOR = 0x02; + public static final int MTEX_SPEC = 0x04; + public static final int MTEX_EMIT = 0x40; + public static final int MTEX_ALPHA = 0x80; + public static final int MTEX_AMB = 0x800; + + /* package */final String name; + /* package */final List loadedTextures; + + /* package */final ColorRGBA diffuseColor; + /* package */final DiffuseShader diffuseShader; + /* package */final SpecularShader specularShader; + /* package */final ColorRGBA specularColor; + /* package */final ColorRGBA ambientColor; + /* package */final float shininess; + /* package */final boolean shadeless; + /* package */final boolean vertexColor; + /* package */final boolean transparent; + /* package */final boolean vTangent; + /* package */FaceCullMode faceCullMode; + + /* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + name = structure.getName(); + + int mode = ((Number) structure.getFieldValue("mode")).intValue(); + shadeless = (mode & 0x4) != 0; + vertexColor = (mode & 0x80) != 0; + vTangent = (mode & 0x4000000) != 0; // NOTE: Requires tangents + + int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue(); + diffuseShader = DiffuseShader.values()[diff_shader]; + + if (shadeless) { + float r = ((Number) structure.getFieldValue("r")).floatValue(); + float g = ((Number) structure.getFieldValue("g")).floatValue(); + float b = ((Number) structure.getFieldValue("b")).floatValue(); + float alpha = ((Number) structure.getFieldValue("alpha")).floatValue(); + + diffuseColor = new ColorRGBA(r, g, b, alpha); + specularShader = null; + specularColor = ambientColor = null; + shininess = 0.0f; + } else { + diffuseColor = this.readDiffuseColor(structure, diffuseShader); + + int spec_shader = ((Number) structure.getFieldValue("spec_shader")).intValue(); + specularShader = SpecularShader.values()[spec_shader]; + specularColor = this.readSpecularColor(structure); + float shininess = ((Number) structure.getFieldValue("har")).floatValue();// this is (probably) the specular hardness in blender + this.shininess = shininess > 0.0f ? shininess : MaterialHelper.DEFAULT_SHININESS; + + float r = ((Number) structure.getFieldValue("ambr")).floatValue(); + float g = ((Number) structure.getFieldValue("ambg")).floatValue(); + float b = ((Number) structure.getFieldValue("ambb")).floatValue(); + float alpha = ((Number) structure.getFieldValue("alpha")).floatValue(); + ambientColor = new ColorRGBA(r, g, b, alpha); + } + + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + loadedTextures = textureHelper.readTextureData(structure, new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a }, false); + + // veryfying if the transparency is present + // (in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when + // it is not required + boolean transparent = false; + if (diffuseColor != null) { + transparent = diffuseColor.a < 1.0f; + if (loadedTextures.size() > 0) {// texutre covers the material color + diffuseColor.set(1, 1, 1, 1); + } + } + if (specularColor != null) { + transparent = transparent || specularColor.a < 1.0f; + } + if (ambientColor != null) { + transparent = transparent || ambientColor.a < 1.0f; + } + this.transparent = transparent; + } + + /** + * Applies material to a given geometry. + * + * @param geometry + * the geometry + * @param geometriesOMA + * the geometries OMA + * @param userDefinedUVCoordinates + * UV coords defined by user + * @param blenderContext + * the blender context + */ + public void applyMaterial(Geometry geometry, Long geometriesOMA, LinkedHashMap> userDefinedUVCoordinates, BlenderContext blenderContext) { + Material material = null; + if (shadeless) { + material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + + if (!transparent) { + diffuseColor.a = 1; + } + + material.setColor("Color", diffuseColor); + } else { + material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md"); + material.setBoolean("UseMaterialColors", Boolean.TRUE); + + // setting the colors + material.setBoolean("Minnaert", diffuseShader == DiffuseShader.MINNAERT); + if (!transparent) { + diffuseColor.a = 1; + } + material.setColor("Diffuse", diffuseColor); + + material.setBoolean("WardIso", specularShader == SpecularShader.WARDISO); + material.setColor("Specular", specularColor); + material.setFloat("Shininess", shininess); + + material.setColor("Ambient", ambientColor); + } + + // applying textures + if (loadedTextures != null && loadedTextures.size() > 0) { + int textureIndex = 0; + if (loadedTextures.size() > TextureHelper.TEXCOORD_TYPES.length) { + LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different textures. JME supports only {0} UV mappings.", TextureHelper.TEXCOORD_TYPES.length); + } + for (CombinedTexture combinedTexture : loadedTextures) { + if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) { + combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext); + + this.setTexture(material, combinedTexture.getMappingType(), combinedTexture.getResultTexture()); + List uvs = combinedTexture.getResultUVS(); + VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]); + uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()]))); + geometry.getMesh().setBuffer(uvCoordsBuffer); + } else { + LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length); + } + } + } else if (userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { + LOGGER.fine("No textures found for the mesh, but UV coordinates are applied."); + int textureIndex = 0; + if (userDefinedUVCoordinates.size() > TextureHelper.TEXCOORD_TYPES.length) { + LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different UV coordinates for the mesh. JME supports only {0} UV coordinates buffers.", TextureHelper.TEXCOORD_TYPES.length); + } + for (Entry> entry : userDefinedUVCoordinates.entrySet()) { + if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) { + List uvs = entry.getValue(); + VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]); + uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()]))); + geometry.getMesh().setBuffer(uvCoordsBuffer); + } else { + LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length); + } + } + } + + // applying additional data + material.setName(name); + if (vertexColor) { + material.setBoolean(shadeless ? "VertexColor" : "UseVertexColor", true); + } + material.getAdditionalRenderState().setFaceCullMode(faceCullMode != null ? faceCullMode : blenderContext.getBlenderKey().getFaceCullMode()); + if (transparent) { + material.setTransparent(true); + material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + geometry.setQueueBucket(Bucket.Transparent); + } + + geometry.setMaterial(material); + } + + /** + * Sets the texture to the given material. + * + * @param material + * the material that we add texture to + * @param mapTo + * the texture mapping type + * @param texture + * the added texture + */ + private void setTexture(Material material, int mapTo, Texture texture) { + switch (mapTo) { + case MTEX_COL: + material.setTexture(shadeless ? MaterialHelper.TEXTURE_TYPE_COLOR : MaterialHelper.TEXTURE_TYPE_DIFFUSE, texture); + break; + case MTEX_NOR: + material.setTexture(MaterialHelper.TEXTURE_TYPE_NORMAL, texture); + break; + case MTEX_SPEC: + material.setTexture(MaterialHelper.TEXTURE_TYPE_SPECULAR, texture); + break; + case MTEX_EMIT: + material.setTexture(MaterialHelper.TEXTURE_TYPE_GLOW, texture); + break; + case MTEX_ALPHA: + if (!shadeless) { + material.setTexture(MaterialHelper.TEXTURE_TYPE_ALPHA, texture); + } else { + LOGGER.warning("JME does not support alpha map on unshaded material. Material name is " + name); + } + break; + case MTEX_AMB: + material.setTexture(MaterialHelper.TEXTURE_TYPE_LIGHTMAP, texture); + break; + default: + LOGGER.severe("Unknown mapping type: " + mapTo); + } + } + + /** + * @return true if the material has at least one generated texture and false otherwise + */ + public boolean hasGeneratedTextures() { + if (loadedTextures != null) { + for (CombinedTexture generatedTextures : loadedTextures) { + if (generatedTextures.hasGeneratedTextures()) { + return true; + } + } + } + return false; + } + + /** + * This method sets the face cull mode. + * @param faceCullMode + * the face cull mode + */ + public void setFaceCullMode(FaceCullMode faceCullMode) { + this.faceCullMode = faceCullMode; + } + + /** + * This method returns the diffuse color. + * + * @param materialStructure + * the material structure + * @param diffuseShader + * the diffuse shader + * @return the diffuse color + */ + private ColorRGBA readDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) { + // bitwise 'or' of all textures mappings + int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue(); + + // diffuse color + float r = ((Number) materialStructure.getFieldValue("r")).floatValue(); + float g = ((Number) materialStructure.getFieldValue("g")).floatValue(); + float b = ((Number) materialStructure.getFieldValue("b")).floatValue(); + float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); + if ((commonMapto & 0x01) == 0x01) {// Col + return new ColorRGBA(r, g, b, alpha); + } else { + switch (diffuseShader) { + case FRESNEL: + case ORENNAYAR: + case TOON: + break;// TODO: find what is the proper modification + case MINNAERT: + case LAMBERT:// TODO: check if that is correct + float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue(); + r *= ref; + g *= ref; + b *= ref; + break; + default: + throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString()); + } + return new ColorRGBA(r, g, b, alpha); + } + } + + /** + * This method returns a specular color used by the material. + * + * @param materialStructure + * the material structure filled with data + * @return a specular color used by the material + */ + private ColorRGBA readSpecularColor(Structure materialStructure) { + float specularIntensity = ((Number) materialStructure.getFieldValue("spec")).floatValue(); + float r = ((Number) materialStructure.getFieldValue("specr")).floatValue() * specularIntensity; + float g = ((Number) materialStructure.getFieldValue("specg")).floatValue() * specularIntensity; + float b = ((Number) materialStructure.getFieldValue("specb")).floatValue() * specularIntensity; + float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); + return new ColorRGBA(r, g, b, alpha); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java new file mode 100644 index 000000000..f4fff839c --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.materials; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.material.MatParam; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.shader.VarType; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.util.BufferUtils; + +public class MaterialHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName()); + protected static final float DEFAULT_SHININESS = 20.0f; + + public static final String TEXTURE_TYPE_COLOR = "ColorMap"; + public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap"; + public static final String TEXTURE_TYPE_NORMAL = "NormalMap"; + public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap"; + public static final String TEXTURE_TYPE_GLOW = "GlowMap"; + public static final String TEXTURE_TYPE_ALPHA = "AlphaMap"; + public static final String TEXTURE_TYPE_LIGHTMAP = "LightMap"; + + public static final Integer ALPHA_MASK_NONE = Integer.valueOf(0); + public static final Integer ALPHA_MASK_CIRCLE = Integer.valueOf(1); + public static final Integer ALPHA_MASK_CONE = Integer.valueOf(2); + public static final Integer ALPHA_MASK_HYPERBOLE = Integer.valueOf(3); + protected final Map alphaMasks = new HashMap(); + + /** + * The type of the material's diffuse shader. + */ + public static enum DiffuseShader { + LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL + } + + /** + * The type of the material's specular shader. + */ + public static enum SpecularShader { + COOKTORRENCE, PHONG, BLINN, TOON, WARDISO + } + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender + * versions. + * + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public MaterialHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + // setting alpha masks + alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() { + public void setImageSize(int width, int height) { + } + + public byte getAlpha(float x, float y) { + return (byte) 255; + } + }); + alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() { + private float r; + private float[] center; + + public void setImageSize(int width, int height) { + r = Math.min(width, height) * 0.5f; + center = new float[] { width * 0.5f, height * 0.5f }; + } + + public byte getAlpha(float x, float y) { + float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))); + return (byte) (d >= r ? 0 : 255); + } + }); + alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() { + private float r; + private float[] center; + + public void setImageSize(int width, int height) { + r = Math.min(width, height) * 0.5f; + center = new float[] { width * 0.5f, height * 0.5f }; + } + + public byte getAlpha(float x, float y) { + float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))); + return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f); + } + }); + alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() { + private float r; + private float[] center; + + public void setImageSize(int width, int height) { + r = Math.min(width, height) * 0.5f; + center = new float[] { width * 0.5f, height * 0.5f }; + } + + public byte getAlpha(float x, float y) { + float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))) / r; + return d >= 1.0f ? 0 : (byte) ((-FastMath.sqrt((2.0f - d) * d) + 1.0f) * 255.0f); + } + }); + } + + /** + * This method converts the material structure to jme Material. + * @param structure + * structure with material data + * @param blenderContext + * the blender context + * @return jme material + * @throws BlenderFileException + * an exception is throw when problems with blend file occur + */ + public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.log(Level.FINE, "Loading material."); + MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (result != null) { + return result; + } + + result = new MaterialContext(structure, blenderContext); + LOGGER.log(Level.FINE, "Material''s name: {0}", result.name); + blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result); + return result; + } + + /** + * This method converts the given material into particles-usable material. + * The texture and glow color are being copied. + * The method assumes it receives the Lighting type of material. + * @param material + * the source material + * @param blenderContext + * the blender context + * @return material converted into particles-usable material + */ + public Material getParticlesMaterial(Material material, Integer alphaMaskIndex, BlenderContext blenderContext) { + Material result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); + + // copying texture + MatParam diffuseMap = material.getParam("DiffuseMap"); + if (diffuseMap != null) { + Texture texture = ((Texture) diffuseMap.getValue()).clone(); + + // applying alpha mask to the texture + Image image = texture.getImage(); + ByteBuffer sourceBB = image.getData(0); + sourceBB.rewind(); + int w = image.getWidth(); + int h = image.getHeight(); + ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4); + IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex); + iAlphaMask.setImageSize(w, h); + + for (int x = 0; x < w; ++x) { + for (int y = 0; y < h; ++y) { + bb.put(sourceBB.get()); + bb.put(sourceBB.get()); + bb.put(sourceBB.get()); + bb.put(iAlphaMask.getAlpha(x, y)); + } + } + + image = new Image(Format.RGBA8, w, h, bb); + texture.setImage(image); + + result.setTextureParam("Texture", VarType.Texture2D, texture); + } + + // copying glow color + MatParam glowColor = material.getParam("GlowColor"); + if (glowColor != null) { + ColorRGBA color = (ColorRGBA) glowColor.getValue(); + result.setParam("GlowColor", VarType.Vector3, color); + } + return result; + } + + /** + * This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or + * curve) but needs to have 'mat' field/ + * + * @param structureWithMaterials + * the structure containing the mesh data + * @param blenderContext + * the blender context + * @return a list of vertices colors, each color belongs to a single vertex + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + */ + public MaterialContext[] getMaterials(Structure structureWithMaterials, BlenderContext blenderContext) throws BlenderFileException { + Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat"); + MaterialContext[] materials = null; + if (ppMaterials.isNotNull()) { + List materialStructures = ppMaterials.fetchData(); + if (materialStructures != null && materialStructures.size() > 0) { + MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + materials = new MaterialContext[materialStructures.size()]; + int i = 0; + for (Structure s : materialStructures) { + materials[i++] = s == null ? null : materialHelper.toMaterialContext(s, blenderContext); + } + } + } + return materials; + } + + /** + * This method converts rgb values to hsv values. + * + * @param r + * red value of the color + * @param g + * green value of the color + * @param b + * blue value of the color + * @param hsv + * hsv values of a color (this table contains the result of the transformation) + */ + public void rgbToHsv(float r, float g, float b, float[] hsv) { + float cmax = r; + float cmin = r; + cmax = g > cmax ? g : cmax; + cmin = g < cmin ? g : cmin; + cmax = b > cmax ? b : cmax; + cmin = b < cmin ? b : cmin; + + hsv[2] = cmax; /* value */ + if (cmax != 0.0) { + hsv[1] = (cmax - cmin) / cmax; + } else { + hsv[1] = 0.0f; + hsv[0] = 0.0f; + } + if (hsv[1] == 0.0) { + hsv[0] = -1.0f; + } else { + float cdelta = cmax - cmin; + float rc = (cmax - r) / cdelta; + float gc = (cmax - g) / cdelta; + float bc = (cmax - b) / cdelta; + if (r == cmax) { + hsv[0] = bc - gc; + } else if (g == cmax) { + hsv[0] = 2.0f + rc - bc; + } else { + hsv[0] = 4.0f + gc - rc; + } + hsv[0] *= 60.0f; + if (hsv[0] < 0.0f) { + hsv[0] += 360.0f; + } + } + + hsv[0] /= 360.0f; + if (hsv[0] < 0.0f) { + hsv[0] = 0.0f; + } + } + + /** + * This method converts rgb values to hsv values. + * + * @param h + * hue + * @param s + * saturation + * @param v + * value + * @param rgb + * rgb result vector (should have 3 elements) + */ + public void hsvToRgb(float h, float s, float v, float[] rgb) { + h *= 360.0f; + if (s == 0.0) { + rgb[0] = rgb[1] = rgb[2] = v; + } else { + if (h == 360) { + h = 0; + } else { + h /= 60; + } + int i = (int) Math.floor(h); + float f = h - i; + float p = v * (1.0f - s); + float q = v * (1.0f - s * f); + float t = v * (1.0f - s * (1.0f - f)); + switch (i) { + case 0: + rgb[0] = v; + rgb[1] = t; + rgb[2] = p; + break; + case 1: + rgb[0] = q; + rgb[1] = v; + rgb[2] = p; + break; + case 2: + rgb[0] = p; + rgb[1] = v; + rgb[2] = t; + break; + case 3: + rgb[0] = p; + rgb[1] = q; + rgb[2] = v; + break; + case 4: + rgb[0] = t; + rgb[1] = p; + rgb[2] = v; + break; + case 5: + rgb[0] = v; + rgb[1] = p; + rgb[2] = q; + break; + } + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java new file mode 100644 index 000000000..45742d78b --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java @@ -0,0 +1,88 @@ +package com.jme3.scene.plugins.blender.meshes; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.jme3.scene.Geometry; + +/** + * Class that holds information about the mesh. + * + * @author Marcin Roguski (Kaelthas) + */ +public class MeshContext { + /** A map between material index and the geometry. */ + private Map> geometries = new HashMap>(); + /** The vertex reference map. */ + private Map>> vertexReferenceMap; + + /** + * Adds a geometry for the specified material index. + * @param materialIndex + * the material index + * @param geometry + * the geometry + */ + public void putGeometry(Integer materialIndex, Geometry geometry) { + List geomList = geometries.get(materialIndex); + if (geomList == null) { + geomList = new ArrayList(); + geometries.put(materialIndex, geomList); + } + geomList.add(geometry); + } + + /** + * @param materialIndex + * the material index + * @return vertices amount that is used by mesh with the specified material + */ + public int getVertexCount(int materialIndex) { + int result = 0; + for (Geometry geometry : geometries.get(materialIndex)) { + result += geometry.getVertexCount(); + } + return result; + } + + /** + * Returns material index for the geometry. + * @param geometry + * the geometry + * @return material index + * @throws IllegalStateException + * this exception is thrown when no material is found for the specified geometry + */ + public int getMaterialIndex(Geometry geometry) { + for (Entry> entry : geometries.entrySet()) { + for (Geometry g : entry.getValue()) { + if (g.equals(geometry)) { + return entry.getKey(); + } + } + } + throw new IllegalStateException("Cannot find material index for the given geometry: " + geometry); + } + + /** + * This method returns the vertex reference map. + * + * @return the vertex reference map + */ + public Map> getVertexReferenceMap(int materialIndex) { + return vertexReferenceMap.get(materialIndex); + } + + /** + * This method sets the vertex reference map. + * + * @param vertexReferenceMap + * the vertex reference map + */ + public void setVertexReferenceMap(Map>> vertexReferenceMap) { + this.vertexReferenceMap = vertexReferenceMap; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java new file mode 100644 index 000000000..3d10c1ecc --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.meshes; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.asset.BlenderKey.FeaturesToLoad; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.scene.Geometry; +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.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialContext; +import com.jme3.scene.plugins.blender.materials.MaterialHelper; +import com.jme3.scene.plugins.blender.meshes.builders.MeshBuilder; +import com.jme3.scene.plugins.blender.objects.Properties; +import com.jme3.scene.plugins.blender.textures.TextureHelper; +import com.jme3.util.BufferUtils; + +/** + * A class that is used in mesh calculations. + * + * @author Marcin Roguski (Kaelthas) + */ +public class MeshHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(MeshHelper.class.getName()); + + /** A type of UV data layer in traditional faced mesh (triangles or quads). */ + public static final int UV_DATA_LAYER_TYPE_FMESH = 5; + /** A type of UV data layer in bmesh type. */ + public static final int UV_DATA_LAYER_TYPE_BMESH = 16; + /** A material used for single lines and points. */ + private Material blackUnshadedMaterial; + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender + * versions. + * + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public MeshHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + /** + * This method reads converts the given structure into mesh. The given structure needs to be filled with the appropriate data. + * + * @param structure + * the structure we read the mesh from + * @return the mesh feature + * @throws BlenderFileException + */ + @SuppressWarnings("unchecked") + public List toMesh(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + List geometries = (List) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (geometries != null) { + List copiedGeometries = new ArrayList(geometries.size()); + for (Geometry geometry : geometries) { + copiedGeometries.add(geometry.clone()); + } + return copiedGeometries; + } + + String name = structure.getName(); + MeshContext meshContext = new MeshContext(); + LOGGER.log(Level.FINE, "Reading mesh: {0}.", name); + + LOGGER.fine("Loading materials."); + MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + MaterialContext[] materials = null; + if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { + materials = materialHelper.getMaterials(structure, blenderContext); + } + + LOGGER.fine("Reading vertices."); + MeshBuilder meshBuilder = new MeshBuilder(structure, materials, blenderContext); + if (meshBuilder.isEmpty()) { + LOGGER.fine("The geometry is empty."); + geometries = new ArrayList(0); + blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries); + blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext); + return geometries; + } + + meshContext.setVertexReferenceMap(meshBuilder.getVertexReferenceMap()); + + LOGGER.fine("Reading vertices groups (from the Object structure)."); + Structure parent = blenderContext.peekParent(); + Structure defbase = (Structure) parent.getFieldValue("defbase"); + List defs = defbase.evaluateListBase(); + String[] verticesGroups = new String[defs.size()]; + int defIndex = 0; + for (Structure def : defs) { + verticesGroups[defIndex++] = def.getFieldValue("name").toString(); + } + + LOGGER.fine("Reading custom properties."); + Properties properties = this.loadProperties(structure, blenderContext); + + LOGGER.fine("Generating meshes."); + Map> meshes = meshBuilder.buildMeshes(); + geometries = new ArrayList(meshes.size()); + for (Entry> meshEntry : meshes.entrySet()) { + int materialIndex = meshEntry.getKey(); + for (Mesh mesh : meshEntry.getValue()) { + LOGGER.fine("Preparing the result part."); + Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh); + if (properties != null && properties.getValue() != null) { + this.applyProperties(geometry, properties); + } + geometries.add(geometry); + meshContext.putGeometry(materialIndex, geometry); + } + } + + // store the data in blender context before applying the material + blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries); + blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext); + + // apply materials only when all geometries are in place + if (materials != null) { + for (Geometry geometry : geometries) { + int materialNumber = meshContext.getMaterialIndex(geometry); + if (materialNumber < 0) { + geometry.setMaterial(this.getBlackUnshadedMaterial(blenderContext)); + } else if (materials[materialNumber] != null) { + LinkedHashMap> uvCoordinates = meshBuilder.getUVCoordinates(materialNumber); + MaterialContext materialContext = materials[materialNumber]; + materialContext.applyMaterial(geometry, structure.getOldMemoryAddress(), uvCoordinates, blenderContext); + } else { + geometry.setMaterial(blenderContext.getDefaultMaterial()); + LOGGER.warning("The importer came accross mesh that points to a null material. Default material is used to prevent loader from crashing, " + "but the model might look not the way it should. Sometimes blender does not assign materials properly. " + "Enter the edit mode and assign materials once more to your faces."); + } + } + } else { + // add UV coordinates if they are defined even if the material is not applied to the model + List uvCoordsBuffer = null; + if (meshBuilder.hasUVCoordinates()) { + Map> uvs = meshBuilder.getUVCoordinates(0); + if (uvs != null && uvs.size() > 0) { + uvCoordsBuffer = new ArrayList(uvs.size()); + int uvIndex = 0; + for (Entry> entry : uvs.entrySet()) { + VertexBuffer buffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[uvIndex++]); + buffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(entry.getValue().toArray(new Vector2f[uvs.size()]))); + uvCoordsBuffer.add(buffer); + } + } + } + + for (Geometry geometry : geometries) { + Mode mode = geometry.getMesh().getMode(); + if (mode != Mode.Triangles && mode != Mode.TriangleFan && mode != Mode.TriangleStrip) { + geometry.setMaterial(this.getBlackUnshadedMaterial(blenderContext)); + } else { + Material defaultMaterial = blenderContext.getDefaultMaterial(); + if(geometry.getMesh().getBuffer(Type.Color) != null) { + defaultMaterial = defaultMaterial.clone(); + defaultMaterial.setBoolean("VertexColor", true); + } + geometry.setMaterial(defaultMaterial); + } + if (uvCoordsBuffer != null) { + for (VertexBuffer buffer : uvCoordsBuffer) { + geometry.getMesh().setBuffer(buffer); + } + } + } + } + + return geometries; + } + + /** + * Tells if the given mesh structure supports BMesh. + * + * @param meshStructure + * the mesh structure + * @return true if BMesh is supported and false otherwise + */ + public boolean isBMeshCompatible(Structure meshStructure) { + Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); + Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); + return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull(); + } + + private synchronized Material getBlackUnshadedMaterial(BlenderContext blenderContext) { + if (blackUnshadedMaterial == null) { + blackUnshadedMaterial = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + blackUnshadedMaterial.setColor("Color", ColorRGBA.Black); + } + return blackUnshadedMaterial; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java new file mode 100644 index 000000000..0d694ed80 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java @@ -0,0 +1,586 @@ +package com.jme3.scene.plugins.blender.meshes.builders; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Logger; + +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; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.meshes.MeshHelper; +import com.jme3.scene.plugins.blender.textures.UserUVCollection; +import com.jme3.util.BufferUtils; + +/** + * A builder class for meshes made of triangles (faces). + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class FaceMeshBuilder { + private static final Logger LOGGER = Logger.getLogger(FaceMeshBuilder.class.getName()); + + /** An array of reference vertices. */ + private Vector3f[][] verticesAndNormals; + /** An list of vertices colors. */ + private List verticesColors; + /** A variable that indicates if the model uses generated textures. */ + private boolean usesGeneratedTextures; + /** + * This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' + * positions (it simply tells which vertex is referenced where in the result list). + */ + private Map>> globalVertexReferenceMap; + /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ + private Map> normalMap = new HashMap>(); + /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ + private Map> vertexMap = new HashMap>(); + /** The following map sorts vertices colors by material number (because in jme Mesh can have only one material). */ + private Map> vertexColorsMap = new HashMap>(); + /** The following map sorts indexes by material number (because in jme Mesh can have only one material). */ + private Map> indexMap = new HashMap>(); + /** A collection of user defined UV coordinates (one mesh can have more than one such mappings). */ + private UserUVCollection userUVCollection = new UserUVCollection(); + + /** + * Constructor. Stores the given array (not copying it). + * The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied. + * The amount of vertices is always faceCount * 3. + * @param verticesAndNormals + * the reference vertices and normals array + * @param usesGeneratedTextures + * a variable that indicates if the model uses generated textures or not + */ + public FaceMeshBuilder(Vector3f[][] verticesAndNormals, boolean usesGeneratedTextures) { + this.verticesAndNormals = verticesAndNormals; + this.usesGeneratedTextures = usesGeneratedTextures; + globalVertexReferenceMap = new HashMap>>(verticesAndNormals.length); + } + + public void readMesh(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + verticesColors = this.getVerticesColors(structure, blenderContext); + MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); + + if (meshHelper.isBMeshCompatible(structure)) { + this.readBMesh(structure); + } else { + this.readTraditionalFaces(structure); + } + } + + /** + * Builds the meshes. + * @return a map between material index and the mesh + */ + public Map buildMeshes() { + Map result = new HashMap(indexMap.size()); + + for (Entry> meshEntry : indexMap.entrySet()) { + int materialIndex = meshEntry.getKey(); + // key is the material index + // value is a list of vertex indices + Mesh mesh = new Mesh(); + + // creating vertices indices for this mesh + List indexList = meshEntry.getValue(); + if (this.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) { + short[] indices = new short[indexList.size()]; + for (int i = 0; i < indexList.size(); ++i) { + indices[i] = indexList.get(i).shortValue(); + } + mesh.setBuffer(Type.Index, 1, indices); + } else { + int[] indices = new int[indexList.size()]; + for (int i = 0; i < indexList.size(); ++i) { + indices[i] = indexList.get(i).intValue(); + } + mesh.setBuffer(Type.Index, 1, indices); + } + + LOGGER.fine("Creating vertices buffer."); + VertexBuffer verticesBuffer = new VertexBuffer(Type.Position); + verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(this.getVertices(materialIndex))); + mesh.setBuffer(verticesBuffer); + + LOGGER.fine("Creating normals buffer."); + VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal); + normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(this.getNormals(materialIndex))); + mesh.setBuffer(normalsBuffer); + + if (verticesColors != null) { + LOGGER.fine("Setting vertices colors."); + mesh.setBuffer(Type.Color, 4, this.getVertexColorsBuffer(materialIndex)); + mesh.getBuffer(Type.Color).setNormalized(true); + } + + result.put(materialIndex, mesh); + } + + return result; + } + + /** + * This method reads the mesh from the new BMesh system. + * + * @param meshStructure + * the mesh structure + * @throws BlenderFileException + * an exception is thrown when there are problems with the + * blender file + */ + private void readBMesh(Structure meshStructure) throws BlenderFileException { + LOGGER.fine("Reading BMesh."); + Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); + Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); + Map uvCoordinatesForFace = new HashMap(); + + if (pMPoly.isNotNull() && pMLoop.isNotNull()) { + Map> uvs = this.loadUVCoordinates(meshStructure, true); + List polys = pMPoly.fetchData(); + List loops = pMLoop.fetchData(); + int[] vertexColorIndex = verticesColors == null ? null : new int[3]; + for (Structure poly : polys) { + int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue(); + int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue(); + int totLoop = ((Number) poly.getFieldValue("totloop")).intValue(); + boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00; + int[] vertexIndexes = new int[totLoop]; + + for (int i = loopStart; i < loopStart + totLoop; ++i) { + vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue(); + } + + int i = 0; + while (i < totLoop - 2) { + int v1 = vertexIndexes[0]; + int v2 = vertexIndexes[i + 1]; + int v3 = vertexIndexes[i + 2]; + if (vertexColorIndex != null) { + vertexColorIndex[0] = loopStart; + vertexColorIndex[1] = loopStart + i + 1; + vertexColorIndex[2] = loopStart + i + 2; + } + + if (uvs != null) { + // uvs always must be added wheater we have texture or not + for (Entry> entry : uvs.entrySet()) { + Vector2f[] uvCoordsForASingleFace = new Vector2f[3]; + uvCoordsForASingleFace[0] = entry.getValue().get(loopStart); + uvCoordsForASingleFace[1] = entry.getValue().get(loopStart + i + 1); + uvCoordsForASingleFace[2] = entry.getValue().get(loopStart + i + 2); + uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace); + } + } + + this.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex); + uvCoordinatesForFace.clear(); + ++i; + } + } + } + } + + /** + * This method reads the mesh from traditional triangle/quad storing + * structures. + * + * @param meshStructure + * the mesh structure + * @throws BlenderFileException + * an exception is thrown when there are problems with the + * blender file + */ + private void readTraditionalFaces(Structure meshStructure) throws BlenderFileException { + LOGGER.fine("Reading traditional faces."); + Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface"); + List mFaces = pMFace.isNotNull() ? pMFace.fetchData() : null; + if (mFaces != null && mFaces.size() > 0) { + // indicates if the material with the specified number should have a texture attached + Map> uvs = this.loadUVCoordinates(meshStructure, false); + Map uvCoordinatesForFace = new HashMap(); + int[] vertexColorIndex = verticesColors == null ? null : new int[3]; + for (int i = 0; i < mFaces.size(); ++i) { + Structure mFace = mFaces.get(i); + int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue(); + boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00; + if (uvs != null) { + // uvs always must be added wheater we have texture or not + for (Entry> entry : uvs.entrySet()) { + Vector2f[] uvCoordsForASingleFace = new Vector2f[3]; + uvCoordsForASingleFace[0] = entry.getValue().get(i * 4); + uvCoordsForASingleFace[1] = entry.getValue().get(i * 4 + 1); + uvCoordsForASingleFace[2] = entry.getValue().get(i * 4 + 2); + uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace); + } + } + + int v1 = ((Number) mFace.getFieldValue("v1")).intValue(); + int v2 = ((Number) mFace.getFieldValue("v2")).intValue(); + int v3 = ((Number) mFace.getFieldValue("v3")).intValue(); + int v4 = ((Number) mFace.getFieldValue("v4")).intValue(); + if (vertexColorIndex != null) { + vertexColorIndex[0] = i * 4; + vertexColorIndex[1] = i * 4 + 1; + vertexColorIndex[2] = i * 4 + 2; + } + + this.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex); + uvCoordinatesForFace.clear(); + if (v4 > 0) { + if (uvs != null) { + // uvs always must be added wheater we have texture or not + for (Entry> entry : uvs.entrySet()) { + Vector2f[] uvCoordsForASingleFace = new Vector2f[3]; + uvCoordsForASingleFace[0] = entry.getValue().get(i * 4); + uvCoordsForASingleFace[1] = entry.getValue().get(i * 4 + 2); + uvCoordsForASingleFace[2] = entry.getValue().get(i * 4 + 3); + uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace); + } + } + if (vertexColorIndex != null) { + vertexColorIndex[0] = i * 4; + vertexColorIndex[1] = i * 4 + 2; + vertexColorIndex[2] = i * 4 + 3; + } + this.appendFace(v1, v3, v4, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex); + uvCoordinatesForFace.clear(); + } + } + } + } + + /** + * This method adds a face to the mesh. + * @param v1 + * index of the 1'st vertex from the reference vertex table + * @param v2 + * index of the 2'nd vertex from the reference vertex table + * @param v3 + * index of the 3'rd vertex from the reference vertex table + * @param smooth + * indicates if this face should have smooth shading or flat shading + * @param materialNumber + * the material number for this face + * @param uvsForFace + * a 3-element array of vertices UV coordinates mapped to the UV's set name + * @param vertexColorIndex + * a table of 3 elements that indicates the verts' colors indexes + */ + private void appendFace(int v1, int v2, int v3, boolean smooth, int materialNumber, Map uvsForFace, int[] vertexColorIndex) { + if (uvsForFace != null && uvsForFace.size() > 0) { + for (Entry entry : uvsForFace.entrySet()) { + if (entry.getValue().length != 3) { + throw new IllegalArgumentException("UV coordinates must be a 3-element array!" + (entry.getKey() != null ? " (UV set name: " + entry.getKey() + ')' : "")); + } + } + } + + // getting the required lists + List indexList = indexMap.get(materialNumber); + if (indexList == null) { + indexList = new ArrayList(); + indexMap.put(materialNumber, indexList); + } + List vertexList = vertexMap.get(materialNumber); + if (vertexList == null) { + vertexList = new ArrayList(); + vertexMap.put(materialNumber, vertexList); + } + List vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null; + if (vertexColorsList == null && vertexColorsMap != null) { + vertexColorsList = new ArrayList(); + vertexColorsMap.put(materialNumber, vertexColorsList); + } + List normalList = normalMap.get(materialNumber); + if (normalList == null) { + normalList = new ArrayList(); + normalMap.put(materialNumber, normalList); + } + Map> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber); + if (vertexReferenceMap == null) { + vertexReferenceMap = new HashMap>(); + globalVertexReferenceMap.put(materialNumber, vertexReferenceMap); + } + + // creating faces + Integer[] index = new Integer[] { v1, v2, v3 }; + if (smooth && !usesGeneratedTextures) { + for (int i = 0; i < 3; ++i) { + if (!vertexReferenceMap.containsKey(index[i])) { + // if this index is not yet used then create another face + this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); + if (uvsForFace != null) { + for (Entry entry : uvsForFace.entrySet()) { + userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size()); + } + } + + vertexList.add(verticesAndNormals[index[i]][0]); + if (verticesColors != null) { + vertexColorsList.add(verticesColors.get(vertexColorIndex[i])); + } + normalList.add(verticesAndNormals[index[i]][1]); + + index[i] = vertexList.size() - 1; + } else if (uvsForFace != null) { + // if the index is used then check if the vertexe's UV coordinates match, if yes then the vertex doesn't have separate UV's + // in different faces so we can use it here as well, if UV's are different in separate faces the we need to add this vert + // because in jme one vertex can have only on UV coordinate + boolean vertexAlreadyUsed = false; + for (Integer vertexIndex : vertexReferenceMap.get(index[i])) { + int vertexUseCounter = 0; + for (Entry entry : uvsForFace.entrySet()) { + if (entry.getValue()[i].equals(userUVCollection.getUVForVertex(entry.getKey(), vertexIndex))) { + ++vertexUseCounter; + } + } + if (vertexUseCounter == uvsForFace.size()) { + vertexAlreadyUsed = true; + index[i] = vertexIndex; + break; + } + } + + if (!vertexAlreadyUsed) { + // treat this face as a new one because its vertices have separate UV's + this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); + for (Entry entry : uvsForFace.entrySet()) { + userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size()); + } + vertexList.add(verticesAndNormals[index[i]][0]); + if (verticesColors != null) { + vertexColorsList.add(verticesColors.get(vertexColorIndex[i])); + } + normalList.add(verticesAndNormals[index[i]][1]); + index[i] = vertexList.size() - 1; + } + } else { + // use this index again + index[i] = vertexList.indexOf(verticesAndNormals[index[i]][0]); + } + indexList.add(index[i]); + } + } else { + Vector3f n = smooth ? null : FastMath.computeNormal(verticesAndNormals[v1][0], verticesAndNormals[v2][0], verticesAndNormals[v3][0]); + for (int i = 0; i < 3; ++i) { + indexList.add(vertexList.size()); + this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); + if (uvsForFace != null) { + for (Entry entry : uvsForFace.entrySet()) { + userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size()); + } + } + vertexList.add(verticesAndNormals[index[i]][0]); + if (verticesColors != null) { + vertexColorsList.add(verticesColors.get(vertexColorIndex[i])); + } + normalList.add(smooth ? verticesAndNormals[index[i]][1] : n); + } + } + } + + /** + * The method loads the UV coordinates. The result is a map where the key is the user's UV set name and the values are UV coordinates. + * But depending on the mesh type (triangle/quads or bmesh) the lists in the map have different meaning. + * For bmesh they are enlisted just like they are stored in the blend file (in loops). + * For traditional faces every 4 UV's should be assigned for a single face. + * @param meshStructure + * the mesh structure + * @param useBMesh + * tells if we should load the coordinates from loops of from faces + * @return a map that sorts UV coordinates between different UV sets + * @throws BlenderFileException + * an exception is thrown when problems with blend file occur + */ + @SuppressWarnings("unchecked") + private Map> loadUVCoordinates(Structure meshStructure, boolean useBMesh) throws BlenderFileException { + Map> result = new HashMap>(); + if (useBMesh) { + // in this case the UV's are assigned to vertices (an array is the same length as the vertex array) + Structure loopData = (Structure) meshStructure.getFieldValue("ldata"); + Pointer pLoopDataLayers = (Pointer) loopData.getFieldValue("layers"); + List loopDataLayers = pLoopDataLayers.fetchData(); + for (Structure structure : loopDataLayers) { + Pointer p = (Pointer) structure.getFieldValue("data"); + if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_BMESH) { + String uvSetName = structure.getFieldValue("name").toString(); + List uvsStructures = p.fetchData(); + List uvs = new ArrayList(uvsStructures.size()); + for (Structure uvStructure : uvsStructures) { + DynamicArray loopUVS = (DynamicArray) uvStructure.getFieldValue("uv"); + uvs.add(new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue())); + } + result.put(uvSetName, uvs); + } + } + } else { + // in this case UV's are assigned to faces (the array has the same legnth as the faces count) + Structure facesData = (Structure) meshStructure.getFieldValue("fdata"); + Pointer pFacesDataLayers = (Pointer) facesData.getFieldValue("layers"); + List facesDataLayers = pFacesDataLayers.fetchData(); + for (Structure structure : facesDataLayers) { + Pointer p = (Pointer) structure.getFieldValue("data"); + if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_FMESH) { + String uvSetName = structure.getFieldValue("name").toString(); + List uvsStructures = p.fetchData(); + List uvs = new ArrayList(uvsStructures.size()); + for (Structure uvStructure : uvsStructures) { + DynamicArray mFaceUVs = (DynamicArray) uvStructure.getFieldValue("uv"); + uvs.add(new Vector2f(mFaceUVs.get(0).floatValue(), mFaceUVs.get(1).floatValue())); + uvs.add(new Vector2f(mFaceUVs.get(2).floatValue(), mFaceUVs.get(3).floatValue())); + uvs.add(new Vector2f(mFaceUVs.get(4).floatValue(), mFaceUVs.get(5).floatValue())); + uvs.add(new Vector2f(mFaceUVs.get(6).floatValue(), mFaceUVs.get(7).floatValue())); + } + result.put(uvSetName, uvs); + } + } + } + return result; + } + + /** + * This method returns the vertices colors. Each vertex is stored in byte[4] array. + * + * @param meshStructure + * the structure containing the mesh data + * @param blenderContext + * the blender context + * @return a list of vertices colors, each color belongs to a single vertex + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + */ + private List getVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { + MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); + Pointer pMCol = (Pointer) meshStructure.getFieldValue(meshHelper.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol"); + List verticesColors = null; + // it was likely a bug in blender untill version 2.63 (the blue and red factors were misplaced in their structure) + // so we need to put them right + boolean useBGRA = blenderContext.getBlenderVersion() < 263; + if (pMCol.isNotNull()) { + List mCol = pMCol.fetchData(); + verticesColors = new ArrayList(mCol.size()); + for (Structure color : mCol) { + byte r = ((Number) color.getFieldValue("r")).byteValue(); + byte g = ((Number) color.getFieldValue("g")).byteValue(); + byte b = ((Number) color.getFieldValue("b")).byteValue(); + byte a = ((Number) color.getFieldValue("a")).byteValue(); + verticesColors.add(useBGRA ? new byte[] { b, g, r, a } : new byte[] { r, g, b, a }); + } + } + return verticesColors; + } + + /** + * @return a map that maps vertex index from reference array to its indices in the result list + */ + public Map>> getVertexReferenceMap() { + return globalVertexReferenceMap; + } + + /** + * @param materialNumber + * the material index + * @return result vertices array + */ + private Vector3f[] getVertices(int materialNumber) { + return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]); + } + + /** + * @param materialNumber + * the material index + * @return the amount of result vertices + */ + private int getVerticesAmount(int materialNumber) { + return vertexMap.get(materialNumber).size(); + } + + /** + * @param materialNumber + * the material index + * @return normals result array + */ + private Vector3f[] getNormals(int materialNumber) { + return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]); + } + + /** + * @param materialNumber + * the material index + * @return the vertices colors buffer or null if no vertex colors is set + */ + private ByteBuffer getVertexColorsBuffer(int materialNumber) { + ByteBuffer result = null; + if (verticesColors != null && vertexColorsMap.get(materialNumber) != null) { + List data = vertexColorsMap.get(materialNumber); + result = BufferUtils.createByteBuffer(4 * data.size()); + for (byte[] v : data) { + if (v != null) { + result.put(v[0]).put(v[1]).put(v[2]).put(v[3]); + } else { + result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0); + } + } + result.flip(); + } + return result; + } + + /** + * @param materialNumber + * the material number that is appied to the mesh + * @return UV coordinates of vertices that belong to the required mesh part + */ + public LinkedHashMap> getUVCoordinates(int materialNumber) { + return userUVCollection.getUVCoordinates(materialNumber); + } + + /** + * @return indicates if the mesh has UV coordinates + */ + public boolean hasUVCoordinates() { + return userUVCollection.hasUVCoordinates(); + } + + /** + * @return true if the mesh has no vertices and false otherwise + */ + public boolean isEmpty() { + return vertexMap.size() == 0; + } + + /** + * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created + * to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key + * - the reference indices list. + * + * @param basicVertexIndex + * the index of the vertex from its basic table + * @param resultIndex + * the index of the vertex in its result vertex list + * @param vertexReferenceMap + * the reference map + */ + private void appendVertexReference(int basicVertexIndex, int resultIndex, Map> vertexReferenceMap) { + List referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex)); + if (referenceList == null) { + referenceList = new ArrayList(); + vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList); + } + referenceList.add(Integer.valueOf(resultIndex)); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java new file mode 100644 index 000000000..4191484d4 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java @@ -0,0 +1,160 @@ +package com.jme3.scene.plugins.blender.meshes.builders; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import com.jme3.math.Vector3f; +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.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.util.BufferUtils; + +/** + * A builder that creates a lines mesh. The result is made of lines that do not belong to any face. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class LineMeshBuilder { + private static final Logger LOGGER = Logger.getLogger(LineMeshBuilder.class.getName()); + + private static final int EDGE_NOT_IN_FACE_FLAG = 0x80; + + /** An array of reference vertices. */ + private Vector3f[][] verticesAndNormals; + /** The vertices of the mesh. */ + private List vertices = new ArrayList(); + /** The normals of the mesh. */ + private List normals = new ArrayList(); + + /** + * This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' + * positions (it simply tells which vertex is referenced where in the result list). + */ + private Map> globalVertexReferenceMap; + + /** + * Constructor. Stores the given array (not copying it). + * The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied. + * The amount of vertices is always faceCount * 3. + * @param verticesAndNormals + * the reference vertices and normals array + */ + public LineMeshBuilder(Vector3f[][] verticesAndNormals) { + this.verticesAndNormals = verticesAndNormals; + globalVertexReferenceMap = new HashMap>(verticesAndNormals.length); + } + + /** + * The method reads the mesh. It loads only edges that are marked as not belonging to any face in their flag field. + * @param meshStructure + * the mesh structure + * @throws BlenderFileException + * an exception thrown when reading from the blend file fails + */ + public void readMesh(Structure meshStructure) throws BlenderFileException { + LOGGER.fine("Reading line mesh."); + Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); + + if (pMEdge.isNotNull()) { + List edges = pMEdge.fetchData(); + int vertexIndex = 0;//vertex index in the result mesh + for (Structure edge : edges) { + int flag = ((Number) edge.getFieldValue("flag")).intValue(); + if ((flag & EDGE_NOT_IN_FACE_FLAG) != 0) { + int v1 = ((Number) edge.getFieldValue("v1")).intValue(); + int v2 = ((Number) edge.getFieldValue("v2")).intValue(); + + vertices.add(verticesAndNormals[v1][0]); + normals.add(verticesAndNormals[v1][1]); + this.appendVertexReference(v1, vertexIndex++, globalVertexReferenceMap); + + vertices.add(verticesAndNormals[v2][0]); + normals.add(verticesAndNormals[v2][1]); + this.appendVertexReference(v2, vertexIndex++, globalVertexReferenceMap); + } + } + } + } + + /** + * Builds the meshes. + * @return a map between material index and the mesh + */ + public Map buildMeshes() { + LOGGER.fine("Building line mesh."); + Map result = new HashMap(1); + if (vertices.size() > 0) { + Mesh mesh = new Mesh(); + mesh.setMode(Mode.Lines); + + LOGGER.fine("Creating indices buffer."); + if (vertices.size() <= Short.MAX_VALUE) { + short[] indices = new short[vertices.size()]; + for (int i = 0; i < vertices.size(); ++i) { + indices[i] = (short) i; + } + mesh.setBuffer(Type.Index, 1, indices); + } else { + int[] indices = new int[vertices.size()]; + for (int i = 0; i < vertices.size(); ++i) { + indices[i] = i; + } + mesh.setBuffer(Type.Index, 1, indices); + } + + LOGGER.fine("Creating vertices buffer."); + VertexBuffer verticesBuffer = new VertexBuffer(Type.Position); + verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(vertices.toArray(new Vector3f[vertices.size()]))); + mesh.setBuffer(verticesBuffer); + + LOGGER.fine("Creating normals buffer (in case of lines it is required if skeleton is applied)."); + VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal); + normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(normals.toArray(new Vector3f[normals.size()]))); + mesh.setBuffer(normalsBuffer); + + result.put(-1, mesh); + } + return result; + } + + /** + * @return true if the mesh has no vertices and false otherwise + */ + public boolean isEmpty() { + return vertices == null; + } + + public Map> getGlobalVertexReferenceMap() { + return globalVertexReferenceMap; + } + + /** + * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created + * to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key + * - the reference indices list. + * + * @param basicVertexIndex + * the index of the vertex from its basic table + * @param resultIndex + * the index of the vertex in its result vertex list + * @param vertexReferenceMap + * the reference map + */ + private void appendVertexReference(int basicVertexIndex, int resultIndex, Map> vertexReferenceMap) { + List referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex)); + if (referenceList == null) { + referenceList = new ArrayList(); + vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList); + } + referenceList.add(Integer.valueOf(resultIndex)); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java new file mode 100644 index 000000000..12cfe4920 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java @@ -0,0 +1,161 @@ +package com.jme3.scene.plugins.blender.meshes.builders; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialContext; + +public class MeshBuilder { + private boolean fixUpAxis; + private PointMeshBuilder pointMeshBuilder; + private LineMeshBuilder lineMeshBuilder; + private FaceMeshBuilder faceMeshBuilder; + /** + * This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' + * positions (it simply tells which vertex is referenced where in the result list). + */ + private Map>> globalVertexReferenceMap = new HashMap>>(); + + public MeshBuilder(Structure meshStructure, MaterialContext[] materials, BlenderContext blenderContext) throws BlenderFileException { + fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis(); + Vector3f[][] verticesAndNormals = this.getVerticesAndNormals(meshStructure); + + faceMeshBuilder = new FaceMeshBuilder(verticesAndNormals, this.areGeneratedTexturesPresent(materials)); + faceMeshBuilder.readMesh(meshStructure, blenderContext); + lineMeshBuilder = new LineMeshBuilder(verticesAndNormals); + lineMeshBuilder.readMesh(meshStructure); + pointMeshBuilder = new PointMeshBuilder(verticesAndNormals); + pointMeshBuilder.readMesh(meshStructure); + } + + public Map> buildMeshes() { + Map> result = new HashMap>(); + + Map meshes = faceMeshBuilder.buildMeshes(); + for (Entry entry : meshes.entrySet()) { + List meshList = new ArrayList(); + meshList.add(entry.getValue()); + result.put(entry.getKey(), meshList); + } + + meshes = lineMeshBuilder.buildMeshes(); + for (Entry entry : meshes.entrySet()) { + List meshList = result.get(entry.getKey()); + if (meshList == null) { + meshList = new ArrayList(); + result.put(entry.getKey(), meshList); + } + meshList.add(entry.getValue()); + } + + meshes = pointMeshBuilder.buildMeshes(); + for (Entry entry : meshes.entrySet()) { + List meshList = result.get(entry.getKey()); + if (meshList == null) { + meshList = new ArrayList(); + result.put(entry.getKey(), meshList); + } + meshList.add(entry.getValue()); + } + + globalVertexReferenceMap.putAll(faceMeshBuilder.getVertexReferenceMap()); + globalVertexReferenceMap.put(-1, lineMeshBuilder.getGlobalVertexReferenceMap()); + globalVertexReferenceMap.get(-1).putAll(pointMeshBuilder.getGlobalVertexReferenceMap()); + return result; + } + + /** + * @return true if the mesh has no vertices and false otherwise + */ + public boolean isEmpty() { + return faceMeshBuilder.isEmpty() && lineMeshBuilder.isEmpty() && pointMeshBuilder.isEmpty(); + } + + /** + * @return a map that maps vertex index from reference array to its indices in the result list + */ + public Map>> getVertexReferenceMap() { + return globalVertexReferenceMap; + } + + /** + * @param materialNumber + * the material number that is appied to the mesh + * @return UV coordinates of vertices that belong to the required mesh part + */ + public LinkedHashMap> getUVCoordinates(int materialNumber) { + return faceMeshBuilder.getUVCoordinates(materialNumber); + } + + /** + * @return indicates if the mesh has UV coordinates + */ + public boolean hasUVCoordinates() { + return faceMeshBuilder.hasUVCoordinates(); + } + + /** + * This method returns the vertices. + * + * @param meshStructure + * the structure containing the mesh data + * @return a list of two - element arrays, the first element is the vertex and the second - its normal + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + */ + @SuppressWarnings("unchecked") + private Vector3f[][] getVerticesAndNormals(Structure meshStructure) throws BlenderFileException { + int count = ((Number) meshStructure.getFieldValue("totvert")).intValue(); + Vector3f[][] result = new Vector3f[count][2]; + if (count == 0) { + return result; + } + + Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert"); + List mVerts = pMVert.fetchData(); + if (fixUpAxis) { + for (int i = 0; i < count; ++i) { + DynamicArray coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); + result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue()); + + DynamicArray normals = (DynamicArray) mVerts.get(i).getFieldValue("no"); + result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f, -normals.get(1).shortValue() / 32767.0f); + } + } else { + for (int i = 0; i < count; ++i) { + DynamicArray coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); + result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue()); + + DynamicArray normals = (DynamicArray) mVerts.get(i).getFieldValue("no"); + result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(1).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f); + } + } + return result; + } + + /** + * @return true if the material has at least one generated component and false otherwise + */ + private boolean areGeneratedTexturesPresent(MaterialContext[] materials) { + if (materials != null) { + for (MaterialContext material : materials) { + if (material != null && material.hasGeneratedTextures()) { + return true; + } + } + } + return false; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java new file mode 100644 index 000000000..57e99100b --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java @@ -0,0 +1,171 @@ +package com.jme3.scene.plugins.blender.meshes.builders; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import com.jme3.math.Vector3f; +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.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.util.BufferUtils; + +/** + * A builder that creates a points mesh. The result is made of points that do not belong to any edge and face. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class PointMeshBuilder { + private static final Logger LOGGER = Logger.getLogger(PointMeshBuilder.class.getName()); + + /** An array of reference vertices. */ + private Vector3f[][] verticesAndNormals; + /** The vertices of the mesh. */ + private List vertices = new ArrayList(); + /** The normals of the mesh. */ + private List normals = new ArrayList(); + + /** + * This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' + * positions (it simply tells which vertex is referenced where in the result list). + */ + private Map> globalVertexReferenceMap; + + /** + * Constructor. Stores the given array (not copying it). + * The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied. + * The amount of vertices is always faceCount * 3. + * @param verticesAndNormals + * the reference vertices and normals array + */ + public PointMeshBuilder(Vector3f[][] verticesAndNormals) { + this.verticesAndNormals = verticesAndNormals; + globalVertexReferenceMap = new HashMap>(verticesAndNormals.length); + } + + /** + * The method reads the mesh. Since blender does not store the information in the vertex itself whether it belongs + * anywhere or not, we need to check all vertices and use here only those that are not used. + * @param meshStructure + * the mesh structure + * @throws BlenderFileException + * an exception thrown when reading from the blend file fails + */ + public void readMesh(Structure meshStructure) throws BlenderFileException { + LOGGER.fine("Reading points mesh."); + Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); + + if (pMEdge.isNotNull()) { + int count = ((Number) meshStructure.getFieldValue("totvert")).intValue(); + Set usedVertices = new HashSet(count); + List edges = pMEdge.fetchData(); + + for (Structure edge : edges) { + int v1 = ((Number) edge.getFieldValue("v1")).intValue(); + int v2 = ((Number) edge.getFieldValue("v2")).intValue(); + usedVertices.add(verticesAndNormals[v1][0]); + usedVertices.add(verticesAndNormals[v2][0]); + } + + if (usedVertices.size() < count) { + vertices = new ArrayList(count - usedVertices.size()); + int vertexIndex = 0, blenderVertexIndex = 0; + for (Vector3f[] vertAndNormal : verticesAndNormals) { + if (!usedVertices.contains(vertAndNormal[0])) { + vertices.add(vertAndNormal[0]); + normals.add(vertAndNormal[1]); + this.appendVertexReference(blenderVertexIndex, vertexIndex++, globalVertexReferenceMap); + } + ++blenderVertexIndex; + } + } + } + } + + /** + * Builds the meshes. + * @return a map between material index and the mesh + */ + public Map buildMeshes() { + LOGGER.fine("Building point mesh."); + Map result = new HashMap(1); + + if (vertices.size() > 0) { + Mesh mesh = new Mesh(); + mesh.setMode(Mode.Points); + mesh.setPointSize(3); + + // the point mesh does not need index buffer, but some modifiers applied by importer need it + // the 'alone point' situation should be quite rare so not too many resources are wasted here + LOGGER.fine("Creating indices buffer."); + if (vertices.size() <= Short.MAX_VALUE) { + short[] indices = new short[vertices.size()]; + for (int i = 0; i < vertices.size(); ++i) { + indices[i] = (short) i; + } + mesh.setBuffer(Type.Index, 1, indices); + } else { + int[] indices = new int[vertices.size()]; + for (int i = 0; i < vertices.size(); ++i) { + indices[i] = i; + } + mesh.setBuffer(Type.Index, 1, indices); + } + + LOGGER.fine("Creating vertices buffer."); + VertexBuffer verticesBuffer = new VertexBuffer(Type.Position); + verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(vertices.toArray(new Vector3f[vertices.size()]))); + mesh.setBuffer(verticesBuffer); + + LOGGER.fine("Creating normals buffer (in case of points it is required if skeleton is applied)."); + VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal); + normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(normals.toArray(new Vector3f[normals.size()]))); + mesh.setBuffer(normalsBuffer); + + result.put(-1, mesh); + } + return result; + } + + /** + * @return true if the mesh has no vertices and false otherwise + */ + public boolean isEmpty() { + return vertices == null; + } + + public Map> getGlobalVertexReferenceMap() { + return globalVertexReferenceMap; + } + + /** + * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created + * to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key + * - the reference indices list. + * + * @param basicVertexIndex + * the index of the vertex from its basic table + * @param resultIndex + * the index of the vertex in its result vertex list + * @param vertexReferenceMap + * the reference map + */ + private void appendVertexReference(int basicVertexIndex, int resultIndex, Map> vertexReferenceMap) { + List referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex)); + if (referenceList == null) { + referenceList = new ArrayList(); + vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList); + } + referenceList.add(Integer.valueOf(resultIndex)); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java new file mode 100644 index 000000000..258a2c8aa --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java @@ -0,0 +1,464 @@ +package com.jme3.scene.plugins.blender.modifiers; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.Animation; +import com.jme3.animation.Bone; +import com.jme3.animation.BoneTrack; +import com.jme3.animation.Skeleton; +import com.jme3.animation.SkeletonControl; +import com.jme3.math.Matrix4f; +import com.jme3.math.Transform; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.animations.AnimationData; +import com.jme3.scene.plugins.blender.animations.ArmatureHelper; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.meshes.MeshContext; +import com.jme3.scene.plugins.blender.objects.ObjectHelper; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; + +/** + * This modifier allows to add bone animation to the object. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ArmatureModifier extends Modifier { + private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName()); + private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // JME + + private Structure armatureObject; + private Skeleton skeleton; + private Structure objectStructure; + private Structure meshStructure; + + /** Loaded animation data. */ + private AnimationData animationData; + /** Old memory address of the mesh that will have the skeleton applied. */ + private Long meshOMA; + + /** + * This constructor reads animation data from the object structore. The + * stored data is the AnimData and additional data is armature's OMA. + * + * @param objectStructure + * the structure of the object + * @param modifierStructure + * the structure of the modifier + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { + Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData().get(0); + if (this.validate(modifierStructure, blenderContext)) { + Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object"); + if (pArmatureObject.isNotNull()) { + ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); + + armatureObject = pArmatureObject.fetchData().get(0); + + // load skeleton + Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0); + List bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(); + List bonesList = new ArrayList(); + for (int i = 0; i < bonebase.size(); ++i) { + armatureHelper.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectStructure.getOldMemoryAddress(), blenderContext); + } + bonesList.add(0, new Bone("")); + Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]); + skeleton = new Skeleton(bones); + blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton); + this.objectStructure = objectStructure; + this.meshStructure = meshStructure; + + // read mesh indexes + meshOMA = meshStructure.getOldMemoryAddress(); + + // read animations + ArrayList animations = new ArrayList(); + List actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); + if (actionHeaders != null) {// it may happen that the model has + // armature with no actions + for (FileBlockHeader header : actionHeaders) { + Structure actionStructure = header.getStructure(blenderContext); + String actionName = actionStructure.getName(); + + BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext); + if (tracks != null && tracks.length > 0) { + // determining the animation time + float maximumTrackLength = 0; + for (BoneTrack track : tracks) { + float length = track.getLength(); + if (length > maximumTrackLength) { + maximumTrackLength = length; + } + } + + Animation boneAnimation = new Animation(actionName, maximumTrackLength); + boneAnimation.setTracks(tracks); + animations.add(boneAnimation); + } + } + } + // fetching action defined in object + Pointer pAction = (Pointer) objectStructure.getFieldValue("action"); + if (pAction.isNotNull()) { + Structure actionStructure = pAction.fetchData().get(0); + String actionName = actionStructure.getName(); + + BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext); + if (tracks != null && tracks.length > 0) { + // determining the animation time + float maximumTrackLength = 0; + for (BoneTrack track : tracks) { + float length = track.getLength(); + if (length > maximumTrackLength) { + maximumTrackLength = length; + } + } + + Animation boneAnimation = new Animation(actionName, maximumTrackLength); + boneAnimation.setTracks(tracks); + animations.add(boneAnimation); + } + } + + animationData = new AnimationData(skeleton, animations); + + // store the animation data for each bone + for (Bone bone : bones) { + if (bone.getName().length() > 0) { + BoneContext boneContext = blenderContext.getBoneContext(bone); + Long boneOma = boneContext.getBoneOma(); + if (boneOma != null) { + blenderContext.setAnimData(boneOma, animationData); + } + } + } + } else { + modifying = false; + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void apply(Node node, BlenderContext blenderContext) { + if (invalid) { + LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName()); + }// if invalid, animData will be null + if (animationData != null && skeleton != null) { + // setting weights for bones + List geomList = (List) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE); + MeshContext meshContext = blenderContext.getMeshContext(meshOMA); + int[] bonesGroups = new int[] { 0 }; + for (Geometry geom : geomList) { + int materialIndex = meshContext.getMaterialIndex(geom); + Mesh mesh = geom.getMesh(); + + try { + VertexBuffer[] buffers = this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, materialIndex, bonesGroups, blenderContext); + if (buffers != null) { + mesh.setMaxNumWeights(bonesGroups[0]); + mesh.setBuffer(buffers[0]); + mesh.setBuffer(buffers[1]); + + LOGGER.fine("Generating bind pose and normal buffers."); + mesh.generateBindPose(true); + + // change the usage type of vertex and normal buffers from + // Static to Stream + mesh.getBuffer(Type.Position).setUsage(Usage.Stream); + mesh.getBuffer(Type.Normal).setUsage(Usage.Stream); + + // creating empty buffers for HW skinning + // the buffers will be setup if ever used. + VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight); + VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex); + mesh.setBuffer(verticesWeightsHW); + mesh.setBuffer(verticesWeightsIndicesHW); + } + } catch (BlenderFileException e) { + LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); + invalid = true; + } + } + + if (!invalid) { + // applying animations + AnimControl control = new AnimControl(animationData.skeleton); + List animList = animationData.anims; + if (animList != null && animList.size() > 0) { + HashMap anims = new HashMap(animList.size()); + for (int i = 0; i < animList.size(); ++i) { + Animation animation = animList.get(i); + anims.put(animation.getName(), animation); + } + control.setAnimations(anims); + } + node.addControl(control); + node.addControl(new SkeletonControl(animationData.skeleton)); + blenderContext.setNodeForSkeleton(skeleton, node); + + TempVars tempVars = TempVars.get(); + try { + Pointer pPose = (Pointer) armatureObject.getFieldValue("pose"); + if (pPose.isNotNull()) { + LOGGER.fine("Loading the pose of the armature."); + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + + Structure pose = pPose.fetchData().get(0); + Structure chanbase = (Structure) pose.getFieldValue("chanbase"); + List chans = chanbase.evaluateListBase(); + Transform transform = new Transform(); + for (Structure poseChannel : chans) { + Pointer pBone = (Pointer) poseChannel.getFieldValue("bone"); + if (pBone.isNull()) { + throw new BlenderFileException("Cannot find bone for pose channel named: " + poseChannel.getName()); + } + BoneContext boneContext = blenderContext.getBoneContext(pBone.getOldMemoryAddress()); + + LOGGER.log(Level.FINEST, "Getting the global pose transformation for bone: {0}", boneContext); + Matrix4f poseMat = objectHelper.getMatrix(poseChannel, "pose_mat", blenderContext.getBlenderKey().isFixUpAxis()); + poseMat.multLocal(BoneContext.BONE_ARMATURE_TRANSFORMATION_MATRIX); + + Matrix4f armatureWorldMat = objectHelper.getMatrix(armatureObject, "obmat", blenderContext.getBlenderKey().isFixUpAxis()); + Matrix4f boneWorldMat = armatureWorldMat.multLocal(poseMat); + + boneWorldMat.toTranslationVector(tempVars.vect1); + boneWorldMat.toRotationQuat(tempVars.quat1); + boneWorldMat.toScaleVector(tempVars.vect2); + transform.setTranslation(tempVars.vect1); + transform.setRotation(tempVars.quat1); + transform.setScale(tempVars.vect2); + + constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, transform); + } + } + } catch (BlenderFileException e) { + LOGGER.log(Level.WARNING, "Problems occured during pose loading: {0}.", e.getLocalizedMessage()); + } finally { + tempVars.release(); + } + + node.updateModelBound(); + } + } + } + + /** + * This method reads mesh indexes + * + * @param objectStructure + * structure of the object that has the armature modifier applied + * @param meshStructure + * the structure of the object's mesh + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blend file structure is + * somehow invalid or corrupted + */ + private VertexBuffer[] readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, int materialIndex, int[] bonesGroups, BlenderContext blenderContext) throws BlenderFileException { + ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); + Structure defBase = (Structure) objectStructure.getFieldValue("defbase"); + Map groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton); + + MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress()); + + return this.getBoneWeightAndIndexBuffer(meshStructure, meshContext.getVertexCount(materialIndex), bonesGroups, meshContext.getVertexReferenceMap(materialIndex), groupToBoneIndexMap); + } + + /** + * This method returns an array of size 2. The first element is a vertex + * buffer holding bone weights for every vertex in the model. The second + * element is a vertex buffer holding bone indices for vertices (the indices + * of bones the vertices are assigned to). + * + * @param meshStructure + * the mesh structure object + * @param vertexListSize + * a number of vertices in the model + * @param bonesGroups + * this is an output parameter, it should be a one-sized array; + * the maximum amount of weights per vertex (up to + * MAXIMUM_WEIGHTS_PER_VERTEX) is stored there + * @param vertexReferenceMap + * this reference map allows to map the original vertices read + * from blender to vertices that are really in the model; one + * vertex may appear several times in the result model + * @param groupToBoneIndexMap + * this object maps the group index (to which a vertices in + * blender belong) to bone index of the model + * @return arrays of vertices weights and their bone indices and (as an + * output parameter) the maximum amount of weights for a vertex + * @throws BlenderFileException + * this exception is thrown when the blend file structure is + * somehow invalid or corrupted + */ + private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map> vertexReferenceMap, Map groupToBoneIndexMap) throws BlenderFileException { + bonesGroups[0] = 0; + Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert + // = + // DeformVERTices + FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); + ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); + + if (pDvert.isNotNull()) {// assigning weights and bone indices + boolean warnAboutTooManyVertexWeights = false; + // dverts.size() = verticesAmount (one dvert per vertex in blender) + List dverts = pDvert.fetchData(); + int vertexIndex = 0; + // use tree map to sort weights from the lowest to the highest ones + TreeMap weightToIndexMap = new TreeMap(); + + for (Structure dvert : dverts) { + // we fetch the referenced vertices here + List vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex)); + if (vertexIndices != null) { + // total amount of wights assigned to the vertex (max. 4 in JME) + int totweight = ((Number) dvert.getFieldValue("totweight")).intValue(); + Pointer pDW = (Pointer) dvert.getFieldValue("dw"); + if (totweight > 0 && groupToBoneIndexMap != null) { + weightToIndexMap.clear(); + int weightIndex = 0; + List dw = pDW.fetchData(); + for (Structure deformWeight : dw) { + Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue()); + float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); + // boneIndex == null: it here means that we came + // accross group that has no bone attached to, so + // simply ignore it + // if weight == 0 and weightIndex == 0 then ignore + // the weight (do not set weight = 0 as a first + // weight) + if (boneIndex != null && (weight > 0.0f || weightIndex > 0)) { + if (weightIndex < MAXIMUM_WEIGHTS_PER_VERTEX) { + if (weight == 0.0f) { + boneIndex = Integer.valueOf(0); + } + // we apply the weight to all referenced + // vertices + for (Integer index : vertexIndices) { + weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight); + indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue()); + } + weightToIndexMap.put(weight, weightIndex); + bonesGroups[0] = Math.max(bonesGroups[0], weightIndex + 1); + } else if (weight > 0) {// if weight is zero the + // simply ignore it + warnAboutTooManyVertexWeights = true; + Entry lowestWeightAndIndex = weightToIndexMap.firstEntry(); + if (lowestWeightAndIndex != null && lowestWeightAndIndex.getKey() < weight) { + // we apply the weight to all referenced + // vertices + for (Integer index : vertexIndices) { + weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), weight); + indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), boneIndex.byteValue()); + } + weightToIndexMap.remove(lowestWeightAndIndex.getKey()); + weightToIndexMap.put(weight, lowestWeightAndIndex.getValue()); + } + } + ++weightIndex; + } + } + } else { + // 0.0 weight indicates, do not transform this vertex, + // but keep it in bind pose. + for (Integer index : vertexIndices) { + weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 0.0f); + indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); + } + } + } + ++vertexIndex; + } + + if (warnAboutTooManyVertexWeights) { + LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName()); + } + } else { + // always bind all vertices to 0-indexed bone + // this bone makes the model look normally if vertices have no bone + // assigned and it is used in object animation, so if we come + // accross object + // animation we can use the 0-indexed bone for this + for (List vertexIndexList : vertexReferenceMap.values()) { + // we apply the weight to all referenced vertices + for (Integer index : vertexIndexList) { + weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f); + indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); + } + } + } + + bonesGroups[0] = Math.max(bonesGroups[0], 1); + + this.endBoneAssigns(vertexListSize, weightsFloatData); + VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight); + verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData); + + VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex); + verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData); + return new VertexBuffer[] { verticesWeights, verticesWeightsIndices }; + } + + /** + * Normalizes weights if needed and finds largest amount of weights used for + * all vertices in the buffer. + * + * @param vertCount + * amount of vertices + * @param weightsFloatData + * weights for vertices + */ + private void endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) { + weightsFloatData.rewind(); + float[] weights = new float[MAXIMUM_WEIGHTS_PER_VERTEX]; + for (int v = 0; v < vertCount; ++v) { + float sum = 0; + for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) { + weights[i] = weightsFloatData.get(); + sum += weights[i]; + } + if (sum != 1f && sum != 0.0f) { + weightsFloatData.position(weightsFloatData.position() - MAXIMUM_WEIGHTS_PER_VERTEX); + // compute new vals based on sum + float sumToB = 1f / sum; + for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) { + weightsFloatData.put(weights[i] * sumToB); + } + } + } + weightsFloatData.rewind(); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java new file mode 100644 index 000000000..1c6b52b5f --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java @@ -0,0 +1,247 @@ +package com.jme3.scene.plugins.blender.modifiers; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingSphere; +import com.jme3.bounding.BoundingVolume; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.objects.ObjectHelper; +import com.jme3.scene.shape.Curve; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This modifier allows to array modifier to the object. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ArrayModifier extends Modifier { + private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName()); + + /** Parameters of the modifier. */ + private Map modifierData = new HashMap(); + + /** + * This constructor reads array data from the modifier structure. The + * stored data is a map of parameters for array modifier. No additional data + * is loaded. + * + * @param objectStructure + * the structure of the object + * @param modifierStructure + * the structure of the modifier + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + @SuppressWarnings("unchecked") + public ArrayModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { + if (this.validate(modifierStructure, blenderContext)) { + Number fittype = (Number) modifierStructure.getFieldValue("fit_type"); + modifierData.put("fittype", fittype); + switch (fittype.intValue()) { + case 0:// FIXED COUNT + modifierData.put("count", modifierStructure.getFieldValue("count")); + break; + case 1:// FIXED LENGTH + modifierData.put("length", modifierStructure.getFieldValue("length")); + break; + case 2:// FITCURVE + Pointer pCurveOb = (Pointer) modifierStructure.getFieldValue("curve_ob"); + float length = 0; + if (pCurveOb.isNotNull()) { + Structure curveStructure = pCurveOb.fetchData().get(0); + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + Node curveObject = (Node) objectHelper.toObject(curveStructure, blenderContext); + Set referencesToCurveLengths = new HashSet(curveObject.getChildren().size()); + for (Spatial spatial : curveObject.getChildren()) { + if (spatial instanceof Geometry) { + Mesh mesh = ((Geometry) spatial).getMesh(); + if (mesh instanceof Curve) { + length += ((Curve) mesh).getLength(); + } else { + // if bevel object has several parts then each mesh will have the same reference + // to length value (and we should use only one) + Number curveLength = spatial.getUserData("curveLength"); + if (curveLength != null && !referencesToCurveLengths.contains(curveLength)) { + length += curveLength.floatValue(); + referencesToCurveLengths.add(curveLength); + } + } + } + } + } + modifierData.put("length", Float.valueOf(length)); + modifierData.put("fittype", Integer.valueOf(1));// treat it like FIXED LENGTH + break; + default: + assert false : "Unknown array modifier fit type: " + fittype; + } + + // offset parameters + int offsettype = ((Number) modifierStructure.getFieldValue("offset_type")).intValue(); + if ((offsettype & 0x01) != 0) {// Constant offset + DynamicArray offsetArray = (DynamicArray) modifierStructure.getFieldValue("offset"); + float[] offset = new float[] { offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue() }; + modifierData.put("offset", offset); + } + if ((offsettype & 0x02) != 0) {// Relative offset + DynamicArray scaleArray = (DynamicArray) modifierStructure.getFieldValue("scale"); + float[] scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() }; + modifierData.put("scale", scale); + } + if ((offsettype & 0x04) != 0) {// Object offset + Pointer pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob"); + if (pOffsetObject.isNotNull()) { + modifierData.put("offsetob", pOffsetObject); + } + } + + // start cap and end cap + Pointer pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap"); + if (pStartCap.isNotNull()) { + modifierData.put("startcap", pStartCap); + } + Pointer pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap"); + if (pEndCap.isNotNull()) { + modifierData.put("endcap", pEndCap); + } + } + } + + @Override + public void apply(Node node, BlenderContext blenderContext) { + if (invalid) { + LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName()); + } else { + int fittype = ((Number) modifierData.get("fittype")).intValue(); + float[] offset = (float[]) modifierData.get("offset"); + if (offset == null) {// the node will be repeated several times in the same place + offset = new float[] { 0.0f, 0.0f, 0.0f }; + } + float[] scale = (float[]) modifierData.get("scale"); + if (scale == null) {// the node will be repeated several times in the same place + scale = new float[] { 0.0f, 0.0f, 0.0f }; + } else { + // getting bounding box + node.updateModelBound(); + BoundingVolume boundingVolume = node.getWorldBound(); + if (boundingVolume instanceof BoundingBox) { + scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f; + scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f; + scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f; + } else if (boundingVolume instanceof BoundingSphere) { + float radius = ((BoundingSphere) boundingVolume).getRadius(); + scale[0] *= radius * 2.0f; + scale[1] *= radius * 2.0f; + scale[2] *= radius * 2.0f; + } else { + throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName()); + } + } + + // adding object's offset + float[] objectOffset = new float[] { 0.0f, 0.0f, 0.0f }; + Pointer pOffsetObject = (Pointer) modifierData.get("offsetob"); + if (pOffsetObject != null) { + FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress()); + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + try {// we take the structure in case the object was not yet loaded + Structure offsetStructure = offsetObjectBlock.getStructure(blenderContext); + Vector3f translation = objectHelper.getTransformation(offsetStructure, blenderContext).getTranslation(); + objectOffset[0] = translation.x; + objectOffset[1] = translation.y; + objectOffset[2] = translation.z; + } catch (BlenderFileException e) { + LOGGER.log(Level.WARNING, "Problems in blender file structure! Object offset cannot be applied! The problem: {0}", e.getMessage()); + } + } + + // getting start and end caps + Node[] caps = new Node[] { null, null }; + Pointer[] pCaps = new Pointer[] { (Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap") }; + for (int i = 0; i < pCaps.length; ++i) { + if (pCaps[i] != null) { + caps[i] = (Node) blenderContext.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (caps[i] != null) { + caps[i] = (Node) caps[i].clone(); + } else { + FileBlockHeader capBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress()); + try {// we take the structure in case the object was not yet loaded + Structure capStructure = capBlock.getStructure(blenderContext); + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + caps[i] = (Node) objectHelper.toObject(capStructure, blenderContext); + if (caps[i] == null) { + LOGGER.log(Level.WARNING, "Cap object ''{0}'' couldn''t be loaded!", capStructure.getName()); + } + } catch (BlenderFileException e) { + LOGGER.log(Level.WARNING, "Problems in blender file structure! Cap object cannot be applied! The problem: {0}", e.getMessage()); + } + } + } + } + + Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], offset[1] + scale[1] + objectOffset[1], offset[2] + scale[2] + objectOffset[2]); + if(blenderContext.getBlenderKey().isFixUpAxis()) { + float y = translationVector.y; + translationVector.y = translationVector.z; + translationVector.z = y == 0 ? 0 : -y; + } + + // getting/calculating repeats amount + int count = 0; + if (fittype == 0) {// Fixed count + count = ((Number) modifierData.get("count")).intValue() - 1; + } else if (fittype == 1) {// Fixed length + float length = ((Number) modifierData.get("length")).floatValue(); + if (translationVector.length() > 0.0f) { + count = (int) (length / translationVector.length()) - 1; + } + } else if (fittype == 2) {// Fit curve + throw new IllegalStateException("Fit curve should be transformed to Fixed Length array type!"); + } else { + throw new IllegalStateException("Unknown fit type: " + fittype); + } + + // adding translated nodes and caps + if (count > 0) { + Node[] arrayNodes = new Node[count]; + Vector3f newTranslation = new Vector3f(); + for (int i = 0; i < count; ++i) { + newTranslation.addLocal(translationVector); + Node nodeClone = (Node) node.clone(); + nodeClone.setLocalTranslation(newTranslation); + arrayNodes[i] = nodeClone; + } + for (Node nodeClone : arrayNodes) { + node.attachChild(nodeClone); + } + if (caps[0] != null) { + caps[0].getLocalTranslation().set(node.getLocalTranslation()).subtractLocal(translationVector); + node.attachChild(caps[0]); + } + if (caps[1] != null) { + caps[1].getLocalTranslation().set(newTranslation).addLocal(translationVector); + node.attachChild(caps[1]); + } + } + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java new file mode 100644 index 000000000..aba576020 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java @@ -0,0 +1,308 @@ +package com.jme3.scene.plugins.blender.modifiers; + +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.objects.ObjectHelper; + +/** + * This modifier allows to array modifier to the object. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class MirrorModifier extends Modifier { + private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); + + private static final int FLAG_MIRROR_X = 0x08; + private static final int FLAG_MIRROR_Y = 0x10; + private static final int FLAG_MIRROR_Z = 0x20; + private static final int FLAG_MIRROR_U = 0x02; + private static final int FLAG_MIRROR_V = 0x04; + // private static final int FLAG_MIRROR_VERTEX_GROUP = 0x40; + private static final int FLAG_MIRROR_MERGE = 0x80; + + private boolean[] isMirrored; + private boolean mirrorU, mirrorV; + private boolean merge; + private float tolerance; + private Pointer pMirrorObject; + + /** + * This constructor reads mirror data from the modifier structure. The + * stored data is a map of parameters for mirror modifier. No additional data + * is loaded. + * When the modifier is applied it is necessary to get the newly created node. + * + * @param objectStructure + * the structure of the object + * @param modifierStructure + * the structure of the modifier + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public MirrorModifier(Structure modifierStructure, BlenderContext blenderContext) { + if (this.validate(modifierStructure, blenderContext)) { + int flag = ((Number) modifierStructure.getFieldValue("flag")).intValue(); + + isMirrored = new boolean[] { (flag & FLAG_MIRROR_X) != 0, (flag & FLAG_MIRROR_Y) != 0, (flag & FLAG_MIRROR_Z) != 0 }; + if (blenderContext.getBlenderKey().isFixUpAxis()) { + boolean temp = isMirrored[1]; + isMirrored[1] = isMirrored[2]; + isMirrored[2] = temp; + } + mirrorU = (flag & FLAG_MIRROR_U) != 0; + mirrorV = (flag & FLAG_MIRROR_V) != 0; + // boolean mirrorVGroup = (flag & FLAG_MIRROR_VERTEX_GROUP) != 0; + merge = (flag & FLAG_MIRROR_MERGE) == 0;// in this case we use == instead of != (this is not a mistake) + + tolerance = ((Number) modifierStructure.getFieldValue("tolerance")).floatValue(); + pMirrorObject = (Pointer) modifierStructure.getFieldValue("mirror_ob"); + } + } + + @Override + public void apply(Node node, BlenderContext blenderContext) { + if (invalid) { + LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName()); + } else { + Vector3f mirrorPlaneCenter = new Vector3f(); + if (pMirrorObject.isNotNull()) { + Structure objectStructure; + try { + objectStructure = pMirrorObject.fetchData().get(0); + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + Node object = (Node) objectHelper.toObject(objectStructure, blenderContext); + if (object != null) { + // compute the mirror object coordinates in node's local space + mirrorPlaneCenter = this.getWorldMatrix(node).invertLocal().mult(object.getWorldTranslation()); + } + } catch (BlenderFileException e) { + LOGGER.log(Level.SEVERE, "Cannot load mirror''s reference object. Cause: {0}", e.getLocalizedMessage()); + LOGGER.log(Level.SEVERE, "Mirror modifier will not be applied to node named: {0}", node.getName()); + return; + } + } + + LOGGER.finest("Allocating temporal variables."); + float d; + Vector3f mirrorPlaneNormal = new Vector3f(); + Vector3f shiftVector = new Vector3f(); + Vector3f point = new Vector3f(); + Vector3f normal = new Vector3f(); + Set modifiedIndexes = new HashSet(); + List geometriesToAdd = new ArrayList(); + final char[] mirrorNames = new char[] { 'X', 'Y', 'Z' }; + + LOGGER.fine("Mirroring mesh."); + for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) { + if (isMirrored[mirrorIndex]) { + boolean mirrorAtPoint0 = mirrorPlaneCenter.get(mirrorIndex) == 0; + if (!mirrorAtPoint0) {// compute mirror's plane normal vector in node's space + mirrorPlaneNormal.set(0, 0, 0).set(mirrorIndex, Math.signum(mirrorPlaneCenter.get(mirrorIndex))); + } + + for (Spatial spatial : node.getChildren()) { + if (spatial instanceof Geometry) { + Mesh mesh = ((Geometry) spatial).getMesh(); + Mesh clone = mesh.deepClone(); + + LOGGER.log(Level.FINEST, "Fetching buffers of cloned spatial: {0}", spatial.getName()); + FloatBuffer position = mesh.getFloatBuffer(Type.Position); + FloatBuffer bindPosePosition = mesh.getFloatBuffer(Type.BindPosePosition); + + FloatBuffer clonePosition = clone.getFloatBuffer(Type.Position); + FloatBuffer cloneBindPosePosition = clone.getFloatBuffer(Type.BindPosePosition); + FloatBuffer cloneNormals = clone.getFloatBuffer(Type.Normal); + FloatBuffer cloneBindPoseNormals = clone.getFloatBuffer(Type.BindPoseNormal); + Buffer cloneIndexes = clone.getBuffer(Type.Index).getData(); + + for (int i = 0; i < cloneIndexes.limit(); ++i) { + int index = cloneIndexes instanceof ShortBuffer ? ((ShortBuffer) cloneIndexes).get(i) : ((IntBuffer) cloneIndexes).get(i); + if (!modifiedIndexes.contains(index)) { + modifiedIndexes.add(index); + + this.get(clonePosition, index, point); + if (mirrorAtPoint0) { + d = Math.abs(point.get(mirrorIndex)); + shiftVector.set(0, 0, 0).set(mirrorIndex, -point.get(mirrorIndex)); + } else { + d = this.computeDistanceFromPlane(point, mirrorPlaneCenter, mirrorPlaneNormal); + mirrorPlaneNormal.mult(d, shiftVector); + } + + if (merge && d <= tolerance) { + point.addLocal(shiftVector); + + this.set(index, point, clonePosition, cloneBindPosePosition, position, bindPosePosition); + if (cloneNormals != null) { + this.get(cloneNormals, index, normal); + normal.set(mirrorIndex, 0); + this.set(index, normal, cloneNormals, cloneBindPoseNormals); + } + } else { + point.addLocal(shiftVector.multLocal(2)); + + this.set(index, point, clonePosition, cloneBindPosePosition); + if (cloneNormals != null) { + this.get(cloneNormals, index, normal); + normal.set(mirrorIndex, -normal.get(mirrorIndex)); + this.set(index, normal, cloneNormals, cloneBindPoseNormals); + } + } + } + } + modifiedIndexes.clear(); + + LOGGER.finer("Flipping index order."); + switch (mesh.getMode()) { + case Points: + cloneIndexes.flip(); + break; + case Lines: + for (int i = 0; i < cloneIndexes.limit(); i += 2) { + if (cloneIndexes instanceof ShortBuffer) { + short index = ((ShortBuffer) cloneIndexes).get(i + 1); + ((ShortBuffer) cloneIndexes).put(i + 1, ((ShortBuffer) cloneIndexes).get(i)); + ((ShortBuffer) cloneIndexes).put(i, index); + } else { + int index = ((IntBuffer) cloneIndexes).get(i + 1); + ((IntBuffer) cloneIndexes).put(i + 1, ((IntBuffer) cloneIndexes).get(i)); + ((IntBuffer) cloneIndexes).put(i, index); + } + } + break; + case Triangles: + for (int i = 0; i < cloneIndexes.limit(); i += 3) { + if (cloneIndexes instanceof ShortBuffer) { + short index = ((ShortBuffer) cloneIndexes).get(i + 2); + ((ShortBuffer) cloneIndexes).put(i + 2, ((ShortBuffer) cloneIndexes).get(i + 1)); + ((ShortBuffer) cloneIndexes).put(i + 1, index); + } else { + int index = ((IntBuffer) cloneIndexes).get(i + 2); + ((IntBuffer) cloneIndexes).put(i + 2, ((IntBuffer) cloneIndexes).get(i + 1)); + ((IntBuffer) cloneIndexes).put(i + 1, index); + } + } + break; + default: + throw new IllegalStateException("Invalid mesh mode: " + mesh.getMode()); + } + + if (mirrorU && clone.getBuffer(Type.TexCoord) != null) { + LOGGER.finer("Mirroring U coordinates."); + FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData(); + for (int i = 0; i < cloneUVs.limit(); i += 2) { + cloneUVs.put(i, 1.0f - cloneUVs.get(i)); + } + } + if (mirrorV && clone.getBuffer(Type.TexCoord) != null) { + LOGGER.finer("Mirroring V coordinates."); + FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData(); + for (int i = 1; i < cloneUVs.limit(); i += 2) { + cloneUVs.put(i, 1.0f - cloneUVs.get(i)); + } + } + + Geometry geometry = new Geometry(spatial.getName() + " - mirror " + mirrorNames[mirrorIndex], clone); + geometry.setMaterial(((Geometry) spatial).getMaterial()); + geometriesToAdd.add(geometry); + } + } + + LOGGER.log(Level.FINE, "Adding {0} geometries to current node.", geometriesToAdd.size()); + for (Geometry geometry : geometriesToAdd) { + node.attachChild(geometry); + } + geometriesToAdd.clear(); + } + } + } + } + + /** + * Fetches the world matrix transformation of the given node. + * @param node + * the node + * @return the node's world transformation matrix + */ + private Matrix4f getWorldMatrix(Node node) { + Matrix4f result = new Matrix4f(); + result.setTranslation(node.getWorldTranslation()); + result.setRotationQuaternion(node.getWorldRotation()); + result.setScale(node.getWorldScale()); + return result; + } + + /** + * The method computes the distance between a point and a plane (described by point in space and normal vector). + * @param p + * the point in the space + * @param c + * mirror's plane center + * @param n + * mirror's plane normal (should be normalized) + * @return the minimum distance from point to plane + */ + private float computeDistanceFromPlane(Vector3f p, Vector3f c, Vector3f n) { + return Math.abs(n.dot(p) - c.dot(n)); + } + + /** + * Sets the given value (v) into every of the buffers at the given index. + * The index is cosidered to be an index of a vertex of the mesh. + * @param index + * the index of vertex of the mesh + * @param value + * the value to be set + * @param buffers + * the buffers where the value will be set + */ + private void set(int index, Vector3f value, FloatBuffer... buffers) { + index *= 3; + for (FloatBuffer buffer : buffers) { + if (buffer != null) { + buffer.put(index, value.x); + buffer.put(index + 1, value.y); + buffer.put(index + 2, value.z); + } + } + } + + /** + * Fetches the vector's value from the given buffer at specified index. + * @param buffer + * the buffer we get the data from + * @param index + * the index of vertex of the mesh + * @param store + * the vector where the result will be set + */ + private void get(FloatBuffer buffer, int index, Vector3f store) { + index *= 3; + store.x = buffer.get(index); + store.y = buffer.get(index + 1); + store.z = buffer.get(index + 2); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java new file mode 100644 index 000000000..e1755999e --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java @@ -0,0 +1,70 @@ +package com.jme3.scene.plugins.blender.modifiers; + +import com.jme3.scene.Node; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; + +/** + * This class represents an object's modifier. The modifier object can be varied + * and the user needs to know what is the type of it for the specified type + * name. For example "ArmatureModifierData" type specified in blender is + * represented by AnimData object from jMonkeyEngine. + * + * @author Marcin Roguski (Kaelthas) + */ +public abstract class Modifier { + public static final String ARRAY_MODIFIER_DATA = "ArrayModifierData"; + public static final String ARMATURE_MODIFIER_DATA = "ArmatureModifierData"; + public static final String PARTICLE_MODIFIER_DATA = "ParticleSystemModifierData"; + public static final String MIRROR_MODIFIER_DATA = "MirrorModifierData"; + public static final String SUBSURF_MODIFIER_DATA = "SubsurfModifierData"; + public static final String OBJECT_ANIMATION_MODIFIER_DATA = "ObjectAnimationModifierData"; + + /** This variable indicates if the modifier is invalid (true) or not (false). */ + protected boolean invalid; + /** + * A variable that tells if the modifier causes modification. Some modifiers like ArmatureModifier might have no + * Armature object attached and thus not really modifying the feature. In such cases it is good to know if it is + * sense to add the modifier to the list of object's modifiers. + */ + protected boolean modifying = true; + + /** + * This method applies the modifier to the given node. + * + * @param node + * the node that will have modifier applied + * @param blenderContext + * the blender context + */ + public abstract void apply(Node node, BlenderContext blenderContext); + + /** + * Determines if the modifier can be applied multiple times over one mesh. + * At this moment only armature and object animation modifiers cannot be + * applied multiple times. + * + * @param modifierType + * the type name of the modifier + * @return true if the modifier can be applied many times and + * false otherwise + */ + public static boolean canBeAppliedMultipleTimes(String modifierType) { + return !(ARMATURE_MODIFIER_DATA.equals(modifierType) || OBJECT_ANIMATION_MODIFIER_DATA.equals(modifierType)); + } + + protected boolean validate(Structure modifierStructure, BlenderContext blenderContext) { + Structure modifierData = (Structure) modifierStructure.getFieldValue("modifier"); + Pointer pError = (Pointer) modifierData.getFieldValue("error"); + invalid = pError.isNotNull(); + return !invalid; + } + + /** + * @return true if the modifier causes feature's modification or false if not + */ + public boolean isModifying() { + return modifying; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java new file mode 100644 index 000000000..a0ad5c8ef --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.modifiers; + +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.animations.Ipo; +import com.jme3.scene.plugins.blender.animations.IpoHelper; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A class that is used in modifiers calculations. + * + * @author Marcin Roguski + */ +public class ModifierHelper extends AbstractBlenderHelper { + + private static final Logger LOGGER = Logger.getLogger(ModifierHelper.class.getName()); + + /** + * This constructor parses the given blender version and stores the result. + * Some functionalities may differ in different blender versions. + * + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public ModifierHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + /** + * This method reads the given object's modifiers. + * + * @param objectStructure + * the object structure + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public Collection readModifiers(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { + Set alreadyReadModifiers = new HashSet(); + Collection result = new ArrayList(); + Structure modifiersListBase = (Structure) objectStructure.getFieldValue("modifiers"); + List modifiers = modifiersListBase.evaluateListBase(); + for (Structure modifierStructure : modifiers) { + String modifierType = modifierStructure.getType(); + if (!Modifier.canBeAppliedMultipleTimes(modifierType) && alreadyReadModifiers.contains(modifierType)) { + LOGGER.log(Level.WARNING, "Modifier {0} can only be applied once to object: {1}", new Object[] { modifierType, objectStructure.getName() }); + } else { + Modifier modifier = null; + if (Modifier.ARRAY_MODIFIER_DATA.equals(modifierStructure.getType())) { + modifier = new ArrayModifier(modifierStructure, blenderContext); + } else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifierStructure.getType())) { + modifier = new MirrorModifier(modifierStructure, blenderContext); + } else if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifierStructure.getType())) { + modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext); + } else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) { + modifier = new ParticlesModifier(modifierStructure, blenderContext); + } + + if (modifier != null) { + if (modifier.isModifying()) { + result.add(modifier); + alreadyReadModifiers.add(modifierType); + } else { + LOGGER.log(Level.WARNING, "The modifier {0} will cause no changes in the model. It will be ignored!", modifierStructure.getName()); + } + } else { + LOGGER.log(Level.WARNING, "Unsupported modifier type: {0}", modifierStructure.getType()); + } + } + } + + // at the end read object's animation modifier (object animation is + // either described by action or by ipo of the object) + Modifier modifier; + if (blenderVersion <= 249) { + modifier = this.readAnimationModifier249(objectStructure, blenderContext); + } else { + modifier = this.readAnimationModifier250(objectStructure, blenderContext); + } + if (modifier != null) { + result.add(modifier); + } + return result; + } + + /** + * This method reads the object's animation modifier for blender version + * 2.49 and lower. + * + * @param objectStructure + * the object's structure + * @param blenderContext + * the blender context + * @return loaded modifier + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + private Modifier readAnimationModifier249(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { + Modifier result = null; + Pointer pIpo = (Pointer) objectStructure.getFieldValue("ipo"); + if (pIpo.isNotNull()) { + IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); + Structure ipoStructure = pIpo.fetchData().get(0); + Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext); + if (ipo != null) { + result = new ObjectAnimationModifier(ipo, objectStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext); + } + } + return result; + } + + /** + * This method reads the object's animation modifier for blender version + * 2.50 and higher. + * + * @param objectStructure + * the object's structure + * @param blenderContext + * the blender context + * @return loaded modifier + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + private Modifier readAnimationModifier250(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { + Modifier result = null; + Pointer pAnimData = (Pointer) objectStructure.getFieldValue("adt"); + if (pAnimData.isNotNull()) { + Structure animData = pAnimData.fetchData().get(0); + Pointer pAction = (Pointer) animData.getFieldValue("action"); + if (pAction.isNotNull()) { + Structure actionStructure = pAction.fetchData().get(0); + IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); + Ipo ipo = ipoHelper.fromAction(actionStructure, blenderContext); + if (ipo != null) {// ipo can be null if it has no curves applied, ommit such modifier then + result = new ObjectAnimationModifier(ipo, actionStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext); + } + } + } + return result; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java new file mode 100644 index 000000000..caf1e206e --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java @@ -0,0 +1,89 @@ +package com.jme3.scene.plugins.blender.modifiers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.Animation; +import com.jme3.animation.SpatialTrack; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.animations.AnimationData; +import com.jme3.scene.plugins.blender.animations.Ipo; +import com.jme3.scene.plugins.blender.file.BlenderFileException; + +/** + * This modifier allows to add animation to the object. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ObjectAnimationModifier extends Modifier { + private static final Logger LOGGER = Logger.getLogger(ObjectAnimationModifier.class.getName()); + + /** Loaded animation data. */ + private AnimationData animationData; + + /** + * This constructor reads animation of the object itself (without bones) and + * stores it as an ArmatureModifierData modifier. The animation is returned + * as a modifier. It should be later applied regardless other modifiers. The + * reason for this is that object may not have modifiers added but it's + * animation should be working. The stored modifier is an anim data and + * additional data is given object's OMA. + * + * @param ipo + * the object's interpolation curves + * @param objectAnimationName + * the name of object's animation + * @param objectOMA + * the OMA of the object + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public ObjectAnimationModifier(Ipo ipo, String objectAnimationName, Long objectOMA, BlenderContext blenderContext) throws BlenderFileException { + int fps = blenderContext.getBlenderKey().getFps(); + + Spatial object = (Spatial) blenderContext.getLoadedFeature(objectOMA, LoadedFeatureDataType.LOADED_FEATURE); + // calculating track + SpatialTrack track = (SpatialTrack) ipo.calculateTrack(-1, object.getLocalTranslation(), object.getLocalRotation(), object.getLocalScale(), 0, ipo.getLastFrame(), fps, true); + + Animation animation = new Animation(objectAnimationName, ipo.getLastFrame() / (float) fps); + animation.setTracks(new SpatialTrack[] { track }); + ArrayList animations = new ArrayList(1); + animations.add(animation); + + animationData = new AnimationData(animations); + blenderContext.setAnimData(objectOMA, animationData); + } + + @Override + public void apply(Node node, BlenderContext blenderContext) { + if (invalid) { + LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName()); + }// if invalid, animData will be null + if (animationData != null) { + // INFO: constraints for this modifier are applied in the + // ObjectHelper when the whole object is loaded + List animList = animationData.anims; + if (animList != null && animList.size() > 0) { + HashMap anims = new HashMap(); + for (int i = 0; i < animList.size(); ++i) { + Animation animation = animList.get(i); + anims.put(animation.getName(), animation); + } + + AnimControl control = new AnimControl(null); + control.setAnimations(anims); + node.addControl(control); + } + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java new file mode 100644 index 000000000..bda881a05 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java @@ -0,0 +1,94 @@ +package com.jme3.scene.plugins.blender.modifiers; + +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.shapes.EmitterMeshVertexShape; +import com.jme3.effect.shapes.EmitterShape; +import com.jme3.material.Material; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialHelper; +import com.jme3.scene.plugins.blender.particles.ParticlesHelper; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This modifier allows to add particles to the object. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ParticlesModifier extends Modifier { + private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); + + /** Loaded particles emitter. */ + private ParticleEmitter particleEmitter; + + /** + * This constructor reads the particles system structure and stores it in + * order to apply it later to the node. + * + * @param modifierStructure + * the structure of the modifier + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is throw wneh there are problems with the + * blender file + */ + public ParticlesModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { + if (this.validate(modifierStructure, blenderContext)) { + Pointer pParticleSystem = (Pointer) modifierStructure.getFieldValue("psys"); + if (pParticleSystem.isNotNull()) { + ParticlesHelper particlesHelper = blenderContext.getHelper(ParticlesHelper.class); + Structure particleSystem = pParticleSystem.fetchData().get(0); + particleEmitter = particlesHelper.toParticleEmitter(particleSystem); + } + } + } + + @Override + public void apply(Node node, BlenderContext blenderContext) { + if (invalid) { + LOGGER.log(Level.WARNING, "Particles modifier is invalid! Cannot be applied to: {0}", node.getName()); + } else { + MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + ParticleEmitter emitter = particleEmitter.clone(); + + // veryfying the alpha function for particles' texture + Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE; + char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1); + if (nameSuffix == 'B' || nameSuffix == 'N') { + alphaFunction = MaterialHelper.ALPHA_MASK_NONE; + } + // removing the type suffix from the name + emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1)); + + // applying emitter shape + EmitterShape emitterShape = emitter.getShape(); + List meshes = new ArrayList(); + for (Spatial spatial : node.getChildren()) { + if (spatial instanceof Geometry) { + Mesh mesh = ((Geometry) spatial).getMesh(); + if (mesh != null) { + meshes.add(mesh); + Material material = materialHelper.getParticlesMaterial(((Geometry) spatial).getMaterial(), alphaFunction, blenderContext); + emitter.setMaterial(material);// TODO: divide into several pieces + } + } + } + if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) { + ((EmitterMeshVertexShape) emitterShape).setMeshes(meshes); + } + + node.attachChild(emitter); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java new file mode 100644 index 000000000..cf1835095 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.objects; + +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.asset.BlenderKey.FeaturesToLoad; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.animations.ArmatureHelper; +import com.jme3.scene.plugins.blender.cameras.CameraHelper; +import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; +import com.jme3.scene.plugins.blender.curves.CurvesHelper; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.lights.LightHelper; +import com.jme3.scene.plugins.blender.meshes.MeshHelper; +import com.jme3.scene.plugins.blender.modifiers.Modifier; +import com.jme3.scene.plugins.blender.modifiers.ModifierHelper; +import com.jme3.util.TempVars; + +/** + * A class that is used in object calculations. + * + * @author Marcin Roguski (Kaelthas) + */ +public class ObjectHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName()); + + public static final String OMA_MARKER = "oma"; + + /** + * This constructor parses the given blender version and stores the result. + * Some functionalities may differ in different blender versions. + * + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public ObjectHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + /** + * This method reads the given structure and createn an object that + * represents the data. + * + * @param objectStructure + * the object's structure + * @param blenderContext + * the blender context + * @return blener's object representation or null if its type is excluded from loading + * @throws BlenderFileException + * an exception is thrown when the given data is inapropriate + */ + public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.fine("Loading blender object."); + + int type = ((Number) objectStructure.getFieldValue("type")).intValue(); + ObjectType objectType = ObjectType.valueOf(type); + LOGGER.log(Level.FINE, "Type of the object: {0}.", objectType); + if (objectType == ObjectType.LAMP && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.LIGHTS)) { + LOGGER.fine("Lamps are not included in loading."); + return null; + } + if (objectType == ObjectType.CAMERA && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.CAMERAS)) { + LOGGER.fine("Cameras are not included in loading."); + return null; + } + if (!blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.OBJECTS)) { + LOGGER.fine("Objects are not included in loading."); + return null; + } + int lay = ((Number) objectStructure.getFieldValue("lay")).intValue(); + if ((lay & blenderContext.getBlenderKey().getLayersToLoad()) == 0) { + LOGGER.fine("The layer this object is located in is not included in loading."); + return null; + } + + LOGGER.fine("Checking if the object has not been already loaded."); + Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (loadedResult != null) { + return loadedResult; + } + + blenderContext.pushParent(objectStructure); + String name = objectStructure.getName(); + LOGGER.log(Level.FINE, "Loading obejct: {0}", name); + + int restrictflag = ((Number) objectStructure.getFieldValue("restrictflag")).intValue(); + boolean visible = (restrictflag & 0x01) != 0; + + Pointer pParent = (Pointer) objectStructure.getFieldValue("parent"); + Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (parent == null && pParent.isNotNull()) { + Structure parentStructure = pParent.fetchData().get(0); + parent = this.toObject(parentStructure, blenderContext); + } + + Transform t = this.getTransformation(objectStructure, blenderContext); + LOGGER.log(Level.FINE, "Importing object of type: {0}", objectType); + Node result = null; + try { + switch (objectType) { + case EMPTY: + case ARMATURE: + // need to use an empty node to properly create + // parent-children relationships between nodes + result = new Node(name); + break; + case MESH: + result = new Node(name); + MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); + Pointer pMesh = (Pointer) objectStructure.getFieldValue("data"); + List meshesArray = pMesh.fetchData(); + List geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext); + if (geometries != null) { + for (Geometry geometry : geometries) { + result.attachChild(geometry); + } + } + break; + case SURF: + case CURVE: + result = new Node(name); + Pointer pCurve = (Pointer) objectStructure.getFieldValue("data"); + if (pCurve.isNotNull()) { + CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class); + Structure curveData = pCurve.fetchData().get(0); + List curves = curvesHelper.toCurve(curveData, blenderContext); + for (Geometry curve : curves) { + result.attachChild(curve); + } + } + break; + case LAMP: + Pointer pLamp = (Pointer) objectStructure.getFieldValue("data"); + if (pLamp.isNotNull()) { + LightHelper lightHelper = blenderContext.getHelper(LightHelper.class); + List lampsArray = pLamp.fetchData(); + result = lightHelper.toLight(lampsArray.get(0), blenderContext); + } + break; + case CAMERA: + Pointer pCamera = (Pointer) objectStructure.getFieldValue("data"); + if (pCamera.isNotNull()) { + CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class); + List camerasArray = pCamera.fetchData(); + result = cameraHelper.toCamera(camerasArray.get(0), blenderContext); + } + break; + default: + LOGGER.log(Level.WARNING, "Unsupported object type: {0}", type); + } + } finally { + blenderContext.popParent(); + } + + if (result != null) { + blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result); + + result.setLocalTransform(t); + result.setCullHint(visible ? CullHint.Always : CullHint.Inherit); + if (parent instanceof Node) { + ((Node) parent).attachChild(result); + } + + if (result.getChildren() != null) { + for (Spatial child : result.getChildren()) { + if (child instanceof Geometry) { + this.flipMeshIfRequired((Geometry) child, child.getWorldScale()); + } + } + } + + LOGGER.fine("Reading and applying object's modifiers."); + ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class); + Collection modifiers = modifierHelper.readModifiers(objectStructure, blenderContext); + for (Modifier modifier : modifiers) { + modifier.apply(result, blenderContext); + } + + // I prefer do compute bounding box here than read it from the file + result.updateModelBound(); + + LOGGER.fine("Applying markers (those will be removed before the final result is released)."); + blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress()); + if (objectType == ObjectType.ARMATURE) { + blenderContext.addMarker(ArmatureHelper.ARMATURE_NODE_MARKER, result, Boolean.TRUE); + } + + LOGGER.fine("Loading constraints connected with this object."); + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + constraintHelper.loadConstraints(objectStructure, blenderContext); + + LOGGER.fine("Loading custom properties."); + if (blenderContext.getBlenderKey().isLoadObjectProperties()) { + Properties properties = this.loadProperties(objectStructure, blenderContext); + // the loaded property is a group property, so we need to get + // each value and set it to Spatial + if (properties != null && properties.getValue() != null) { + this.applyProperties(result, properties); + } + } + } + return result; + } + + /** + * The method flips the mesh if the scale is mirroring it. Mirroring scale has either 1 or all 3 factors negative. + * If two factors are negative then there is no mirroring because a rotation and translation can be found that will + * lead to the same transform when all scales are positive. + * + * @param geometry + * the geometry that is being flipped if necessary + * @param scale + * the scale vector of the given geometry + */ + private void flipMeshIfRequired(Geometry geometry, Vector3f scale) { + float s = scale.x * scale.y * scale.z; + + if (s < 0 && geometry.getMesh() != null) {// negative s means that the scale is mirroring the object + FloatBuffer normals = geometry.getMesh().getFloatBuffer(Type.Normal); + if (normals != null) { + for (int i = 0; i < normals.limit(); i += 3) { + if (scale.x < 0) { + normals.put(i, -normals.get(i)); + } + if (scale.y < 0) { + normals.put(i + 1, -normals.get(i + 1)); + } + if (scale.z < 0) { + normals.put(i + 2, -normals.get(i + 2)); + } + } + } + + if (geometry.getMesh().getMode() == Mode.Triangles) {// there is no need to flip the indexes for lines and points + LOGGER.finer("Flipping index order in triangle mesh."); + Buffer indexBuffer = geometry.getMesh().getBuffer(Type.Index).getData(); + for (int i = 0; i < indexBuffer.limit(); i += 3) { + if (indexBuffer instanceof ShortBuffer) { + short index = ((ShortBuffer) indexBuffer).get(i + 1); + ((ShortBuffer) indexBuffer).put(i + 1, ((ShortBuffer) indexBuffer).get(i + 2)); + ((ShortBuffer) indexBuffer).put(i + 2, index); + } else { + int index = ((IntBuffer) indexBuffer).get(i + 1); + ((IntBuffer) indexBuffer).put(i + 1, ((IntBuffer) indexBuffer).get(i + 2)); + ((IntBuffer) indexBuffer).put(i + 2, index); + } + } + } + } + } + + /** + * Checks if the first given OMA points to a parent of the second one. + * The parent need not to be the direct one. This method should be called when we are sure + * that both of the features are alred loaded because it does not check it. + * The OMA's should point to a spatials, otherwise the function will throw ClassCastException. + * @param supposedParentOMA + * the OMA of the node that we suppose might be a parent of the second one + * @param spatialOMA + * the OMA of the scene's node + * @return true if the first given OMA points to a parent of the second one and false otherwise + */ + public boolean isParent(Long supposedParentOMA, Long spatialOMA) { + Spatial supposedParent = (Spatial) blenderContext.getLoadedFeature(supposedParentOMA, LoadedFeatureDataType.LOADED_FEATURE); + Spatial spatial = (Spatial) blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_FEATURE); + + Spatial parent = spatial.getParent(); + while (parent != null) { + if (parent.equals(supposedParent)) { + return true; + } + parent = parent.getParent(); + } + return false; + } + + /** + * This method calculates local transformation for the object. Parentage is + * taken under consideration. + * + * @param objectStructure + * the object's structure + * @return objects transformation relative to its parent + */ + public Transform getTransformation(Structure objectStructure, BlenderContext blenderContext) { + TempVars tempVars = TempVars.get(); + + Matrix4f parentInv = tempVars.tempMat4; + Pointer pParent = (Pointer) objectStructure.getFieldValue("parent"); + if (pParent.isNotNull()) { + Structure parentObjectStructure = (Structure) blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_STRUCTURE); + this.getMatrix(parentObjectStructure, "obmat", fixUpAxis, parentInv).invertLocal(); + } else { + parentInv.loadIdentity(); + } + + Matrix4f globalMatrix = this.getMatrix(objectStructure, "obmat", fixUpAxis, tempVars.tempMat42); + Matrix4f localMatrix = parentInv.multLocal(globalMatrix); + + this.getSizeSignums(objectStructure, tempVars.vect1); + + localMatrix.toTranslationVector(tempVars.vect2); + localMatrix.toRotationQuat(tempVars.quat1); + localMatrix.toScaleVector(tempVars.vect3); + + Transform t = new Transform(tempVars.vect2, tempVars.quat1.normalizeLocal(), tempVars.vect3.multLocal(tempVars.vect1)); + tempVars.release(); + return t; + } + + /** + * The method gets the signs of the scale factors and stores them properly in the given vector. + * @param objectStructure + * the object's structure + * @param store + * the vector where the result will be stored + */ + @SuppressWarnings("unchecked") + private void getSizeSignums(Structure objectStructure, Vector3f store) { + DynamicArray size = (DynamicArray) objectStructure.getFieldValue("size"); + if (fixUpAxis) { + store.x = Math.signum(size.get(0).floatValue()); + store.y = Math.signum(size.get(2).floatValue()); + store.z = Math.signum(size.get(1).floatValue()); + } else { + store.x = Math.signum(size.get(0).floatValue()); + store.y = Math.signum(size.get(1).floatValue()); + store.z = Math.signum(size.get(2).floatValue()); + } + } + + /** + * This method returns the matrix of a given name for the given structure. + * It takes up axis into consideration. + * + * The method that moves the matrix from Z-up axis to Y-up axis space is as follows: + * - load the matrix directly from blender (it has the Z-up axis orientation) + * - switch the second and third rows in the matrix + * - switch the second and third column in the matrix + * - multiply the values in the third row by -1 + * - multiply the values in the third column by -1 + * + * The result matrix is now in Y-up axis orientation. + * The procedure was discovered by experimenting but it looks like it's working :) + * The previous procedure transformet the loaded matrix into component (loc, rot, scale), + * switched several values and pu the back into the matrix. + * It worked fine until models with negative scale are used. + * The current method is not touched by that flaw. + * + * @param structure + * the structure with matrix data + * @param matrixName + * the name of the matrix + * @param fixUpAxis + * tells if the Y axis is a UP axis + * @param store + * the matrix where the result will pe placed + * @return the required matrix + */ + @SuppressWarnings("unchecked") + private Matrix4f getMatrix(Structure structure, String matrixName, boolean fixUpAxis, Matrix4f store) { + DynamicArray obmat = (DynamicArray) structure.getFieldValue(matrixName); + // the matrix must be square + int rowAndColumnSize = Math.abs((int) Math.sqrt(obmat.getTotalSize())); + for (int i = 0; i < rowAndColumnSize; ++i) { + for (int j = 0; j < rowAndColumnSize; ++j) { + float value = obmat.get(j, i).floatValue(); + if (Math.abs(value) <= FastMath.FLT_EPSILON) { + value = 0; + } + store.set(i, j, value); + } + } + if (fixUpAxis) { + // first switch the second and third row + for (int i = 0; i < 4; ++i) { + float temp = store.get(1, i); + store.set(1, i, store.get(2, i)); + store.set(2, i, temp); + } + + // then switch the second and third column + for (int i = 0; i < 4; ++i) { + float temp = store.get(i, 1); + store.set(i, 1, store.get(i, 2)); + store.set(i, 2, temp); + } + + // multiply the values in the third row by -1 + store.m20 *= -1; + store.m21 *= -1; + store.m22 *= -1; + store.m23 *= -1; + + // multiply the values in the third column by -1 + store.m02 *= -1; + store.m12 *= -1; + store.m22 *= -1; + store.m32 *= -1; + } + + return store; + } + + /** + * This method returns the matrix of a given name for the given structure. + * It takes up axis into consideration. + * + * @param structure + * the structure with matrix data + * @param matrixName + * the name of the matrix + * @param fixUpAxis + * tells if the Y axis is a UP axis + * @return the required matrix + */ + public Matrix4f getMatrix(Structure structure, String matrixName, boolean fixUpAxis) { + return this.getMatrix(structure, matrixName, fixUpAxis, new Matrix4f()); + } + + private static enum ObjectType { + EMPTY(0), MESH(1), CURVE(2), SURF(3), TEXT(4), METABALL(5), LAMP(10), CAMERA(11), WAVE(21), LATTICE(22), ARMATURE(25); + + private int blenderTypeValue; + + private ObjectType(int blenderTypeValue) { + this.blenderTypeValue = blenderTypeValue; + } + + public static ObjectType valueOf(int blenderTypeValue) throws BlenderFileException { + for (ObjectType type : ObjectType.values()) { + if (type.blenderTypeValue == blenderTypeValue) { + return type; + } + } + throw new BlenderFileException("Unknown type value: " + blenderTypeValue); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/Properties.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/Properties.java new file mode 100644 index 000000000..c1f867543 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/Properties.java @@ -0,0 +1,365 @@ +package com.jme3.scene.plugins.blender.objects; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.BlenderInputStream; +import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The blender object's custom properties. + * This class is valid for all versions of blender. + * @author Marcin Roguski (Kaelthas) + */ +public class Properties implements Cloneable { + // property type + public static final int IDP_STRING = 0; + public static final int IDP_INT = 1; + public static final int IDP_FLOAT = 2; + public static final int IDP_ARRAY = 5; + public static final int IDP_GROUP = 6; + // public static final int IDP_ID = 7;//this is not implemented in blender (yet) + public static final int IDP_DOUBLE = 8; + // the following are valid for blender 2.5x+ + public static final int IDP_IDPARRAY = 9; + public static final int IDP_NUMTYPES = 10; + + protected static final String RNA_PROPERTY_NAME = "_RNA_UI"; + /** Default name of the property (used if the name is not specified in blender file). */ + protected static final String DEFAULT_NAME = "Unnamed property"; + + /** The name of the property. */ + private String name; + /** The type of the property. */ + private int type; + /** The subtype of the property. Defines the type of array's elements. */ + private int subType; + /** The value of the property. */ + private Object value; + /** The description of the property. */ + private String description; + + /** + * This method loads the property from the belnder file. + * @param idPropertyStructure + * the ID structure constining the property + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when the belnder file is somehow invalid + */ + public void load(Structure idPropertyStructure, BlenderContext blenderContext) throws BlenderFileException { + name = idPropertyStructure.getFieldValue("name").toString(); + if (name == null || name.length() == 0) { + name = DEFAULT_NAME; + } + subType = ((Number) idPropertyStructure.getFieldValue("subtype")).intValue(); + type = ((Number) idPropertyStructure.getFieldValue("type")).intValue(); + + // reading the data + Structure data = (Structure) idPropertyStructure.getFieldValue("data"); + int len = ((Number) idPropertyStructure.getFieldValue("len")).intValue(); + switch (type) { + case IDP_STRING: { + Pointer pointer = (Pointer) data.getFieldValue("pointer"); + BlenderInputStream bis = blenderContext.getInputStream(); + FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress()); + bis.setPosition(dataFileBlock.getBlockPosition()); + value = bis.readString(); + break; + } + case IDP_INT: + int intValue = ((Number) data.getFieldValue("val")).intValue(); + value = Integer.valueOf(intValue); + break; + case IDP_FLOAT: + int floatValue = ((Number) data.getFieldValue("val")).intValue(); + value = Float.valueOf(Float.intBitsToFloat(floatValue)); + break; + case IDP_ARRAY: { + Pointer pointer = (Pointer) data.getFieldValue("pointer"); + BlenderInputStream bis = blenderContext.getInputStream(); + FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress()); + bis.setPosition(dataFileBlock.getBlockPosition()); + int elementAmount = dataFileBlock.getSize(); + switch (subType) { + case IDP_INT: + elementAmount /= 4; + int[] intList = new int[elementAmount]; + for (int i = 0; i < elementAmount; ++i) { + intList[i] = bis.readInt(); + } + value = intList; + break; + case IDP_FLOAT: + elementAmount /= 4; + float[] floatList = new float[elementAmount]; + for (int i = 0; i < elementAmount; ++i) { + floatList[i] = bis.readFloat(); + } + value = floatList; + break; + case IDP_DOUBLE: + elementAmount /= 8; + double[] doubleList = new double[elementAmount]; + for (int i = 0; i < elementAmount; ++i) { + doubleList[i] = bis.readDouble(); + } + value = doubleList; + break; + default: + throw new IllegalStateException("Invalid array subtype: " + subType); + } + } + case IDP_GROUP: + Structure group = (Structure) data.getFieldValue("group"); + List dataList = group.evaluateListBase(); + List subProperties = new ArrayList(len); + for (Structure d : dataList) { + Properties properties = new Properties(); + properties.load(d, blenderContext); + subProperties.add(properties); + } + value = subProperties; + break; + case IDP_DOUBLE: + int doublePart1 = ((Number) data.getFieldValue("val")).intValue(); + int doublePart2 = ((Number) data.getFieldValue("val2")).intValue(); + long doubleVal = (long) doublePart2 << 32 | doublePart1; + value = Double.valueOf(Double.longBitsToDouble(doubleVal)); + break; + case IDP_IDPARRAY: { + Pointer pointer = (Pointer) data.getFieldValue("pointer"); + List arrays = pointer.fetchData(); + List result = new ArrayList(arrays.size()); + Properties temp = new Properties(); + for (Structure array : arrays) { + temp.load(array, blenderContext); + result.add(temp.value); + } + value = result; + break; + } + case IDP_NUMTYPES: + throw new UnsupportedOperationException(); + // case IDP_ID://not yet implemented in blender + // return null; + default: + throw new IllegalStateException("Unknown custom property type: " + type); + } + this.completeLoading(); + } + + /** + * This method returns the name of the property. + * @return the name of the property + */ + public String getName() { + return name; + } + + /** + * This method returns the value of the property. + * The type of the value depends on the type of the property. + * @return the value of the property + */ + public Object getValue() { + return value; + } + + /** + * @return the names of properties that are stored withing this property + * (assuming this property is of IDP_GROUP type) + */ + @SuppressWarnings("unchecked") + public List getSubPropertiesNames() { + List result = null; + if (type == IDP_GROUP) { + List properties = (List) value; + if (properties != null && properties.size() > 0) { + result = new ArrayList(properties.size()); + for (Properties property : properties) { + result.add(property.getName()); + } + } + } + return result; + } + + /** + * This method returns the same as getValue if the current property is of + * other type than IDP_GROUP and its name matches 'propertyName' param. If + * this property is a group property the method tries to find subproperty + * value of the given name. The first found value is returnes os use this + * method wisely. If no property of a given name is foung - null + * is returned. + * + * @param propertyName + * the name of the property + * @return found property value or null + */ + @SuppressWarnings("unchecked") + public Object findValue(String propertyName) { + if (name.equals(propertyName)) { + return value; + } else { + if (type == IDP_GROUP) { + List props = (List) value; + for (Properties p : props) { + Object v = p.findValue(propertyName); + if (v != null) { + return v; + } + } + } + } + return null; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + this.append(sb, new StringBuilder()); + return sb.toString(); + } + + /** + * This method appends the data of the property to the given string buffer. + * @param sb + * string buffer + * @param indent + * indent buffer + */ + @SuppressWarnings("unchecked") + private void append(StringBuilder sb, StringBuilder indent) { + sb.append(indent).append("name: ").append(name).append("\n\r"); + sb.append(indent).append("type: ").append(type).append("\n\r"); + sb.append(indent).append("subType: ").append(subType).append("\n\r"); + sb.append(indent).append("description: ").append(description).append("\n\r"); + indent.append('\t'); + sb.append(indent).append("value: "); + if (value instanceof Properties) { + ((Properties) value).append(sb, indent); + } else if (value instanceof List) { + for (Object v : (List) value) { + if (v instanceof Properties) { + sb.append(indent).append("{\n\r"); + indent.append('\t'); + ((Properties) v).append(sb, indent); + indent.deleteCharAt(indent.length() - 1); + sb.append(indent).append("}\n\r"); + } else { + sb.append(v); + } + } + } else { + sb.append(value); + } + sb.append("\n\r"); + indent.deleteCharAt(indent.length() - 1); + } + + /** + * This method should be called after the properties loading. + * It loads the properties from the _RNA_UI property and removes this property from the + * result list. + */ + @SuppressWarnings("unchecked") + protected void completeLoading() { + if (type == IDP_GROUP) { + List groupProperties = (List) value; + Properties rnaUI = null; + for (Properties properties : groupProperties) { + if (properties.name.equals(RNA_PROPERTY_NAME) && properties.type == IDP_GROUP) { + rnaUI = properties; + break; + } + } + if (rnaUI != null) { + // removing the RNA from the result list + groupProperties.remove(rnaUI); + + // loading the descriptions + Map descriptions = new HashMap(groupProperties.size()); + List propertiesRNA = (List) rnaUI.value; + for (Properties properties : propertiesRNA) { + String name = properties.name; + String description = null; + List rnaData = (List) properties.value; + for (Properties rna : rnaData) { + if ("description".equalsIgnoreCase(rna.name)) { + description = (String) rna.value; + break; + } + } + descriptions.put(name, description); + } + + // applying the descriptions + for (Properties properties : groupProperties) { + properties.description = descriptions.get(properties.name); + } + } + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (description == null ? 0 : description.hashCode()); + result = prime * result + (name == null ? 0 : name.hashCode()); + result = prime * result + subType; + result = prime * result + type; + result = prime * result + (value == null ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (this.getClass() != obj.getClass()) { + return false; + } + Properties other = (Properties) obj; + if (description == null) { + if (other.description != null) { + return false; + } + } else if (!description.equals(other.description)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (subType != other.subType) { + return false; + } + if (type != other.type) { + return false; + } + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java new file mode 100644 index 000000000..85c093f66 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java @@ -0,0 +1,192 @@ +package com.jme3.scene.plugins.blender.particles; + +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.effect.influencers.EmptyParticleInfluencer; +import com.jme3.effect.influencers.NewtonianParticleInfluencer; +import com.jme3.effect.influencers.ParticleInfluencer; +import com.jme3.effect.shapes.EmitterMeshConvexHullShape; +import com.jme3.effect.shapes.EmitterMeshFaceShape; +import com.jme3.effect.shapes.EmitterMeshVertexShape; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; + +import java.util.logging.Logger; + +public class ParticlesHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(ParticlesHelper.class.getName()); + + // part->type + public static final int PART_EMITTER = 0; + public static final int PART_REACTOR = 1; + public static final int PART_HAIR = 2; + public static final int PART_FLUID = 3; + + // part->flag + public static final int PART_REACT_STA_END = 1; + public static final int PART_REACT_MULTIPLE = 2; + public static final int PART_LOOP = 4; + // public static final int PART_LOOP_INSTANT =8; + public static final int PART_HAIR_GEOMETRY = 16; + public static final int PART_UNBORN = 32; // show unborn particles + public static final int PART_DIED = 64; // show died particles + public static final int PART_TRAND = 128; + public static final int PART_EDISTR = 256; // particle/face from face areas + public static final int PART_STICKY = 512; // collided particles can stick to collider + public static final int PART_DIE_ON_COL = 1 << 12; + public static final int PART_SIZE_DEFL = 1 << 13; // swept sphere deflections + public static final int PART_ROT_DYN = 1 << 14; // dynamic rotation + public static final int PART_SIZEMASS = 1 << 16; + public static final int PART_ABS_LENGTH = 1 << 15; + public static final int PART_ABS_TIME = 1 << 17; + public static final int PART_GLOB_TIME = 1 << 18; + public static final int PART_BOIDS_2D = 1 << 19; + public static final int PART_BRANCHING = 1 << 20; + public static final int PART_ANIM_BRANCHING = 1 << 21; + public static final int PART_SELF_EFFECT = 1 << 22; + public static final int PART_SYMM_BRANCHING = 1 << 24; + public static final int PART_HAIR_BSPLINE = 1024; + public static final int PART_GRID_INVERT = 1 << 26; + public static final int PART_CHILD_EFFECT = 1 << 27; + public static final int PART_CHILD_SEAMS = 1 << 28; + public static final int PART_CHILD_RENDER = 1 << 29; + public static final int PART_CHILD_GUIDE = 1 << 30; + + // part->from + public static final int PART_FROM_VERT = 0; + public static final int PART_FROM_FACE = 1; + public static final int PART_FROM_VOLUME = 2; + public static final int PART_FROM_PARTICLE = 3; + public static final int PART_FROM_CHILD = 4; + + // part->phystype + public static final int PART_PHYS_NO = 0; + public static final int PART_PHYS_NEWTON = 1; + public static final int PART_PHYS_KEYED = 2; + public static final int PART_PHYS_BOIDS = 3; + + // part->draw_as + public static final int PART_DRAW_NOT = 0; + public static final int PART_DRAW_DOT = 1; + public static final int PART_DRAW_CIRC = 2; + public static final int PART_DRAW_CROSS = 3; + public static final int PART_DRAW_AXIS = 4; + public static final int PART_DRAW_LINE = 5; + public static final int PART_DRAW_PATH = 6; + public static final int PART_DRAW_OB = 7; + public static final int PART_DRAW_GR = 8; + public static final int PART_DRAW_BB = 9; + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in + * different blender versions. + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public ParticlesHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + @SuppressWarnings("unchecked") + public ParticleEmitter toParticleEmitter(Structure particleSystem) throws BlenderFileException { + ParticleEmitter result = null; + Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part"); + if (pParticleSettings.isNotNull()) { + Structure particleSettings = pParticleSettings.fetchData().get(0); + + int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue(); + + // draw type will be stored temporarily in the name (it is used during modifier applying operation) + int drawAs = ((Number) particleSettings.getFieldValue("draw_as")).intValue(); + char nameSuffix;// P - point, L - line, N - None, B - Bilboard + switch (drawAs) { + case PART_DRAW_NOT: + nameSuffix = 'N'; + totPart = 0;// no need to generate particles in this case + break; + case PART_DRAW_BB: + nameSuffix = 'B'; + break; + case PART_DRAW_OB: + case PART_DRAW_GR: + nameSuffix = 'P'; + LOGGER.warning("Neither object nor group particles supported yet! Using point representation instead!");// TODO: support groups and aobjects + break; + case PART_DRAW_LINE: + nameSuffix = 'L'; + LOGGER.warning("Lines not yet supported! Using point representation instead!");// TODO: support lines + default:// all others are rendered as points in blender + nameSuffix = 'P'; + } + result = new ParticleEmitter(particleSettings.getName() + nameSuffix, Type.Triangle, totPart); + if (nameSuffix == 'N') { + return result;// no need to set anything else + } + + // setting the emitters shape (the shapes meshes will be set later during modifier applying operation) + int from = ((Number) particleSettings.getFieldValue("from")).intValue(); + switch (from) { + case PART_FROM_VERT: + result.setShape(new EmitterMeshVertexShape()); + break; + case PART_FROM_FACE: + result.setShape(new EmitterMeshFaceShape()); + break; + case PART_FROM_VOLUME: + result.setShape(new EmitterMeshConvexHullShape()); + break; + default: + LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter: " + from + ')'); + } + + // reading acceleration + DynamicArray acc = (DynamicArray) particleSettings.getFieldValue("acc"); + result.setGravity(-acc.get(0).floatValue(), -acc.get(1).floatValue(), -acc.get(2).floatValue()); + + // setting the colors + result.setEndColor(new ColorRGBA(1f, 1f, 1f, 1f)); + result.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f)); + + // reading size + float sizeFactor = nameSuffix == 'B' ? 1.0f : 0.3f; + float size = ((Number) particleSettings.getFieldValue("size")).floatValue() * sizeFactor; + result.setStartSize(size); + result.setEndSize(size); + + // reading lifetime + int fps = blenderContext.getBlenderKey().getFps(); + float lifetime = ((Number) particleSettings.getFieldValue("lifetime")).floatValue() / fps; + float randlife = ((Number) particleSettings.getFieldValue("randlife")).floatValue() / fps; + result.setLowLife(lifetime * (1.0f - randlife)); + result.setHighLife(lifetime); + + // preparing influencer + ParticleInfluencer influencer; + int phystype = ((Number) particleSettings.getFieldValue("phystype")).intValue(); + switch (phystype) { + case PART_PHYS_NEWTON: + influencer = new NewtonianParticleInfluencer(); + ((NewtonianParticleInfluencer) influencer).setNormalVelocity(((Number) particleSettings.getFieldValue("normfac")).floatValue()); + ((NewtonianParticleInfluencer) influencer).setVelocityVariation(((Number) particleSettings.getFieldValue("randfac")).floatValue()); + ((NewtonianParticleInfluencer) influencer).setSurfaceTangentFactor(((Number) particleSettings.getFieldValue("tanfac")).floatValue()); + ((NewtonianParticleInfluencer) influencer).setSurfaceTangentRotation(((Number) particleSettings.getFieldValue("tanphase")).floatValue()); + break; + case PART_PHYS_BOIDS: + case PART_PHYS_KEYED:// TODO: support other influencers + LOGGER.warning("Boids and Keyed particles physic not yet supported! Empty influencer used!"); + case PART_PHYS_NO: + default: + influencer = new EmptyParticleInfluencer(); + } + result.setParticleInfluencer(influencer); + } + return result; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ColorBand.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ColorBand.java new file mode 100644 index 000000000..d8298e867 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ColorBand.java @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A class constaining the colorband data. + * + * @author Marcin Roguski (Kaelthas) + */ +public class ColorBand { + private static final Logger LOGGER = Logger.getLogger(ColorBand.class.getName()); + + // interpolation types + public static final int IPO_LINEAR = 0; + public static final int IPO_EASE = 1; + public static final int IPO_BSPLINE = 2; + public static final int IPO_CARDINAL = 3; + public static final int IPO_CONSTANT = 4; + + private int cursorsAmount, ipoType; + /** The default amount of possible cursor positions. */ + private int resultSize = 1001; + private ColorBandData[] data; + + /** + * A constructor used to instantiate color band by hand instead of reading it from the blend file. + * @param ipoType + * the interpolation type + * @param colors + * the colorband colors + * @param positions + * the positions for colors' cursors + * @param resultSize + * the size of the result table + */ + public ColorBand(int ipoType, List colors, List positions, int resultSize) { + if (colors == null || colors.size() < 1) { + throw new IllegalArgumentException("The amount of colorband's colors must be at least 1."); + } + if (ipoType < IPO_LINEAR || ipoType > IPO_CONSTANT) { + throw new IllegalArgumentException("Unknown colorband interpolation type: " + ipoType); + } + if (positions == null || positions.size() != colors.size()) { + throw new IllegalArgumentException("The size of positions and colors list should be equal!"); + } + for (Integer position : positions) { + if (position.intValue() < 0 || position.intValue() >= resultSize) { + throw new IllegalArgumentException("Invalid position value: " + position + "! Should be from range: [0, " + resultSize + "]!"); + } + } + + cursorsAmount = colors.size(); + this.ipoType = ipoType; + this.resultSize = resultSize; + data = new ColorBandData[cursorsAmount]; + for (int i = 0; i < cursorsAmount; ++i) { + data[i] = new ColorBandData(colors.get(i), positions.get(i)); + } + } + + /** + * Constructor. Loads the data from the given structure. + * @param tex + * @param blenderContext + */ + @SuppressWarnings("unchecked") + public ColorBand(Structure tex, BlenderContext blenderContext) { + int flag = ((Number) tex.getFieldValue("flag")).intValue(); + if ((flag & GeneratedTexture.TEX_COLORBAND) != 0) { + Pointer pColorband = (Pointer) tex.getFieldValue("coba"); + try { + Structure colorbandStructure = pColorband.fetchData().get(0); + cursorsAmount = ((Number) colorbandStructure.getFieldValue("tot")).intValue(); + ipoType = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue(); + data = new ColorBandData[cursorsAmount]; + DynamicArray data = (DynamicArray) colorbandStructure.getFieldValue("data"); + for (int i = 0; i < cursorsAmount; ++i) { + this.data[i] = new ColorBandData(data.get(i)); + } + } catch (BlenderFileException e) { + LOGGER.log(Level.WARNING, "Cannot fetch the colorband structure. The reason: {0}", e.getLocalizedMessage()); + } + } + } + + /** + * This method determines if the colorband has any transparencies or is not + * transparent at all. + * + * @return true if the colorband has transparencies and false + * otherwise + */ + public boolean hasTransparencies() { + if (data != null) { + for (ColorBandData colorBandData : data) { + if (colorBandData.a < 1.0f) { + return true; + } + } + } + return false; + } + + /** + * This method computes the values of the colorband. + * + * @return an array of 1001 elements and each element is float[4] object + * containing rgba values + */ + public float[][] computeValues() { + float[][] result = null; + if (data != null) { + result = new float[resultSize][4];// resultSize - amount of possible cursor positions; 4 = [r, g, b, a] + if (data.length == 1) {// special case; use only one color for all types of colorband interpolation + for (int i = 0; i < result.length; ++i) { + result[i][0] = data[0].r; + result[i][1] = data[0].g; + result[i][2] = data[0].b; + result[i][3] = data[0].a; + } + } else { + int currentCursor = 0; + ColorBandData currentData = data[0]; + ColorBandData nextData = data[0]; + switch (ipoType) { + case ColorBand.IPO_LINEAR: + float rDiff = 0, + gDiff = 0, + bDiff = 0, + aDiff = 0, + posDiff; + for (int i = 0; i < result.length; ++i) { + posDiff = i - currentData.pos; + result[i][0] = currentData.r + rDiff * posDiff; + result[i][1] = currentData.g + gDiff * posDiff; + result[i][2] = currentData.b + bDiff * posDiff; + result[i][3] = currentData.a + aDiff * posDiff; + if (nextData.pos == i) { + currentData = data[currentCursor++]; + if (currentCursor < data.length) { + nextData = data[currentCursor]; + // calculate differences + int d = nextData.pos - currentData.pos; + rDiff = (nextData.r - currentData.r) / d; + gDiff = (nextData.g - currentData.g) / d; + bDiff = (nextData.b - currentData.b) / d; + aDiff = (nextData.a - currentData.a) / d; + } else { + rDiff = gDiff = bDiff = aDiff = 0; + } + } + } + break; + case ColorBand.IPO_BSPLINE: + case ColorBand.IPO_CARDINAL: + Map cbDataMap = new TreeMap(); + for (int i = 0; i < data.length; ++i) { + cbDataMap.put(Integer.valueOf(i), data[i]); + } + + if (data[0].pos == 0) { + cbDataMap.put(Integer.valueOf(-1), data[0]); + } else { + ColorBandData cbData = new ColorBandData(data[0]); + cbData.pos = 0; + cbDataMap.put(Integer.valueOf(-1), cbData); + cbDataMap.put(Integer.valueOf(-2), cbData); + } + + if (data[data.length - 1].pos == 1000) { + cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]); + } else { + ColorBandData cbData = new ColorBandData(data[data.length - 1]); + cbData.pos = 1000; + cbDataMap.put(Integer.valueOf(data.length), cbData); + cbDataMap.put(Integer.valueOf(data.length + 1), cbData); + } + + float[] ipoFactors = new float[4]; + float f; + + ColorBandData data0 = this.getColorbandData(currentCursor - 2, cbDataMap); + ColorBandData data1 = this.getColorbandData(currentCursor - 1, cbDataMap); + ColorBandData data2 = this.getColorbandData(currentCursor, cbDataMap); + ColorBandData data3 = this.getColorbandData(currentCursor + 1, cbDataMap); + + for (int i = 0; i < result.length; ++i) { + if (data2.pos != data1.pos) { + f = (i - data2.pos) / (float) (data1.pos - data2.pos); + f = FastMath.clamp(f, 0.0f, 1.0f); + } else { + f = 0.0f; + } + this.getIpoData(f, ipoFactors); + result[i][0] = ipoFactors[3] * data0.r + ipoFactors[2] * data1.r + ipoFactors[1] * data2.r + ipoFactors[0] * data3.r; + result[i][1] = ipoFactors[3] * data0.g + ipoFactors[2] * data1.g + ipoFactors[1] * data2.g + ipoFactors[0] * data3.g; + result[i][2] = ipoFactors[3] * data0.b + ipoFactors[2] * data1.b + ipoFactors[1] * data2.b + ipoFactors[0] * data3.b; + result[i][3] = ipoFactors[3] * data0.a + ipoFactors[2] * data1.a + ipoFactors[1] * data2.a + ipoFactors[0] * data3.a; + result[i][0] = FastMath.clamp(result[i][0], 0.0f, 1.0f); + result[i][1] = FastMath.clamp(result[i][1], 0.0f, 1.0f); + result[i][2] = FastMath.clamp(result[i][2], 0.0f, 1.0f); + result[i][3] = FastMath.clamp(result[i][3], 0.0f, 1.0f); + + if (nextData.pos == i) { + ++currentCursor; + data0 = cbDataMap.get(currentCursor - 2); + data1 = cbDataMap.get(currentCursor - 1); + data2 = cbDataMap.get(currentCursor); + data3 = cbDataMap.get(currentCursor + 1); + } + } + break; + case ColorBand.IPO_EASE: + float d, + a, + b, + d2; + for (int i = 0; i < result.length; ++i) { + if (nextData.pos != currentData.pos) { + d = (i - currentData.pos) / (float) (nextData.pos - currentData.pos); + d2 = d * d; + a = 3.0f * d2 - 2.0f * d * d2; + b = 1.0f - a; + } else { + d = a = 0.0f; + b = 1.0f; + } + + result[i][0] = b * currentData.r + a * nextData.r; + result[i][1] = b * currentData.g + a * nextData.g; + result[i][2] = b * currentData.b + a * nextData.b; + result[i][3] = b * currentData.a + a * nextData.a; + if (nextData.pos == i) { + currentData = data[currentCursor++]; + if (currentCursor < data.length) { + nextData = data[currentCursor]; + } + } + } + break; + case ColorBand.IPO_CONSTANT: + for (int i = 0; i < result.length; ++i) { + result[i][0] = currentData.r; + result[i][1] = currentData.g; + result[i][2] = currentData.b; + result[i][3] = currentData.a; + if (nextData.pos == i) { + currentData = data[currentCursor++]; + if (currentCursor < data.length) { + nextData = data[currentCursor]; + } + } + } + break; + default: + throw new IllegalStateException("Unknown interpolation type: " + ipoType); + } + } + } + return result; + } + + private ColorBandData getColorbandData(int index, Map cbDataMap) { + ColorBandData result = cbDataMap.get(index); + if (result == null) { + result = new ColorBandData(); + } + return result; + } + + /** + * This method returns the data for either B-spline of Cardinal + * interpolation. + * + * @param d + * distance factor for the current intensity + * @param ipoFactors + * table to store the results (size of the table must be at least + * 4) + */ + private void getIpoData(float d, float[] ipoFactors) { + float d2 = d * d; + float d3 = d2 * d; + if (ipoType == ColorBand.IPO_BSPLINE) { + ipoFactors[0] = -0.71f * d3 + 1.42f * d2 - 0.71f * d; + ipoFactors[1] = 1.29f * d3 - 2.29f * d2 + 1.0f; + ipoFactors[2] = -1.29f * d3 + 1.58f * d2 + 0.71f * d; + ipoFactors[3] = 0.71f * d3 - 0.71f * d2; + } else if (ipoType == ColorBand.IPO_CARDINAL) { + ipoFactors[0] = -0.16666666f * d3 + 0.5f * d2 - 0.5f * d + 0.16666666f; + ipoFactors[1] = 0.5f * d3 - d2 + 0.6666666f; + ipoFactors[2] = -0.5f * d3 + 0.5f * d2 + 0.5f * d + 0.16666666f; + ipoFactors[3] = 0.16666666f * d3; + } else { + throw new IllegalStateException("Cannot get interpolation data for other colorband types than B-spline and Cardinal!"); + } + } + + /** + * Class to store the single colorband cursor data. + * + * @author Marcin Roguski (Kaelthas) + */ + private static class ColorBandData { + public final float r, g, b, a; + public int pos; + + public ColorBandData() { + r = g = b = 0; + a = 1; + } + + /** + * Constructor that stores the color and position of the cursor. + * @param color + * the cursor's color + * @param pos + * the cursor's position + */ + public ColorBandData(ColorRGBA color, int pos) { + r = color.r; + g = color.g; + b = color.b; + a = color.a; + this.pos = pos; + } + + /** + * Copy constructor. + */ + private ColorBandData(ColorBandData data) { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + pos = data.pos; + } + + /** + * Constructor. Loads the data from the given structure. + * + * @param cbdataStructure + * the structure containing the CBData object + */ + public ColorBandData(Structure cbdataStructure) { + r = ((Number) cbdataStructure.getFieldValue("r")).floatValue(); + g = ((Number) cbdataStructure.getFieldValue("g")).floatValue(); + b = ((Number) cbdataStructure.getFieldValue("b")).floatValue(); + a = ((Number) cbdataStructure.getFieldValue("a")).floatValue(); + pos = (int) (((Number) cbdataStructure.getFieldValue("pos")).floatValue() * 1000.0f); + } + + @Override + public String toString() { + return "P: " + pos + " [" + r + ", " + g + ", " + b + ", " + a + "]"; + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java new file mode 100644 index 000000000..344d65e31 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java @@ -0,0 +1,533 @@ +package com.jme3.scene.plugins.blender.textures; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jme3tools.converters.ImageToAwt; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialContext; +import com.jme3.scene.plugins.blender.textures.TriangulatedTexture.TriangleTextureElement; +import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType; +import com.jme3.scene.plugins.blender.textures.UVProjectionGenerator.UVProjectionType; +import com.jme3.scene.plugins.blender.textures.blending.TextureBlender; +import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.texture.TextureCubeMap; +import com.jme3.util.BufferUtils; + +/** + * This class represents a texture that is defined for the material. It can be + * made of several textures (both 2D and 3D) that are merged together and + * returned as a single texture. + * + * @author Marcin Roguski (Kaelthas) + */ +public class CombinedTexture { + private static final Logger LOGGER = Logger.getLogger(CombinedTexture.class.getName()); + + /** The mapping type of the texture. Defined bu MaterialContext.MTEX_COL, MTEX_NOR etc. */ + private final int mappingType; + /** + * If set to true then if a texture without alpha is added then all textures below are discarded because + * the new one will cover them anyway. If set to false then all textures are stored. + */ + private boolean discardCoveredTextures; + /** The data for each of the textures. */ + private List textureDatas = new ArrayList(); + /** The result texture. */ + private Texture resultTexture; + /** The UV values for the result texture. */ + private List resultUVS; + + /** + * Constructor. Stores the texture mapping type (ie. color map, normal map). + * + * @param mappingType + * texture mapping type + * @param discardCoveredTextures + * if set to true then if a texture without alpha is added then all textures below are discarded because + * the new one will cover them anyway, if set to false then all textures are stored + */ + public CombinedTexture(int mappingType, boolean discardCoveredTextures) { + this.mappingType = mappingType; + this.discardCoveredTextures = discardCoveredTextures; + } + + /** + * This method adds a texture data to the resulting texture. + * + * @param texture + * the source texture + * @param textureBlender + * the texture blender (to mix the texture with its material + * color) + * @param uvCoordinatesType + * the type of UV coordinates + * @param projectionType + * the type of UV coordinates projection (for flat textures) + * @param textureStructure + * the texture sructure + * @param uvCoordinatesName + * the name of the used user's UV coordinates for this texture + * @param blenderContext + * the blender context + */ + public void add(Texture texture, TextureBlender textureBlender, int uvCoordinatesType, int projectionType, Structure textureStructure, String uvCoordinatesName, BlenderContext blenderContext) { + if (!(texture instanceof GeneratedTexture) && !(texture instanceof Texture2D)) { + throw new IllegalArgumentException("Unsupported texture type: " + (texture == null ? "null" : texture.getClass())); + } + if (!(texture instanceof GeneratedTexture) || blenderContext.getBlenderKey().isLoadGeneratedTextures()) { + if (UVCoordinatesGenerator.isTextureCoordinateTypeSupported(UVCoordinatesType.valueOf(uvCoordinatesType))) { + TextureData textureData = new TextureData(); + textureData.texture = texture; + textureData.textureBlender = textureBlender; + textureData.uvCoordinatesType = UVCoordinatesType.valueOf(uvCoordinatesType); + textureData.projectionType = UVProjectionType.valueOf(projectionType); + textureData.textureStructure = textureStructure; + textureData.uvCoordinatesName = uvCoordinatesName; + + if (discardCoveredTextures && textureDatas.size() > 0 && this.isWithoutAlpha(textureData, blenderContext)) { + textureDatas.clear();// clear previous textures, they will be covered anyway + } + textureDatas.add(textureData); + } else { + LOGGER.warning("The texture coordinates type is not supported: " + UVCoordinatesType.valueOf(uvCoordinatesType) + ". The texture '" + textureStructure.getName() + "'."); + } + } + } + + /** + * This method flattens the texture and creates a single result of Texture2D + * type. + * + * @param geometry + * the geometry the texture is created for + * @param geometriesOMA + * the old memory address of the geometries list that the given + * geometry belongs to (needed for bounding box creation) + * @param userDefinedUVCoordinates + * the UV's defined by user (null or zero length table if none + * were defined) + * @param blenderContext + * the blender context + */ + @SuppressWarnings("unchecked") + public void flatten(Geometry geometry, Long geometriesOMA, LinkedHashMap> userDefinedUVCoordinates, BlenderContext blenderContext) { + Mesh mesh = geometry.getMesh(); + Texture previousTexture = null; + UVCoordinatesType masterUVCoordinatesType = null; + String masterUserUVSetName = null; + for (TextureData textureData : textureDatas) { + // decompress compressed textures (all will be merged into one texture anyway) + if (textureDatas.size() > 1 && textureData.texture.getImage().getFormat().isCompressed()) { + textureData.texture.setImage(ImageUtils.decompress(textureData.texture.getImage())); + textureData.textureBlender = TextureBlenderFactory.alterTextureType(textureData.texture.getImage().getFormat(), textureData.textureBlender); + } + + if (previousTexture == null) {// the first texture will lead the others to its shape + if (textureData.texture instanceof GeneratedTexture) { + resultTexture = ((GeneratedTexture) textureData.texture).triangulate(mesh, geometriesOMA, textureData.uvCoordinatesType, blenderContext); + } else if (textureData.texture instanceof Texture2D) { + resultTexture = textureData.texture; + + if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { + if (textureData.uvCoordinatesName == null) { + resultUVS = userDefinedUVCoordinates.values().iterator().next();// get the first UV available + } else { + resultUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName); + } + masterUserUVSetName = textureData.uvCoordinatesName; + } else { + List geometries = (List) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE); + resultUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries); + } + } + this.blend(resultTexture, textureData.textureBlender, blenderContext); + + previousTexture = resultTexture; + masterUVCoordinatesType = textureData.uvCoordinatesType; + } else { + if (textureData.texture instanceof GeneratedTexture) { + if (!(resultTexture instanceof TriangulatedTexture)) { + resultTexture = new TriangulatedTexture((Texture2D) resultTexture, resultUVS, blenderContext); + resultUVS = null; + previousTexture = resultTexture; + } + + TriangulatedTexture triangulatedTexture = ((GeneratedTexture) textureData.texture).triangulate(mesh, geometriesOMA, textureData.uvCoordinatesType, blenderContext); + triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext); + triangulatedTexture.blend(textureData.textureBlender, (TriangulatedTexture) resultTexture, blenderContext); + resultTexture = previousTexture = triangulatedTexture; + } else if (textureData.texture instanceof Texture2D) { + if (this.isUVTypesMatch(masterUVCoordinatesType, masterUserUVSetName, textureData.uvCoordinatesType, textureData.uvCoordinatesName) && resultTexture instanceof Texture2D) { + this.scale((Texture2D) textureData.texture, resultTexture.getImage().getWidth(), resultTexture.getImage().getHeight()); + ImageUtils.merge(resultTexture.getImage(), textureData.texture.getImage()); + previousTexture = resultTexture; + } else { + if (!(resultTexture instanceof TriangulatedTexture)) { + resultTexture = new TriangulatedTexture((Texture2D) resultTexture, resultUVS, blenderContext); + resultUVS = null; + } + // first triangulate the current texture + List textureUVS = null; + if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { + if (textureData.uvCoordinatesName == null) { + textureUVS = userDefinedUVCoordinates.values().iterator().next();// get the first UV available + } else { + textureUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName); + } + } else { + List geometries = (List) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE); + textureUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries); + } + TriangulatedTexture triangulatedTexture = new TriangulatedTexture((Texture2D) textureData.texture, textureUVS, blenderContext); + // then move the texture to different UV's + triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext); + // merge triangulated textures + for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) { + ImageUtils.merge(((TriangulatedTexture) resultTexture).getFaceTextureElement(i).image, triangulatedTexture.getFaceTextureElement(i).image); + } + } + } + } + } + + if (resultTexture instanceof TriangulatedTexture) { + if (mappingType == MaterialContext.MTEX_NOR) { + for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) { + TriangleTextureElement triangleTextureElement = ((TriangulatedTexture) resultTexture).getFaceTextureElement(i); + triangleTextureElement.image = ImageUtils.convertToNormalMapTexture(triangleTextureElement.image, 1);// TODO: get proper strength factor + } + } + resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS(); + resultTexture = ((TriangulatedTexture) resultTexture).getResultTexture(); + } + + // setting additional data + resultTexture.setWrap(WrapMode.Repeat); + // the filters are required if generated textures are used because + // otherwise ugly lines appear between the mesh faces + resultTexture.setMagFilter(MagFilter.Nearest); + resultTexture.setMinFilter(MinFilter.NearestNoMipMaps); + } + + /** + * Generates a texture that will be used by the sky spatial. + * The result texture has 6 layers. Every image in each layer has equal size and its shape is a square. + * The size of each image is the maximum size (width or height) of the textures given. + * The default sky generated texture size is used (this value is set in the BlenderKey) if no picture textures + * are present or their sizes is lower than the generated texture size. + * The textures of lower sizes are properly scaled. + * All the textures are mixed into one and put as layers in the result texture. + * + * @param horizontalColor + * the horizon color + * @param zenithColor + * the zenith color + * @param blenderContext + * the blender context + * @return texture for the sky + */ + public TextureCubeMap generateSkyTexture(ColorRGBA horizontalColor, ColorRGBA zenithColor, BlenderContext blenderContext) { + LOGGER.log(Level.FINE, "Preparing sky texture from {0} applied textures.", textureDatas.size()); + + LOGGER.fine("Computing the texture size."); + int size = -1; + for (TextureData textureData : textureDatas) { + if (textureData.texture instanceof Texture2D) { + size = Math.max(textureData.texture.getImage().getWidth(), size); + size = Math.max(textureData.texture.getImage().getHeight(), size); + } + } + if (size < 0) { + size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize(); + } + LOGGER.log(Level.FINE, "The sky texture size will be: {0}x{0}.", size); + + TextureCubeMap result = null; + for (TextureData textureData : textureDatas) { + TextureCubeMap texture = null; + if (textureData.texture instanceof GeneratedTexture) { + texture = ((GeneratedTexture) textureData.texture).generateSkyTexture(size, horizontalColor, zenithColor, blenderContext); + } else { + // first create a grayscale version of the image + Image image = textureData.texture.getImage(); + if (image.getWidth() != image.getHeight() || image.getWidth() != size) { + image = ImageUtils.resizeTo(image, size, size); + } + Image grayscaleImage = ImageUtils.convertToGrayscaleTexture(image); + + // add the sky colors to the image + PixelInputOutput sourcePixelIO = PixelIOFactory.getPixelIO(grayscaleImage.getFormat()); + PixelInputOutput targetPixelIO = PixelIOFactory.getPixelIO(image.getFormat()); + TexturePixel texturePixel = new TexturePixel(); + for (int x = 0; x < image.getWidth(); ++x) { + for (int y = 0; y < image.getHeight(); ++y) { + sourcePixelIO.read(grayscaleImage, 0, texturePixel, x, y); + texturePixel.intensity = texturePixel.red;// no matter which factor we use here, in grayscale they are all equal + ImageUtils.color(texturePixel, horizontalColor, zenithColor); + targetPixelIO.write(image, 0, texturePixel, x, y); + } + } + + // create the cubemap texture from the coloured image + ByteBuffer sourceData = image.getData(0); + ArrayList data = new ArrayList(6); + for (int i = 0; i < 6; ++i) { + data.add(BufferUtils.clone(sourceData)); + } + texture = new TextureCubeMap(new Image(image.getFormat(), image.getWidth(), image.getHeight(), 6, data)); + } + + if (result == null) { + result = texture; + } else { + ImageUtils.mix(result.getImage(), texture.getImage()); + } + } + return result; + } + + /** + * The method checks if the texture UV coordinates match. + * It the types are equal and different then UVCoordinatesType.TEXCO_UV then we consider them a match. + * If they are both UVCoordinatesType.TEXCO_UV then they match only when their UV sets names are equal. + * In other cases they are considered NOT a match. + * @param type1 + * the UV coord type + * @param uvSetName1 + * the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV) + * @param type2 + * the UV coord type + * @param uvSetName2 + * the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV) + * @return true if the types match and false otherwise + */ + private boolean isUVTypesMatch(UVCoordinatesType type1, String uvSetName1, UVCoordinatesType type2, String uvSetName2) { + if (type1 == type2) { + if (type1 == UVCoordinatesType.TEXCO_UV) { + if (uvSetName1 != null && uvSetName2 != null && uvSetName1.equals(uvSetName2)) { + return true; + } + } else { + return true; + } + } + return false; + } + + /** + * This method blends the texture. + * + * @param texture + * the texture to be blended + * @param textureBlender + * blending definition for the texture + * @param blenderContext + * the blender context + */ + private void blend(Texture texture, TextureBlender textureBlender, BlenderContext blenderContext) { + if (texture instanceof TriangulatedTexture) { + ((TriangulatedTexture) texture).blend(textureBlender, null, blenderContext); + } else if (texture instanceof Texture2D) { + Image blendedImage = textureBlender.blend(texture.getImage(), null, blenderContext); + texture.setImage(blendedImage); + } else { + throw new IllegalArgumentException("Invalid type for texture to blend!"); + } + } + + /** + * @return the result texture + */ + public Texture getResultTexture() { + return resultTexture; + } + + /** + * @return the result UV coordinates + */ + public List getResultUVS() { + return resultUVS; + } + + /** + * @return the amount of added textures + */ + public int getTexturesCount() { + return textureDatas.size(); + } + + /** + * @return the texture's mapping type + */ + public int getMappingType() { + return mappingType; + } + + /** + * @return true if the texture has at least one generated texture component and false otherwise + */ + public boolean hasGeneratedTextures() { + if (textureDatas != null) { + for (TextureData textureData : textureDatas) { + if (textureData.texture instanceof GeneratedTexture) { + return true; + } + } + } + return false; + } + + /** + * This method determines if the given texture has no alpha channel. + * + * @param texture + * the texture to check for alpha channel + * @return true if the texture has no alpha channel and false + * otherwise + */ + private boolean isWithoutAlpha(TextureData textureData, BlenderContext blenderContext) { + ColorBand colorBand = new ColorBand(textureData.textureStructure, blenderContext); + if (!colorBand.hasTransparencies()) { + int type = ((Number) textureData.textureStructure.getFieldValue("type")).intValue(); + if (type == TextureHelper.TEX_MAGIC) { + return true; + } + if (type == TextureHelper.TEX_VORONOI) { + int voronoiColorType = ((Number) textureData.textureStructure.getFieldValue("vn_coltype")).intValue(); + return voronoiColorType != 0;// voronoiColorType == 0: + // intensity, voronoiColorType + // != 0: col1, col2 or col3 + } + if (type == TextureHelper.TEX_CLOUDS) { + int sType = ((Number) textureData.textureStructure.getFieldValue("stype")).intValue(); + return sType == 1;// sType==0: without colors, sType==1: with + // colors + } + + // checking the flat textures for alpha values presence + if (type == TextureHelper.TEX_IMAGE) { + Image image = textureData.texture.getImage(); + switch (image.getFormat()) { + case BGR8: + case DXT1: + case Luminance16: + case Luminance16F: + case Luminance32F: + case Luminance8: + case RGB10: + case RGB111110F: + case RGB16: + case RGB16F: + case RGB32F: + case RGB565: + case RGB8: + return true;// these types have no alpha by definition + case ABGR8: + case DXT3: + case DXT5: + case Luminance16Alpha16: + case Luminance16FAlpha16F: + case Luminance8Alpha8: + case RGBA16: + case RGBA16F: + case RGBA32F: + case RGBA8:// with these types it is better to make sure if the texture is or is not transparent + PixelInputOutput pixelInputOutput = PixelIOFactory.getPixelIO(image.getFormat()); + TexturePixel pixel = new TexturePixel(); + int depth = image.getDepth() == 0 ? 1 : image.getDepth(); + for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { + for (int x = 0; x < image.getWidth(); ++x) { + for (int y = 0; y < image.getHeight(); ++y) { + pixelInputOutput.read(image, layerIndex, pixel, x, y); + if (pixel.alpha < 1.0f) { + return false; + } + } + } + } + return true; + } + } + } + return false; + } + + /** + * This method scales the given texture to the given size. + * + * @param texture + * the texture to be scaled + * @param width + * new width of the texture + * @param height + * new height of the texture + */ + private void scale(Texture2D texture, int width, int height) { + // first determine if scaling is required + boolean scaleRequired = texture.getImage().getWidth() != width || texture.getImage().getHeight() != height; + + if (scaleRequired) { + Image image = texture.getImage(); + BufferedImage sourceImage = ImageToAwt.convert(image, false, true, 0); + + int sourceWidth = sourceImage.getWidth(); + int sourceHeight = sourceImage.getHeight(); + + BufferedImage targetImage = new BufferedImage(width, height, sourceImage.getType()); + + Graphics2D g = targetImage.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.drawImage(sourceImage, 0, 0, width, height, 0, 0, sourceWidth, sourceHeight, null); + g.dispose(); + + Image output = new ImageLoader().load(targetImage, false); + image.setWidth(width); + image.setHeight(height); + image.setData(output.getData(0)); + image.setFormat(output.getFormat()); + } + } + + /** + * A simple class to aggregate the texture data (improves code quality). + * + * @author Marcin Roguski (Kaelthas) + */ + private static class TextureData { + /** The texture. */ + public Texture texture; + /** The texture blender (to mix the texture with its material color). */ + public TextureBlender textureBlender; + /** The type of UV coordinates. */ + public UVCoordinatesType uvCoordinatesType; + /** The type of UV coordinates projection (for flat textures). */ + public UVProjectionType projectionType; + /** The texture sructure. */ + public Structure textureStructure; + /** The name of the user's UV coordinates that are used for this texture. */ + public String uvCoordinatesName; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/DDSTexelData.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/DDSTexelData.java new file mode 100644 index 000000000..5537f1e0b --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/DDSTexelData.java @@ -0,0 +1,157 @@ +package com.jme3.scene.plugins.blender.textures; + +import com.jme3.math.FastMath; +import com.jme3.texture.Image.Format; + +/** + * The data that helps in bytes calculations for the result image. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class DDSTexelData { + /** The colors of the texes. */ + private TexturePixel[][] colors; + /** The indexes of the texels. */ + private long[] indexes; + /** The alphas of the texels (might be null). */ + private float[][] alphas; + /** The indexels of texels alpha values (might be null). */ + private long[] alphaIndexes; + /** The counter of texel x column. */ + private int xCounter; + /** The counter of texel y row. */ + private int yCounter; + /** The width of the image in pixels. */ + private int widthInPixels; + /** The height of the image in pixels. */ + private int heightInPixels; + /** The total texel count. */ + private int xTexelCount; + + /** + * Constructor. Allocates memory for data structures. + * + * @param compressedSize + * the size of compressed image (or its mipmap) + * @param widthToHeightRatio + * width/height ratio for the image + * @param format + * the format of the image + */ + public DDSTexelData(int compressedSize, float widthToHeightRatio, Format format) { + int texelsCount = compressedSize * 8 / format.getBitsPerPixel() / 16; + this.colors = new TexturePixel[texelsCount][]; + this.indexes = new long[texelsCount]; + this.widthInPixels = (int) (0.5f * (float) Math.sqrt(this.getSizeInBytes() / widthToHeightRatio)); + this.heightInPixels = (int) (this.widthInPixels / widthToHeightRatio); + this.xTexelCount = widthInPixels >> 2; + this.yCounter = (heightInPixels >> 2) - 1;// xCounter is 0 for now + if (format == Format.DXT3 || format == Format.DXT5) { + this.alphas = new float[texelsCount][]; + this.alphaIndexes = new long[texelsCount]; + } + } + + /** + * This method adds a color and indexes for a texel. + * + * @param colors + * the colors of the texel + * @param indexes + * the indexes of the texel + */ + public void add(TexturePixel[] colors, int indexes) { + this.add(colors, indexes, null, 0); + } + + /** + * This method adds a color, color indexes and alha values (with their + * indexes) for a texel. + * + * @param colors + * the colors of the texel + * @param indexes + * the indexes of the texel + * @param alphas + * the alpha values + * @param alphaIndexes + * the indexes of the given alpha values + */ + public void add(TexturePixel[] colors, int indexes, float[] alphas, long alphaIndexes) { + int index = yCounter * xTexelCount + xCounter; + this.colors[index] = colors; + this.indexes[index] = indexes; + if (alphas != null) { + this.alphas[index] = alphas; + this.alphaIndexes[index] = alphaIndexes; + } + ++this.xCounter; + if (this.xCounter >= this.xTexelCount) { + this.xCounter = 0; + --this.yCounter; + } + } + + /** + * This method returns the values of the pixel located on the given + * coordinates on the result image. + * + * @param x + * the x coordinate of the pixel + * @param y + * the y coordinate of the pixel + * @param result + * the table where the result is stored + * @return true if the pixel was correctly read and false if + * the position was outside the image sizes + */ + public boolean getRGBA8(int x, int y, byte[] result) { + int xTexetlIndex = x % widthInPixels / 4; + int yTexelIndex = y % heightInPixels / 4; + + int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex; + if (texelIndex < colors.length) { + TexturePixel[] colors = this.colors[texelIndex]; + + // coordinates of the pixel in the selected texel + x = x - 4 * xTexetlIndex;// pixels are arranged from left to right + y = 3 - y - 4 * yTexelIndex;// pixels are arranged from bottom to top (that is why '3 - ...' is at the start) + + int pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2); + int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0; + + // getting the pixel + int indexMask = colors.length - 1; + int colorIndex = (int) (this.indexes[texelIndex] >> pixelIndexInTexel & indexMask); + float alpha = this.alphas != null ? this.alphas[texelIndex][(int) (this.alphaIndexes[texelIndex] >> alphaIndexInTexel & 0x07)] : colors[colorIndex].alpha; + result[0] = (byte) (colors[colorIndex].red * 255.0f); + result[1] = (byte) (colors[colorIndex].green * 255.0f); + result[2] = (byte) (colors[colorIndex].blue * 255.0f); + result[3] = (byte) (alpha * 255.0f); + return true; + } + return false; + } + + /** + * @return the size of the decompressed texel (in bytes) + */ + public int getSizeInBytes() { + // indexes.length == count of texels + return indexes.length * 16 * 4; + } + + /** + * @return image (mipmap) width + */ + public int getPixelWidth() { + return widthInPixels; + } + + /** + * @return image (mipmap) height + */ + public int getPixelHeight() { + return heightInPixels; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java new file mode 100644 index 000000000..8202953f2 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java @@ -0,0 +1,282 @@ +package com.jme3.scene.plugins.blender.textures; + +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import com.jme3.bounding.BoundingBox; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.TriangulatedTexture.TriangleTextureElement; +import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType; +import com.jme3.scene.plugins.blender.textures.generating.TextureGenerator; +import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.TextureCubeMap; +import com.jme3.util.TempVars; + +/** + * The generated texture loaded from blender file. The texture is not generated + * after being read. This class rather stores all required data and can compute + * a pixel in the required 3D space position. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class GeneratedTexture extends Texture { + private static final int POSITIVE_X = 0; + private static final int NEGATIVE_X = 1; + private static final int POSITIVE_Y = 2; + private static final int NEGATIVE_Y = 3; + private static final int POSITIVE_Z = 4; + private static final int NEGATIVE_Z = 5; + + // flag values + public static final int TEX_COLORBAND = 1; + public static final int TEX_FLIPBLEND = 2; + public static final int TEX_NEGALPHA = 4; + public static final int TEX_CHECKER_ODD = 8; + public static final int TEX_CHECKER_EVEN = 16; + public static final int TEX_PRV_ALPHA = 32; + public static final int TEX_PRV_NOR = 64; + public static final int TEX_REPEAT_XMIR = 128; + public static final int TEX_REPEAT_YMIR = 256; + public static final int TEX_FLAG_MASK = TEX_COLORBAND | TEX_FLIPBLEND | TEX_NEGALPHA | TEX_CHECKER_ODD | TEX_CHECKER_EVEN | TEX_PRV_ALPHA | TEX_PRV_NOR | TEX_REPEAT_XMIR | TEX_REPEAT_YMIR; + + /** Material-texture link structure. */ + private final Structure mTex; + /** Texture generateo for the specified texture type. */ + private final TextureGenerator textureGenerator; + /** + * The generated texture cast functions. They are used to cas a given point on a plane to a specified shape in 3D space. + * The functions should be ordered as the ordinal of a BlenderKey.CastFunction enums. + */ + private final static CastFunction[] CAST_FUNCTIONS = new CastFunction[] { + /** + * The cube casting function (does nothing except scaling if needed because the given points are already on a cube). + */ + new CastFunction() { + @Override + public void cast(Vector3f pointToCast, float radius) { + //computed using the Thales' theorem + float length = 2 * pointToCast.subtractLocal(0.5f, 0.5f, 0.5f).length() * radius; + pointToCast.normalizeLocal().addLocal(0.5f, 0.5f, 0.5f).multLocal(length); + } + }, + + /** + * The sphere casting function. + */ + new CastFunction() { + /** + * The method casts a point on a plane to a sphere. + * The plane is one of the faces of a cube that has a edge of length 1 and center in (0.5 0.5, 0.5). This cube is a basic 3d area where generated texture + * is created. + * To cast a point on a cube face to a sphere that is inside the cube we perform several easy vector operations. + * 1. create a vector from the cube's center to the point + * 2. setting its length to 0.5 (the radius of the sphere) + * 3. adding the value of the cube's center to get a point on the sphere + * + * The result is stored in the given vector. + * + * @param pointToCast + * the point on a plane that will be cast to a sphere + * @param radius + * the radius of the sphere + */ + @Override + public void cast(Vector3f pointToCast, float radius) { + pointToCast.subtractLocal(0.5f, 0.5f, 0.5f).normalizeLocal().multLocal(radius).addLocal(0.5f, 0.5f, 0.5f); + } + } + }; + + /** + * Constructor. Reads the required data from the 'tex' structure. + * + * @param tex + * the texture structure + * @param mTex + * the material-texture link data structure + * @param textureGenerator + * the generator for the required texture type + * @param blenderContext + * the blender context + */ + public GeneratedTexture(Structure tex, Structure mTex, TextureGenerator textureGenerator, BlenderContext blenderContext) { + this.mTex = mTex; + this.textureGenerator = textureGenerator; + this.textureGenerator.readData(tex, blenderContext); + super.setImage(new GeneratedTextureImage(textureGenerator.getImageFormat())); + } + + /** + * This method computes the textyre color/intensity at the specified (u, v, + * s) position in 3D space. + * + * @param pixel + * the pixel where the result is stored + * @param u + * the U factor + * @param v + * the V factor + * @param s + * the S factor + */ + public void getPixel(TexturePixel pixel, float u, float v, float s) { + textureGenerator.getPixel(pixel, u, v, s); + } + + /** + * This method triangulates the texture. In the result we get a set of small + * flat textures for each face of the given mesh. This can be later merged + * into one flat texture. + * + * @param mesh + * the mesh we create the texture for + * @param geometriesOMA + * the old memory address of the geometries group that the given + * mesh belongs to (required for bounding box calculations) + * @param coordinatesType + * the types of UV coordinates + * @param blenderContext + * the blender context + * @return triangulated texture + */ + @SuppressWarnings("unchecked") + public TriangulatedTexture triangulate(Mesh mesh, Long geometriesOMA, UVCoordinatesType coordinatesType, BlenderContext blenderContext) { + List geometries = (List) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE); + + int[] coordinatesSwappingIndexes = new int[] { ((Number) mTex.getFieldValue("projx")).intValue(), ((Number) mTex.getFieldValue("projy")).intValue(), ((Number) mTex.getFieldValue("projz")).intValue() }; + List uvs = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mesh, coordinatesType, coordinatesSwappingIndexes, geometries); + Vector3f[] uvsArray = uvs.toArray(new Vector3f[uvs.size()]); + BoundingBox boundingBox = UVCoordinatesGenerator.getBoundingBox(geometries); + Set triangleTextureElements = new TreeSet(new Comparator() { + public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { + return o1.faceIndex - o2.faceIndex; + } + }); + int[] indices = new int[3]; + for (int i = 0; i < mesh.getTriangleCount(); ++i) { + mesh.getTriangle(i, indices); + triangleTextureElements.add(new TriangleTextureElement(i, boundingBox, this, uvsArray, indices, blenderContext)); + } + return new TriangulatedTexture(triangleTextureElements, blenderContext); + } + + /** + * Creates a texture for the sky. The result texture has 6 layers. + * @param size + * the size of the texture (width and height are equal) + * @param horizontalColor + * the horizon color + * @param zenithColor + * the zenith color + * @param blenderContext + * the blender context + * @return the sky texture + */ + public TextureCubeMap generateSkyTexture(int size, ColorRGBA horizontalColor, ColorRGBA zenithColor, BlenderContext blenderContext) { + Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6); + PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat()); + TexturePixel pixel = new TexturePixel(); + + float delta = 1 / (float) (size - 1); + float sideV, sideS = 1, forwardU = 1, forwardV, upS; + TempVars tempVars = TempVars.get(); + CastFunction castFunction = CAST_FUNCTIONS[blenderContext.getBlenderKey().getSkyGeneratedTextureShape().ordinal()]; + float castRadius = blenderContext.getBlenderKey().getSkyGeneratedTextureRadius(); + + for (int x = 0; x < size; ++x) { + sideV = 1; + forwardV = 1; + upS = 0; + for (int y = 0; y < size; ++y) { + castFunction.cast(tempVars.vect1.set(1, sideV, sideS), castRadius); + textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); + pixelIO.write(image, NEGATIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// right + + castFunction.cast(tempVars.vect1.set(0, sideV, 1 - sideS), castRadius); + textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); + pixelIO.write(image, POSITIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// left + + castFunction.cast(tempVars.vect1.set(forwardU, forwardV, 0), castRadius); + textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); + pixelIO.write(image, POSITIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// front + + castFunction.cast(tempVars.vect1.set(1 - forwardU, forwardV, 1), castRadius); + textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); + pixelIO.write(image, NEGATIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// back + + castFunction.cast(tempVars.vect1.set(forwardU, 0, upS), castRadius); + textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); + pixelIO.write(image, NEGATIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// top + + castFunction.cast(tempVars.vect1.set(forwardU, 1, 1 - upS), castRadius); + textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); + pixelIO.write(image, POSITIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// bottom + + sideV = FastMath.clamp(sideV - delta, 0, 1); + forwardV = FastMath.clamp(forwardV - delta, 0, 1); + upS = FastMath.clamp(upS + delta, 0, 1); + } + sideS = FastMath.clamp(sideS - delta, 0, 1); + forwardU = FastMath.clamp(forwardU - delta, 0, 1); + } + tempVars.release(); + + return new TextureCubeMap(image); + } + + @Override + public void setWrap(WrapAxis axis, WrapMode mode) { + } + + @Override + public void setWrap(WrapMode mode) { + } + + @Override + public WrapMode getWrap(WrapAxis axis) { + return null; + } + + @Override + public Type getType() { + return Type.ThreeDimensional; + } + + @Override + public Texture createSimpleClone() { + return null; + } + + /** + * Private class to give the format of the 'virtual' 3D texture image. + * + * @author Marcin Roguski (Kaelthas) + */ + private static class GeneratedTextureImage extends Image { + public GeneratedTextureImage(Format imageFormat) { + super.format = imageFormat; + } + } + + /** + * The casting functions to create a sky generated texture against selected shape of a selected size. + * + * @author Marcin Roguski (Kaelthas) + */ + private static interface CastFunction { + void cast(Vector3f pointToCast, float radius); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageLoader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageLoader.java new file mode 100644 index 000000000..ccb21bdf9 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageLoader.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures; + +import com.jme3.scene.plugins.blender.file.BlenderInputStream; +import com.jme3.texture.Image; +import com.jme3.texture.plugins.AWTLoader; +import com.jme3.texture.plugins.DDSLoader; +import com.jme3.texture.plugins.TGALoader; +import java.io.InputStream; +import java.util.logging.Logger; + +/** + * An image loader class. It uses three loaders (AWTLoader, TGALoader and DDSLoader) in an attempt to load the image from the given + * input stream. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class ImageLoader extends AWTLoader { + private static final Logger LOGGER = Logger.getLogger(ImageLoader.class.getName()); + + protected DDSLoader ddsLoader = new DDSLoader(); // DirectX image loader + + /** + * This method loads the image from the blender file itself. It tries each loader to load the image. + * + * @param inputStream + * blender input stream + * @param startPosition + * position in the stream where the image data starts + * @param flipY + * if the image should be flipped (does not work with DirectX image) + * @return loaded image or null if it could not be loaded + */ + public Image loadImage(BlenderInputStream inputStream, int startPosition, boolean flipY) { + // loading using AWT loader + inputStream.setPosition(startPosition); + Image result = this.loadImage(inputStream, ImageType.AWT, flipY); + // loading using TGA loader + if (result == null) { + inputStream.setPosition(startPosition); + result = this.loadImage(inputStream, ImageType.TGA, flipY); + } + // loading using DDS loader + if (result == null) { + inputStream.setPosition(startPosition); + result = this.loadImage(inputStream, ImageType.DDS, flipY); + } + + if (result == null) { + LOGGER.warning("Image could not be loaded by none of available loaders!"); + } + + return result; + } + + /** + * This method loads an image of a specified type from the given input stream. + * + * @param inputStream + * the input stream we read the image from + * @param imageType + * the type of the image {@link ImageType} + * @param flipY + * if the image should be flipped (does not work with DirectX image) + * @return loaded image or null if it could not be loaded + */ + public Image loadImage(InputStream inputStream, ImageType imageType, boolean flipY) { + Image result = null; + switch (imageType) { + case AWT: + try { + result = this.load(inputStream, flipY); + } catch (Exception e) { + LOGGER.warning("Unable to load image using AWT loader!"); + } + break; + case DDS: + try { + result = ddsLoader.load(inputStream); + } catch (Exception e) { + LOGGER.warning("Unable to load image using DDS loader!"); + } + break; + case TGA: + try { + result = TGALoader.load(inputStream, flipY); + } catch (Exception e) { + LOGGER.warning("Unable to load image using TGA loader!"); + } + break; + default: + throw new IllegalStateException("Unknown image type: " + imageType); + } + return result; + } + + /** + * Image types that can be loaded. AWT: png, jpg, jped or bmp TGA: tga DDS: DirectX image files + * + * @author Marcin Roguski (Kaelthas) + */ + private static enum ImageType { + AWT, TGA, DDS; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageUtils.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageUtils.java new file mode 100644 index 000000000..66740769d --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageUtils.java @@ -0,0 +1,472 @@ +package com.jme3.scene.plugins.blender.textures; + +import java.awt.color.ColorSpace; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.awt.image.ColorConvertOp; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import jme3tools.converters.ImageToAwt; +import jme3tools.converters.RGB565; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; + +/** + * This utility class has the methods that deal with images. + * + * @author Marcin Roguski (Kaelthas) + */ +public final class ImageUtils { + /** + * Creates an image of the given size and depth. + * @param format + * the image format + * @param width + * the image width + * @param height + * the image height + * @param depth + * the image depth + * @return the new image instance + */ + public static Image createEmptyImage(Format format, int width, int height, int depth) { + int bufferSize = width * height * (format.getBitsPerPixel() >> 3); + if (depth < 2) { + return new Image(format, width, height, BufferUtils.createByteBuffer(bufferSize)); + } + ArrayList data = new ArrayList(depth); + for (int i = 0; i < depth; ++i) { + data.add(BufferUtils.createByteBuffer(bufferSize)); + } + return new Image(Format.RGB8, width, height, depth, data); + } + + /** + * The method sets a color for the given pixel by merging the two given colors. + * The lowIntensityColor will be most visible when the pixel has low intensity. + * The highIntensityColor will be most visible when the pixel has high intensity. + * + * @param pixel + * the pixel that will have the colors altered + * @param lowIntensityColor + * the low intensity color + * @param highIntensityColor + * the high intensity color + * @return the altered pixel (the same instance) + */ + public static TexturePixel color(TexturePixel pixel, ColorRGBA lowIntensityColor, ColorRGBA highIntensityColor) { + float intensity = pixel.intensity; + pixel.fromColor(lowIntensityColor); + pixel.mult(1 - pixel.intensity); + pixel.add(highIntensityColor.mult(intensity)); + return pixel; + } + + /** + * This method merges two given images. The result is stored in the + * 'target' image. + * + * @param targetImage + * the target image + * @param sourceImage + * the source image + */ + public static void merge(Image targetImage, Image sourceImage) { + if (sourceImage.getDepth() != targetImage.getDepth()) { + throw new IllegalArgumentException("The given images should have the same depth to merge them!"); + } + if (sourceImage.getWidth() != targetImage.getWidth()) { + throw new IllegalArgumentException("The given images should have the same width to merge them!"); + } + if (sourceImage.getHeight() != targetImage.getHeight()) { + throw new IllegalArgumentException("The given images should have the same height to merge them!"); + } + + PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat()); + PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat()); + TexturePixel sourcePixel = new TexturePixel(); + TexturePixel targetPixel = new TexturePixel(); + int depth = targetImage.getDepth() == 0 ? 1 : targetImage.getDepth(); + + for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { + for (int x = 0; x < sourceImage.getWidth(); ++x) { + for (int y = 0; y < sourceImage.getHeight(); ++y) { + sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y); + targetIO.read(targetImage, layerIndex, targetPixel, x, y); + targetPixel.merge(sourcePixel); + targetIO.write(targetImage, layerIndex, targetPixel, x, y); + } + } + } + } + + /** + * This method merges two given images. The result is stored in the + * 'target' image. + * + * @param targetImage + * the target image + * @param sourceImage + * the source image + */ + public static void mix(Image targetImage, Image sourceImage) { + if (sourceImage.getDepth() != targetImage.getDepth()) { + throw new IllegalArgumentException("The given images should have the same depth to merge them!"); + } + if (sourceImage.getWidth() != targetImage.getWidth()) { + throw new IllegalArgumentException("The given images should have the same width to merge them!"); + } + if (sourceImage.getHeight() != targetImage.getHeight()) { + throw new IllegalArgumentException("The given images should have the same height to merge them!"); + } + + PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat()); + PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat()); + TexturePixel sourcePixel = new TexturePixel(); + TexturePixel targetPixel = new TexturePixel(); + int depth = targetImage.getDepth() == 0 ? 1 : targetImage.getDepth(); + + for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { + for (int x = 0; x < sourceImage.getWidth(); ++x) { + for (int y = 0; y < sourceImage.getHeight(); ++y) { + sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y); + targetIO.read(targetImage, layerIndex, targetPixel, x, y); + targetPixel.mix(sourcePixel); + targetIO.write(targetImage, layerIndex, targetPixel, x, y); + } + } + } + } + + /** + * Resizes the image to the given width and height. + * @param source + * the source image (this remains untouched, the new image instance is created) + * @param width + * the target image width + * @param height + * the target image height + * @return the resized image + */ + public static Image resizeTo(Image source, int width, int height) { + BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0); + + double scaleX = width / (double) sourceImage.getWidth(); + double scaleY = height / (double) sourceImage.getHeight(); + AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY); + AffineTransformOp bilinearScaleOp = new AffineTransformOp(scaleTransform, AffineTransformOp.TYPE_BILINEAR); + + BufferedImage scaledImage = bilinearScaleOp.filter(sourceImage, new BufferedImage(width, height, sourceImage.getType())); + return ImageUtils.toJmeImage(scaledImage, source.getFormat()); + } + + /** + * This method converts the given texture into normal-map texture. + * + * @param source + * the source texture + * @param strengthFactor + * the normal strength factor + * @return normal-map texture + */ + public static Image convertToNormalMapTexture(Image source, float strengthFactor) { + BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0); + + BufferedImage heightMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); + BufferedImage bumpMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); + ColorConvertOp gscale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); + gscale.filter(sourceImage, heightMap); + + Vector3f S = new Vector3f(); + Vector3f T = new Vector3f(); + Vector3f N = new Vector3f(); + + for (int x = 0; x < bumpMap.getWidth(); ++x) { + for (int y = 0; y < bumpMap.getHeight(); ++y) { + // generating bump pixel + S.x = 1; + S.y = 0; + S.z = strengthFactor * ImageUtils.getHeight(heightMap, x + 1, y) - strengthFactor * ImageUtils.getHeight(heightMap, x - 1, y); + T.x = 0; + T.y = 1; + T.z = strengthFactor * ImageUtils.getHeight(heightMap, x, y + 1) - strengthFactor * ImageUtils.getHeight(heightMap, x, y - 1); + + float den = (float) Math.sqrt(S.z * S.z + T.z * T.z + 1); + N.x = -S.z; + N.y = -T.z; + N.z = 1; + N.divideLocal(den); + + // setting thge pixel in the result image + bumpMap.setRGB(x, y, ImageUtils.vectorToColor(N.x, N.y, N.z)); + } + } + return ImageUtils.toJmeImage(bumpMap, source.getFormat()); + } + + /** + * This method converts the given texture into black and whit (grayscale) texture. + * + * @param source + * the source texture + * @return grayscale texture + */ + public static Image convertToGrayscaleTexture(Image source) { + BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0); + ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); + op.filter(sourceImage, sourceImage); + return ImageUtils.toJmeImage(sourceImage, source.getFormat()); + } + + /** + * This method decompresses the given image. If the given image is already + * decompressed nothing happens and it is simply returned. + * + * @param image + * the image to decompress + * @return the decompressed image + */ + public static Image decompress(Image image) { + Format format = image.getFormat(); + int depth = image.getDepth(); + if (depth == 0) { + depth = 1; + } + ArrayList dataArray = new ArrayList(depth); + int[] sizes = image.getMipMapSizes() != null ? image.getMipMapSizes() : new int[1]; + int[] newMipmapSizes = image.getMipMapSizes() != null ? new int[image.getMipMapSizes().length] : null; + + for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { + ByteBuffer data = image.getData(dataLayerIndex); + data.rewind(); + if (sizes.length == 1) { + sizes[0] = data.remaining(); + } + float widthToHeightRatio = image.getWidth() / image.getHeight();// this should always be constant for each mipmap + List texelDataList = new ArrayList(sizes.length); + int maxPosition = 0, resultSize = 0; + + for (int sizeIndex = 0; sizeIndex < sizes.length; ++sizeIndex) { + maxPosition += sizes[sizeIndex]; + DDSTexelData texelData = new DDSTexelData(sizes[sizeIndex], widthToHeightRatio, format); + texelDataList.add(texelData); + switch (format) { + case DXT1:// BC1 + case DXT1A: + while (data.position() < maxPosition) { + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + if (col0 > col1) { + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + } else { + // creating color2 = 1/2color0 + 1/2color1 + colors[2].fromPixel(colors[0]); + colors[2].add(colors[1]); + colors[2].mult(0.5f); + + colors[3].fromARGB8(0); + } + int indexes = data.getInt();// 4-byte table with color indexes in decompressed table + texelData.add(colors, indexes); + } + break; + case DXT3:// BC2 + while (data.position() < maxPosition) { + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; + long alpha = data.getLong(); + float[] alphas = new float[16]; + long alphasIndex = 0; + for (int i = 0; i < 16; ++i) { + alphasIndex |= i << i * 4; + byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4); + alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; + } + + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + + int indexes = data.getInt();// 4-byte table with color indexes in decompressed table + texelData.add(colors, indexes, alphas, alphasIndex); + } + break; + case DXT5:// BC3 + float[] alphas = new float[8]; + while (data.position() < maxPosition) { + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; + alphas[0] = data.get() * 255.0f; + alphas[1] = data.get() * 255.0f; + //the casts to long must be done here because otherwise 32-bit integers would be shifetd by 32 and 40 bits which would result in improper values + long alphaIndices = (long)data.get() | (long)data.get() << 8 | (long)data.get() << 16 | (long)data.get() << 24 | (long)data.get() << 32 | (long)data.get() << 40; + if (alphas[0] > alphas[1]) {// 6 interpolated alpha values. + alphas[2] = (6 * alphas[0] + alphas[1]) / 7; + alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7; + alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7; + alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7; + alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7; + alphas[7] = (alphas[0] + 6 * alphas[1]) / 7; + } else { + alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f; + alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f; + alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f; + alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f; + alphas[6] = 0; + alphas[7] = 1; + } + + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + + int indexes = data.getInt();// 4-byte table with color indexes in decompressed table + texelData.add(colors, indexes, alphas, alphaIndices); + } + break; + default: + throw new IllegalStateException("Unknown compressed format: " + format); + } + newMipmapSizes[sizeIndex] = texelData.getSizeInBytes(); + resultSize += texelData.getSizeInBytes(); + } + byte[] bytes = new byte[resultSize]; + int offset = 0; + byte[] pixelBytes = new byte[4]; + for (DDSTexelData texelData : texelDataList) { + for (int i = 0; i < texelData.getPixelWidth(); ++i) { + for (int j = 0; j < texelData.getPixelHeight(); ++j) { + if (texelData.getRGBA8(i, j, pixelBytes)) { + bytes[offset + (j * texelData.getPixelWidth() + i) * 4] = pixelBytes[0]; + bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 1] = pixelBytes[1]; + bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 2] = pixelBytes[2]; + bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 3] = pixelBytes[3]; + } else { + break; + } + } + } + offset += texelData.getSizeInBytes(); + } + dataArray.add(BufferUtils.createByteBuffer(bytes)); + } + + Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) : new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0)); + if (newMipmapSizes != null) { + result.setMipMapSizes(newMipmapSizes); + } + return result; + } + + /** + * This method returns the height represented by the specified pixel in the + * given texture. The given texture should be a height-map. + * + * @param image + * the height-map texture + * @param x + * pixel's X coordinate + * @param y + * pixel's Y coordinate + * @return height reprezented by the given texture in the specified location + */ + private static int getHeight(BufferedImage image, int x, int y) { + if (x < 0) { + x = 0; + } else if (x >= image.getWidth()) { + x = image.getWidth() - 1; + } + if (y < 0) { + y = 0; + } else if (y >= image.getHeight()) { + y = image.getHeight() - 1; + } + return image.getRGB(x, y) & 0xff; + } + + /** + * This method transforms given vector's coordinates into ARGB color (A is + * always = 255). + * + * @param x + * X factor of the vector + * @param y + * Y factor of the vector + * @param z + * Z factor of the vector + * @return color representation of the given vector + */ + private static int vectorToColor(float x, float y, float z) { + int r = Math.round(255 * (x + 1f) / 2f); + int g = Math.round(255 * (y + 1f) / 2f); + int b = Math.round(255 * (z + 1f) / 2f); + return (255 << 24) + (r << 16) + (g << 8) + b; + } + + /** + * Converts java awt image to jme image. + * @param bufferedImage + * the java awt image + * @param format + * the result image format + * @return the jme image + */ + private static Image toJmeImage(BufferedImage bufferedImage, Format format) { + ByteBuffer byteBuffer = BufferUtils.createByteBuffer(bufferedImage.getWidth() * bufferedImage.getHeight() * 3); + ImageToAwt.convert(bufferedImage, format, byteBuffer); + return new Image(format, bufferedImage.getWidth(), bufferedImage.getHeight(), byteBuffer); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java new file mode 100644 index 000000000..f7a6aad3a --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java @@ -0,0 +1,642 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures; + +import java.awt.geom.AffineTransform; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.asset.BlenderKey; +import com.jme3.asset.GeneratedTextureKey; +import com.jme3.asset.TextureKey; +import com.jme3.math.Vector2f; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialContext; +import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType; +import com.jme3.scene.plugins.blender.textures.blending.TextureBlender; +import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory; +import com.jme3.scene.plugins.blender.textures.generating.TextureGeneratorFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; + +/** + * A class that is used in texture calculations. + * + * @author Marcin Roguski + */ +public class TextureHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(TextureHelper.class.getName()); + + // texture types + public static final int TEX_NONE = 0; + public static final int TEX_CLOUDS = 1; + public static final int TEX_WOOD = 2; + public static final int TEX_MARBLE = 3; + public static final int TEX_MAGIC = 4; + public static final int TEX_BLEND = 5; + public static final int TEX_STUCCI = 6; + public static final int TEX_NOISE = 7; + public static final int TEX_IMAGE = 8; + public static final int TEX_PLUGIN = 9; + public static final int TEX_ENVMAP = 10; + public static final int TEX_MUSGRAVE = 11; + public static final int TEX_VORONOI = 12; + public static final int TEX_DISTNOISE = 13; + public static final int TEX_POINTDENSITY = 14; // v. 25+ + public static final int TEX_VOXELDATA = 15; // v. 25+ + public static final int TEX_OCEAN = 16; // v. 26+ + + public static final Type[] TEXCOORD_TYPES = new Type[] { Type.TexCoord, Type.TexCoord2, Type.TexCoord3, Type.TexCoord4, Type.TexCoord5, Type.TexCoord6, Type.TexCoord7, Type.TexCoord8 }; + + private TextureGeneratorFactory textureGeneratorFactory = new TextureGeneratorFactory(); + + /** + * This constructor parses the given blender version and stores the result. + * It creates noise generator and texture generators. + * + * @param blenderVersion + * the version read from the blend file + * @param blenderContext + * the blender context + */ + public TextureHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + /** + * This class returns a texture read from the file or from packed blender + * data. The returned texture has the name set to the value of its blender + * type. + * + * @param tex + * texture structure filled with data + * @param blenderContext + * the blender context + * @return the texture that can be used by JME engine + * @throws BlenderFileException + * this exception is thrown when the blend file structure is + * somehow invalid or corrupted + */ + public Texture getTexture(Structure tex, Structure mTex, BlenderContext blenderContext) throws BlenderFileException { + Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (result != null) { + return result; + } + int type = ((Number) tex.getFieldValue("type")).intValue(); + int imaflag = ((Number) tex.getFieldValue("imaflag")).intValue(); + + switch (type) { + case TEX_IMAGE:// (it is first because probably this will be most commonly used) + Pointer pImage = (Pointer) tex.getFieldValue("ima"); + if (pImage.isNotNull()) { + Structure image = pImage.fetchData().get(0); + Texture loadedTexture = this.loadTexture(image, imaflag, blenderContext); + if (loadedTexture != null) { + result = loadedTexture; + this.applyColorbandAndColorFactors(tex, result.getImage(), blenderContext); + } + } + break; + case TEX_CLOUDS: + case TEX_WOOD: + case TEX_MARBLE: + case TEX_MAGIC: + case TEX_BLEND: + case TEX_STUCCI: + case TEX_NOISE: + case TEX_MUSGRAVE: + case TEX_VORONOI: + case TEX_DISTNOISE: + result = new GeneratedTexture(tex, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext); + break; + case TEX_NONE:// No texture, do nothing + break; + case TEX_POINTDENSITY: + case TEX_VOXELDATA: + case TEX_PLUGIN: + case TEX_ENVMAP: + case TEX_OCEAN: + LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, tex.getName() }); + break; + default: + throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + tex.getName()); + } + if (result != null) { + result.setName(tex.getName()); + result.setWrap(WrapMode.Repeat); + + // decide if the mipmaps will be generated + switch (blenderContext.getBlenderKey().getMipmapGenerationMethod()) { + case ALWAYS_GENERATE: + result.setMinFilter(MinFilter.Trilinear); + break; + case NEVER_GENERATE: + break; + case GENERATE_WHEN_NEEDED: + if ((imaflag & 0x04) != 0) { + result.setMinFilter(MinFilter.Trilinear); + } + break; + default: + throw new IllegalStateException("Unknown mipmap generation method: " + blenderContext.getBlenderKey().getMipmapGenerationMethod()); + } + + if (type != TEX_IMAGE) {// only generated textures should have this key + result.setKey(new GeneratedTextureKey(tex.getName())); + } + + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), tex.getOldMemoryAddress() }); + } + blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), tex.getName(), tex, result); + } + return result; + } + + /** + * This class returns a texture read from the file or from packed blender + * data. + * + * @param imageStructure + * image structure filled with data + * @param imaflag + * the image flag + * @param blenderContext + * the blender context + * @return the texture that can be used by JME engine + * @throws BlenderFileException + * this exception is thrown when the blend file structure is + * somehow invalid or corrupted + */ + protected Texture loadTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", imageStructure.getOldMemoryAddress()); + Texture result = null; + Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (im == null) { + String texturePath = imageStructure.getFieldValue("name").toString(); + Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile"); + if (pPackedFile.isNull()) { + LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath); + result = this.loadImageFromFile(texturePath, imaflag, blenderContext); + } else { + LOGGER.fine("Packed texture. Reading directly from the blend file!"); + Structure packedFile = pPackedFile.fetchData().get(0); + Pointer pData = (Pointer) packedFile.getFieldValue("data"); + FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress()); + blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition()); + ImageLoader imageLoader = new ImageLoader(); + + // Should the texture be flipped? It works for sinbad .. + result = new Texture2D(imageLoader.loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true)); + } + } else { + result = new Texture2D(im); + } + return result; + } + + /** + * This method creates the affine transform that is used to transform a + * triangle defined by one UV coordinates into a triangle defined by + * different UV's. + * + * @param source + * source UV coordinates + * @param dest + * target UV coordinates + * @param sourceSize + * the width and height of the source image + * @param targetSize + * the width and height of the target image + * @return affine transform to transform one triangle to another + */ + public AffineTransform createAffineTransform(Vector2f[] source, Vector2f[] dest, int[] sourceSize, int[] targetSize) { + float x11 = source[0].getX() * sourceSize[0]; + float x12 = source[0].getY() * sourceSize[1]; + float x21 = source[1].getX() * sourceSize[0]; + float x22 = source[1].getY() * sourceSize[1]; + float x31 = source[2].getX() * sourceSize[0]; + float x32 = source[2].getY() * sourceSize[1]; + float y11 = dest[0].getX() * targetSize[0]; + float y12 = dest[0].getY() * targetSize[1]; + float y21 = dest[1].getX() * targetSize[0]; + float y22 = dest[1].getY() * targetSize[1]; + float y31 = dest[2].getX() * targetSize[0]; + float y32 = dest[2].getY() * targetSize[1]; + + float a1 = ((y11 - y21) * (x12 - x32) - (y11 - y31) * (x12 - x22)) / ((x11 - x21) * (x12 - x32) - (x11 - x31) * (x12 - x22)); + float a2 = ((y11 - y21) * (x11 - x31) - (y11 - y31) * (x11 - x21)) / ((x12 - x22) * (x11 - x31) - (x12 - x32) * (x11 - x21)); + float a3 = y11 - a1 * x11 - a2 * x12; + float a4 = ((y12 - y22) * (x12 - x32) - (y12 - y32) * (x12 - x22)) / ((x11 - x21) * (x12 - x32) - (x11 - x31) * (x12 - x22)); + float a5 = ((y12 - y22) * (x11 - x31) - (y12 - y32) * (x11 - x21)) / ((x12 - x22) * (x11 - x31) - (x12 - x32) * (x11 - x21)); + float a6 = y12 - a4 * x11 - a5 * x12; + return new AffineTransform(a1, a4, a2, a5, a3, a6); + } + + /** + * This method returns the proper pixel position on the image. + * + * @param pos + * the relative position (value of range <0, 1> (both inclusive)) + * @param size + * the size of the line the pixel lies on (width, heigth or + * depth) + * @return the integer index of the pixel on the line of the specified width + */ + public int getPixelPosition(float pos, int size) { + float pixelWidth = 1 / (float) size; + pos *= size; + int result = (int) pos; + // here is where we repair floating point operations errors :) + if (Math.abs(result - pos) > pixelWidth) { + ++result; + } + return result; + } + + /** + * This method returns subimage of the give image. The subimage is + * constrained by the rectangle coordinates. The source image is unchanged. + * + * @param image + * the image to be subimaged + * @param minX + * minimum X position + * @param minY + * minimum Y position + * @param maxX + * maximum X position + * @param maxY + * maximum Y position + * @return a part of the given image + */ + public Image getSubimage(Image image, int minX, int minY, int maxX, int maxY) { + if (minY > maxY) { + throw new IllegalArgumentException("Minimum Y value is higher than maximum Y value!"); + } + if (minX > maxX) { + throw new IllegalArgumentException("Minimum Y value is higher than maximum Y value!"); + } + if (image.getData().size() > 1) { + throw new IllegalArgumentException("Only flat images are allowed for subimage operation!"); + } + if (image.getMipMapSizes() != null) { + LOGGER.warning("Subimaging image with mipmaps is not yet supported!"); + } + + int width = maxX - minX; + int height = maxY - minY; + ByteBuffer data = BufferUtils.createByteBuffer(width * height * (image.getFormat().getBitsPerPixel() >> 3)); + + Image result = new Image(image.getFormat(), width, height, data); + PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat()); + TexturePixel pixel = new TexturePixel(); + + for (int x = minX; x < maxX; ++x) { + for (int y = minY; y < maxY; ++y) { + pixelIO.read(image, 0, pixel, x, y); + pixelIO.write(result, 0, pixel, x - minX, y - minY); + } + } + return result; + } + + /** + * This method applies the colorband and color factors to image type + * textures. If there is no colorband defined for the texture or the color + * factors are all equal to 1.0f then no changes are made. + * + * @param tex + * the texture structure + * @param image + * the image that will be altered if necessary + * @param blenderContext + * the blender context + */ + private void applyColorbandAndColorFactors(Structure tex, Image image, BlenderContext blenderContext) { + float rfac = ((Number) tex.getFieldValue("rfac")).floatValue(); + float gfac = ((Number) tex.getFieldValue("gfac")).floatValue(); + float bfac = ((Number) tex.getFieldValue("bfac")).floatValue(); + float[][] colorBand = new ColorBand(tex, blenderContext).computeValues(); + int depth = image.getDepth() == 0 ? 1 : image.getDepth(); + + if (colorBand != null) { + TexturePixel pixel = new TexturePixel(); + PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat()); + for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { + for (int x = 0; x < image.getWidth(); ++x) { + for (int y = 0; y < image.getHeight(); ++y) { + imageIO.read(image, layerIndex, pixel, x, y); + + int colorbandIndex = (int) (pixel.alpha * 1000.0f); + pixel.red = colorBand[colorbandIndex][0] * rfac; + pixel.green = colorBand[colorbandIndex][1] * gfac; + pixel.blue = colorBand[colorbandIndex][2] * bfac; + pixel.alpha = colorBand[colorbandIndex][3]; + + imageIO.write(image, layerIndex, pixel, x, y); + } + } + } + } else if (rfac != 1.0f || gfac != 1.0f || bfac != 1.0f) { + TexturePixel pixel = new TexturePixel(); + PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat()); + for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { + for (int x = 0; x < image.getWidth(); ++x) { + for (int y = 0; y < image.getHeight(); ++y) { + imageIO.read(image, layerIndex, pixel, x, y); + + pixel.red *= rfac; + pixel.green *= gfac; + pixel.blue *= bfac; + + imageIO.write(image, layerIndex, pixel, x, y); + } + } + } + } + } + + /** + * This method loads the textre from outside the blend file using the + * AssetManager that the blend file was loaded with. It returns a texture + * with a full assetKey that references the original texture so it later + * doesn't need to ba packed when the model data is serialized. It searches + * the AssetManager for the full path if the model file is a relative path + * and will attempt to truncate the path if it is an absolute file path + * until the path can be found in the AssetManager. If the texture can not + * be found, it will issue a load attempt for the initial path anyway so the + * failed load can be reported by the AssetManagers callback methods for + * failed assets. + * + * @param name + * the path to the image + * @param imaflag + * the image flag + * @param blenderContext + * the blender context + * @return the loaded image or null if the image cannot be found + */ + protected Texture loadImageFromFile(String name, int imaflag, BlenderContext blenderContext) { + if (!name.contains(".")) { + return null; // no extension means not a valid image + } + + // decide if the mipmaps will be generated + boolean generateMipmaps = false; + switch (blenderContext.getBlenderKey().getMipmapGenerationMethod()) { + case ALWAYS_GENERATE: + generateMipmaps = true; + break; + case NEVER_GENERATE: + break; + case GENERATE_WHEN_NEEDED: + generateMipmaps = (imaflag & 0x04) != 0; + break; + default: + throw new IllegalStateException("Unknown mipmap generation method: " + blenderContext.getBlenderKey().getMipmapGenerationMethod()); + } + + AssetManager assetManager = blenderContext.getAssetManager(); + name = name.replace('\\', '/'); + Texture result = null; + + if (name.startsWith("//")) { + // This is a relative path, so try to find it relative to the .blend file + String relativePath = name.substring(2); + // Augument the path with blender key path + BlenderKey blenderKey = blenderContext.getBlenderKey(); + int idx = blenderKey.getName().lastIndexOf('/'); + String blenderAssetFolder = blenderKey.getName().substring(0, idx != -1 ? idx : 0); + String absoluteName = blenderAssetFolder + '/' + relativePath; + // Directly try to load texture so AssetManager can report missing textures + try { + TextureKey key = new TextureKey(absoluteName); + key.setAsCube(false); + key.setFlipY(true); + key.setGenerateMips(generateMipmaps); + result = assetManager.loadTexture(key); + result.setKey(key); + } catch (AssetNotFoundException e) { + LOGGER.fine(e.getLocalizedMessage()); + } + } else { + // This is a full path, try to truncate it until the file can be found + // this works as the assetManager root is most probably a part of the + // image path. E.g. AssetManager has a locator at c:/Files/ and the + // texture path is c:/Files/Textures/Models/Image.jpg. + // For this we create a list with every possible full path name from + // the asset name to the root. Image.jpg, Models/Image.jpg, + // Textures/Models/Image.jpg (bingo) etc. + List assetNames = new ArrayList(); + String[] paths = name.split("\\/"); + StringBuilder sb = new StringBuilder(paths[paths.length - 1]);// the asset name + assetNames.add(paths[paths.length - 1]); + + for (int i = paths.length - 2; i >= 0; --i) { + sb.insert(0, '/'); + sb.insert(0, paths[i]); + assetNames.add(0, sb.toString()); + } + // Now try to locate the asset + for (String assetName : assetNames) { + try { + TextureKey key = new TextureKey(assetName); + key.setAsCube(false); + key.setFlipY(true); + key.setGenerateMips(generateMipmaps); + AssetInfo info = assetManager.locateAsset(key); + if (info != null) { + Texture texture = assetManager.loadTexture(key); + result = texture; + // Set key explicitly here if other ways fail + texture.setKey(key); + // If texture is found return it; + return result; + } + } catch (AssetNotFoundException e) { + LOGGER.fine(e.getLocalizedMessage()); + } + } + // The asset was not found in the loop above, call loadTexture with + // the original path once anyway so that the AssetManager can report + // the missing asset to subsystems. + try { + TextureKey key = new TextureKey(name); + assetManager.loadTexture(key); + } catch (AssetNotFoundException e) { + LOGGER.fine(e.getLocalizedMessage()); + } + } + + return result; + } + + @SuppressWarnings("unchecked") + public List readTextureData(Structure structure, float[] diffuseColorArray, boolean skyTexture) throws BlenderFileException { + DynamicArray mtexsArray = (DynamicArray) structure.getFieldValue("mtex"); + int separatedTextures = skyTexture ? 0 : ((Number) structure.getFieldValue("septex")).intValue(); + List texturesList = new ArrayList(); + for (int i = 0; i < mtexsArray.getTotalSize(); ++i) { + Pointer p = mtexsArray.get(i); + if (p.isNotNull() && (separatedTextures & 1 << i) == 0) { + TextureData textureData = new TextureData(); + textureData.mtex = p.fetchData().get(0); + textureData.uvCoordinatesType = skyTexture ? UVCoordinatesType.TEXCO_ORCO.blenderValue : ((Number) textureData.mtex.getFieldValue("texco")).intValue(); + textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue(); + textureData.uvCoordinatesName = textureData.mtex.getFieldValue("uvName").toString(); + if (textureData.uvCoordinatesName != null && textureData.uvCoordinatesName.trim().length() == 0) { + textureData.uvCoordinatesName = null; + } + + Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex"); + if (pTex.isNotNull()) { + Structure tex = pTex.fetchData().get(0); + textureData.textureStructure = tex; + texturesList.add(textureData); + } + } + } + + LOGGER.info("Loading model's textures."); + List loadedTextures = new ArrayList(); + if (blenderContext.getBlenderKey().isOptimiseTextures()) { + LOGGER.fine("Optimising the useage of model's textures."); + Map> textureDataMap = this.sortTextures(texturesList); + for (Entry> entry : textureDataMap.entrySet()) { + if (entry.getValue().size() > 0) { + CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue(), !skyTexture); + for (TextureData textureData : entry.getValue()) { + int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue(); + boolean negateTexture = (texflag & 0x04) != 0; + Texture texture = this.getTexture(textureData.textureStructure, textureData.mtex, blenderContext); + if (texture != null) { + int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue(); + float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() }; + float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue(); + TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac); + combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType, textureData.textureStructure, textureData.uvCoordinatesName, blenderContext); + } + } + if (combinedTexture.getTexturesCount() > 0) { + loadedTextures.add(combinedTexture); + } + } + } + } else { + LOGGER.fine("No textures optimisation applied."); + int[] mappings = new int[] { MaterialContext.MTEX_COL, MaterialContext.MTEX_NOR, MaterialContext.MTEX_EMIT, MaterialContext.MTEX_SPEC, MaterialContext.MTEX_ALPHA, MaterialContext.MTEX_AMB }; + for (TextureData textureData : texturesList) { + Texture texture = this.getTexture(textureData.textureStructure, textureData.mtex, blenderContext); + if (texture != null) { + Number mapto = (Number) textureData.mtex.getFieldValue("mapto"); + int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue(); + boolean negateTexture = (texflag & 0x04) != 0; + + for (int i = 0; i < mappings.length; ++i) { + if ((mappings[i] & mapto.intValue()) != 0) { + CombinedTexture combinedTexture = new CombinedTexture(mappings[i], !skyTexture); + int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue(); + float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() }; + float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue(); + TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac); + combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType, textureData.textureStructure, textureData.uvCoordinatesName, blenderContext); + if (combinedTexture.getTexturesCount() > 0) {// the added texture might not have been accepted (if for example loading generated textures is disabled) + loadedTextures.add(combinedTexture); + } + } + } + } + + } + } + + return loadedTextures; + } + + /** + * This method sorts the textures by their mapping type. In each group only + * textures of one type are put (either two- or three-dimensional). + * + * @return a map with sorted textures + */ + private Map> sortTextures(List textures) { + int[] mappings = new int[] { MaterialContext.MTEX_COL, MaterialContext.MTEX_NOR, MaterialContext.MTEX_EMIT, MaterialContext.MTEX_SPEC, MaterialContext.MTEX_ALPHA, MaterialContext.MTEX_AMB }; + Map> result = new HashMap>(); + for (TextureData data : textures) { + Number mapto = (Number) data.mtex.getFieldValue("mapto"); + for (int i = 0; i < mappings.length; ++i) { + if ((mappings[i] & mapto.intValue()) != 0) { + List datas = result.get(mappings[i]); + if (datas == null) { + datas = new ArrayList(); + result.put(mappings[i], datas); + } + datas.add(data); + } + } + } + return result; + } + + private static class TextureData { + public Structure mtex; + public Structure textureStructure; + public int uvCoordinatesType; + public int projectionType; + /** The name of the user's UV coordinates that are used for this texture. */ + public String uvCoordinatesName; + } +} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TexturePixel.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TexturePixel.java new file mode 100644 index 000000000..365b34f5e --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TexturePixel.java @@ -0,0 +1,392 @@ +package com.jme3.scene.plugins.blender.textures; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; + +/** + * The class that stores the pixel values of a texture. + * + * @author Marcin Roguski (Kaelthas) + */ +public class TexturePixel implements Cloneable { + /** The pixel data. */ + public float intensity, red, green, blue, alpha; + + /** + * Copies the values from the given pixel. + * + * @param pixel + * the pixel that we read from + */ + public void fromPixel(TexturePixel pixel) { + this.intensity = pixel.intensity; + this.red = pixel.red; + this.green = pixel.green; + this.blue = pixel.blue; + this.alpha = pixel.alpha; + } + + /** + * Copies the values from the given color. + * + * @param colorRGBA + * the color that we read from + */ + public void fromColor(ColorRGBA colorRGBA) { + this.red = colorRGBA.r; + this.green = colorRGBA.g; + this.blue = colorRGBA.b; + this.alpha = colorRGBA.a; + } + + /** + * Copies the values from the given values. + * + * @param a + * the alpha value + * @param r + * the red value + * @param g + * the green value + * @param b + * the blue value + */ + public void fromARGB(float a, float r, float g, float b) { + this.alpha = a; + this.red = r; + this.green = g; + this.blue = b; + } + + /** + * Copies the values from the given values. + * + * @param a + * the alpha value + * @param r + * the red value + * @param g + * the green value + * @param b + * the blue value + */ + public void fromARGB8(byte a, byte r, byte g, byte b) { + this.alpha = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; + this.red = r >= 0 ? r / 255.0f : 1.0f - ~r / 255.0f; + this.green = g >= 0 ? g / 255.0f : 1.0f - ~g / 255.0f; + this.blue = b >= 0 ? b / 255.0f : 1.0f - ~b / 255.0f; + } + + /** + * Copies the values from the given values. + * + * @param a + * the alpha value + * @param r + * the red value + * @param g + * the green value + * @param b + * the blue value + */ + public void fromARGB16(short a, short r, short g, short b) { + this.alpha = a >= 0 ? a / 65535.0f : 1.0f - ~a / 65535.0f; + this.red = r >= 0 ? r / 65535.0f : 1.0f - ~r / 65535.0f; + this.green = g >= 0 ? g / 65535.0f : 1.0f - ~g / 65535.0f; + this.blue = b >= 0 ? b / 65535.0f : 1.0f - ~b / 65535.0f; + } + + /** + * Copies the intensity from the given value. + * + * @param intensity + * the intensity value + */ + public void fromIntensity(byte intensity) { + this.intensity = intensity >= 0 ? intensity / 255.0f : 1.0f - ~intensity / 255.0f; + } + + /** + * Copies the intensity from the given value. + * + * @param intensity + * the intensity value + */ + public void fromIntensity(short intensity) { + this.intensity = intensity >= 0 ? intensity / 65535.0f : 1.0f - ~intensity / 65535.0f; + } + + /** + * This method sets the alpha value (converts it to float number from range + * <0, 1>). + * + * @param alpha + * the alpha value + */ + public void setAlpha(byte alpha) { + this.alpha = alpha >= 0 ? alpha / 255.0f : 1.0f - ~alpha / 255.0f; + } + + /** + * This method sets the alpha value (converts it to float number from range + * <0, 1>). + * + * @param alpha + * the alpha value + */ + public void setAlpha(short alpha) { + this.alpha = alpha >= 0 ? alpha / 65535.0f : 1.0f - ~alpha / 65535.0f; + } + + /** + * Copies the values from the given integer that stores the ARGB8 data. + * + * @param argb8 + * the data stored in an integer + */ + public void fromARGB8(int argb8) { + byte pixelValue = (byte) ((argb8 & 0xFF000000) >> 24); + this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; + pixelValue = (byte) ((argb8 & 0xFF0000) >> 16); + this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; + pixelValue = (byte) ((argb8 & 0xFF00) >> 8); + this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; + pixelValue = (byte) (argb8 & 0xFF); + this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; + } + + /** + * Stores RGBA values in the given array. + * + * @param result + * the array to store values + */ + public void toRGBA(float[] result) { + result[0] = this.red; + result[1] = this.green; + result[2] = this.blue; + result[3] = this.alpha; + } + + /** + * Stores the data in the given table. + * + * @param result + * the result table + */ + public void toRGBA8(byte[] result) { + result[0] = (byte) (this.red * 255.0f); + result[1] = (byte) (this.green * 255.0f); + result[2] = (byte) (this.blue * 255.0f); + result[3] = (byte) (this.alpha * 255.0f); + } + + /** + * Stores the pixel values in the integer. + * + * @return the integer that stores the pixel values + */ + public int toARGB8() { + int result = 0; + int b = (int) (this.alpha * 255.0f); + result |= b << 24; + b = (int) (this.red * 255.0f); + result |= b << 16; + b = (int) (this.green * 255.0f); + result |= b << 8; + b = (int) (this.blue * 255.0f); + result |= b; + return result; + } + + /** + * @return the intensity of the pixel + */ + public byte getInt() { + return (byte) (this.intensity * 255.0f); + } + + /** + * @return the alpha value of the pixel + */ + public byte getA8() { + return (byte) (this.alpha * 255.0f); + } + + /** + * @return the alpha red of the pixel + */ + public byte getR8() { + return (byte) (this.red * 255.0f); + } + + /** + * @return the green value of the pixel + */ + public byte getG8() { + return (byte) (this.green * 255.0f); + } + + /** + * @return the blue value of the pixel + */ + public byte getB8() { + return (byte) (this.blue * 255.0f); + } + + /** + * @return the alpha value of the pixel + */ + public short getA16() { + return (byte) (this.alpha * 65535.0f); + } + + /** + * @return the alpha red of the pixel + */ + public short getR16() { + return (byte) (this.red * 65535.0f); + } + + /** + * @return the green value of the pixel + */ + public short getG16() { + return (byte) (this.green * 65535.0f); + } + + /** + * @return the blue value of the pixel + */ + public short getB16() { + return (byte) (this.blue * 65535.0f); + } + + /** + * Merges two pixels (adds the values of each color). + * + * @param pixel + * the pixel we merge with + */ + public void merge(TexturePixel pixel) { + float oneMinusAlpha = 1 - pixel.alpha; + this.red = oneMinusAlpha * this.red + pixel.alpha * pixel.red; + this.green = oneMinusAlpha * this.green + pixel.alpha * pixel.green; + this.blue = oneMinusAlpha * this.blue + pixel.alpha * pixel.blue; + this.alpha = (this.alpha + pixel.alpha) * 0.5f; + } + + /** + * Mixes two pixels. + * + * @param pixel + * the pixel we mix with + */ + public void mix(TexturePixel pixel) { + this.red = 0.5f * (this.red + pixel.red); + this.green = 0.5f * (this.green + pixel.green); + this.blue = 0.5f * (this.blue + pixel.blue); + this.alpha = 0.5f * (this.alpha + pixel.alpha); + this.intensity = 0.5f * (this.intensity + pixel.intensity); + } + + /** + * This method negates the colors. + */ + public void negate() { + this.red = 1.0f - this.red; + this.green = 1.0f - this.green; + this.blue = 1.0f - this.blue; + this.alpha = 1.0f - this.alpha; + } + + /** + * This method clears the pixel values. + */ + public void clear() { + this.intensity = this.blue = this.red = this.green = this.alpha = 0.0f; + } + + /** + * This method adds the calues of the given pixel to the current pixel. + * + * @param pixel + * the pixel we add + */ + public void add(TexturePixel pixel) { + this.red += pixel.red; + this.green += pixel.green; + this.blue += pixel.blue; + this.alpha += pixel.alpha; + this.intensity += pixel.intensity; + } + + /** + * This method adds the calues of the given pixel to the current pixel. + * + * @param pixel + * the pixel we add + */ + public void add(ColorRGBA pixel) { + this.red += pixel.r; + this.green += pixel.g; + this.blue += pixel.b; + this.alpha += pixel.a; + } + + /** + * This method multiplies the values of the given pixel by the given value. + * + * @param value + * multiplication factor + */ + public void mult(float value) { + this.red *= value; + this.green *= value; + this.blue *= value; + this.alpha *= value; + this.intensity *= value; + } + + /** + * This method divides the values of the given pixel by the given value. + * ATTENTION! Beware of the zero value. This will cause you NaN's in the + * pixel values. + * + * @param value + * division factor + */ + public void divide(float value) { + this.red /= value; + this.green /= value; + this.blue /= value; + this.alpha /= value; + this.intensity /= value; + } + + /** + * This method clamps the pixel values to the given borders. + * + * @param min + * the minimum value + * @param max + * the maximum value + */ + public void clamp(float min, float max) { + this.red = FastMath.clamp(this.red, min, max); + this.green = FastMath.clamp(this.green, min, max); + this.blue = FastMath.clamp(this.blue, min, max); + this.alpha = FastMath.clamp(this.alpha, min, max); + this.intensity = FastMath.clamp(this.intensity, min, max); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + @Override + public String toString() { + return "[" + red + ", " + green + ", " + blue + ", " + alpha + " {" + intensity + "}]"; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java new file mode 100644 index 000000000..fd4044244 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java @@ -0,0 +1,659 @@ +package com.jme3.scene.plugins.blender.textures; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; + +import jme3tools.converters.ImageToAwt; + +import com.jme3.bounding.BoundingBox; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.textures.blending.TextureBlender; +import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; + +/** + * This texture holds a set of images for each face in the specified mesh. It + * helps to flatten 3D texture, merge 3D and 2D textures and merge 2D textures + * with different UV coordinates. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class TriangulatedTexture extends Texture2D { + /** The result image format. */ + private Format format; + /** The collection of images for each face. */ + private Collection faceTextures; + /** + * The maximum texture size (width/height). This is taken from the blender + * key. + */ + private int maxTextureSize; + /** A variable that can prevent removing identical textures. */ + private boolean keepIdenticalTextures = false; + /** The result texture. */ + private Texture2D resultTexture; + /** The result texture's UV coordinates. */ + private List resultUVS; + + /** + * This method triangulates the given flat texture. The given texture is not + * changed. + * + * @param texture2d + * the texture to be triangulated + * @param uvs + * the UV coordinates for each face + */ + public TriangulatedTexture(Texture2D texture2d, List uvs, BlenderContext blenderContext) { + maxTextureSize = blenderContext.getBlenderKey().getMaxTextureSize(); + faceTextures = new TreeSet(new Comparator() { + public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { + return o1.faceIndex - o2.faceIndex; + } + }); + int facesCount = uvs.size() / 3; + for (int i = 0; i < facesCount; ++i) { + faceTextures.add(new TriangleTextureElement(i, texture2d.getImage(), uvs, true, blenderContext)); + } + this.format = texture2d.getImage().getFormat(); + } + + /** + * Constructor that simply stores precalculated images. + * + * @param faceTextures + * a collection of images for the mesh's faces + * @param blenderContext + * the blender context + */ + public TriangulatedTexture(Collection faceTextures, BlenderContext blenderContext) { + maxTextureSize = blenderContext.getBlenderKey().getMaxTextureSize(); + this.faceTextures = faceTextures; + for (TriangleTextureElement faceTextureElement : faceTextures) { + if (format == null) { + format = faceTextureElement.image.getFormat(); + } else if (format != faceTextureElement.image.getFormat()) { + throw new IllegalArgumentException("Face texture element images MUST have the same image format!"); + } + } + } + + /** + * This method blends the each image using the given blender and taking base + * texture into consideration. + * + * @param textureBlender + * the texture blender that holds the blending definition + * @param baseTexture + * the texture that is 'below' the current texture (can be null) + * @param blenderContext + * the blender context + */ + public void blend(TextureBlender textureBlender, TriangulatedTexture baseTexture, BlenderContext blenderContext) { + Format newFormat = null; + for (TriangleTextureElement triangleTextureElement : this.faceTextures) { + Image baseImage = baseTexture == null ? null : baseTexture.getFaceTextureElement(triangleTextureElement.faceIndex).image; + triangleTextureElement.image = textureBlender.blend(triangleTextureElement.image, baseImage, blenderContext); + if (newFormat == null) { + newFormat = triangleTextureElement.image.getFormat(); + } else if (newFormat != triangleTextureElement.image.getFormat()) { + throw new IllegalArgumentException("Face texture element images MUST have the same image format!"); + } + } + this.format = newFormat; + } + + /** + * This method alters the images to fit them into UV coordinates of the + * given target texture. + * + * @param targetTexture + * the texture to whose UV coordinates we fit current images + * @param blenderContext + * the blender context + */ + public void castToUVS(TriangulatedTexture targetTexture, BlenderContext blenderContext) { + int[] sourceSize = new int[2], targetSize = new int[2]; + ImageLoader imageLoader = new ImageLoader(); + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + for (TriangleTextureElement entry : faceTextures) { + TriangleTextureElement targetFaceTextureElement = targetTexture.getFaceTextureElement(entry.faceIndex); + Vector2f[] dest = targetFaceTextureElement.uv; + + // get the sizes of the source and target images + sourceSize[0] = entry.image.getWidth(); + sourceSize[1] = entry.image.getHeight(); + targetSize[0] = targetFaceTextureElement.image.getWidth(); + targetSize[1] = targetFaceTextureElement.image.getHeight(); + + // create triangle transformation + AffineTransform affineTransform = textureHelper.createAffineTransform(entry.uv, dest, sourceSize, targetSize); + + // compute the result texture + BufferedImage sourceImage = ImageToAwt.convert(entry.image, false, true, 0); + + BufferedImage targetImage = new BufferedImage(targetSize[0], targetSize[1], sourceImage.getType()); + Graphics2D g = targetImage.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.drawImage(sourceImage, affineTransform, null); + g.dispose(); + + Image output = imageLoader.load(targetImage, false); + entry.image = output; + entry.uv[0].set(dest[0]); + entry.uv[1].set(dest[1]); + entry.uv[2].set(dest[2]); + } + } + + /** + * This method returns the flat texture. It is calculated if required or if + * it was not created before. Images that are identical are discarded to + * reduce the texture size. + * + * @param rebuild + * a variable that forces texture recomputation (even if it was + * computed vefore) + * @return flat result texture (all images merged into one) + */ + public Texture2D getResultTexture(boolean rebuild) { + if (resultTexture == null || rebuild) { + // sorting the parts by their height (from highest to the lowest) + List list = new ArrayList(faceTextures); + Collections.sort(list, new Comparator() { + public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { + return o2.image.getHeight() - o1.image.getHeight(); + } + }); + + // arraging the images on the resulting image (calculating the result image width and height) + Set duplicatedFaceIndexes = new HashSet(); + int resultImageHeight = list.get(0).image.getHeight(); + int resultImageWidth = 0; + int currentXPos = 0, currentYPos = 0; + Map imageLayoutData = new HashMap(list.size()); + while (list.size() > 0) { + TriangleTextureElement currentElement = list.remove(0); + if (currentXPos + currentElement.image.getWidth() > maxTextureSize) { + currentXPos = 0; + currentYPos = resultImageHeight; + resultImageHeight += currentElement.image.getHeight(); + } + Integer[] currentPositions = new Integer[] { currentXPos, currentYPos }; + imageLayoutData.put(currentElement, currentPositions); + + if (keepIdenticalTextures) {// removing identical images + for (int i = 0; i < list.size(); ++i) { + if (currentElement.image.equals(list.get(i).image)) { + duplicatedFaceIndexes.add(list.get(i).faceIndex); + imageLayoutData.put(list.remove(i--), currentPositions); + } + } + } + + currentXPos += currentElement.image.getWidth(); + resultImageWidth = Math.max(resultImageWidth, currentXPos); + // currentYPos += currentElement.image.getHeight(); + + // TODO: implement that to compact the result image + // try to add smaller images below the current one + // int remainingHeight = resultImageHeight - + // currentElement.image.getHeight(); + // while(remainingHeight > 0) { + // for(int i=list.size() - 1;i>=0;--i) { + // + // } + // } + } + + // computing the result UV coordinates + resultUVS = new ArrayList(imageLayoutData.size() * 3); + for (int i = 0; i < imageLayoutData.size() * 3; ++i) { + resultUVS.add(null); + } + Vector2f[] uvs = new Vector2f[3]; + for (Entry entry : imageLayoutData.entrySet()) { + Integer[] position = entry.getValue(); + entry.getKey().computeFinalUVCoordinates(resultImageWidth, resultImageHeight, position[0], position[1], uvs); + resultUVS.set(entry.getKey().faceIndex * 3, uvs[0]); + resultUVS.set(entry.getKey().faceIndex * 3 + 1, uvs[1]); + resultUVS.set(entry.getKey().faceIndex * 3 + 2, uvs[2]); + } + + Image resultImage = new Image(format, resultImageWidth, resultImageHeight, BufferUtils.createByteBuffer(resultImageWidth * resultImageHeight * (format.getBitsPerPixel() >> 3))); + resultTexture = new Texture2D(resultImage); + for (Entry entry : imageLayoutData.entrySet()) { + if (!duplicatedFaceIndexes.contains(entry.getKey().faceIndex)) { + this.draw(resultImage, entry.getKey().image, entry.getValue()[0], entry.getValue()[1]); + } + } + + // setting additional data + resultTexture.setWrap(WrapAxis.S, this.getWrap(WrapAxis.S)); + resultTexture.setWrap(WrapAxis.T, this.getWrap(WrapAxis.T)); + resultTexture.setMagFilter(this.getMagFilter()); + resultTexture.setMinFilter(this.getMinFilter()); + } + return resultTexture; + } + + /** + * @return the result flat texture + */ + public Texture2D getResultTexture() { + return this.getResultTexture(false); + } + + /** + * @return the result texture's UV coordinates + */ + public List getResultUVS() { + this.getResultTexture();// this is called here to make sure that the result UVS are computed + return resultUVS; + } + + /** + * This method returns a single image element for the given face index. + * + * @param faceIndex + * the face index + * @return image element for the required face index + * @throws IllegalStateException + * this exception is thrown if the current image set does not + * contain an image for the given face index + */ + public TriangleTextureElement getFaceTextureElement(int faceIndex) { + for (TriangleTextureElement textureElement : faceTextures) { + if (textureElement.faceIndex == faceIndex) { + return textureElement; + } + } + throw new IllegalStateException("No face texture element found for index: " + faceIndex); + } + + /** + * @return the amount of texture faces + */ + public int getFaceTextureCount() { + return faceTextures.size(); + } + + /** + * Tells the object wheather to keep or reduce identical face textures. + * + * @param keepIdenticalTextures + * keeps or discards identical textures + */ + public void setKeepIdenticalTextures(boolean keepIdenticalTextures) { + this.keepIdenticalTextures = keepIdenticalTextures; + } + + /** + * This method draws the source image on the target image starting with the + * specified positions. + * + * @param target + * the target image + * @param source + * the source image + * @param targetXPos + * start X position on the target image + * @param targetYPos + * start Y position on the target image + */ + private void draw(Image target, Image source, int targetXPos, int targetYPos) { + PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(source.getFormat()); + PixelInputOutput targetIO = PixelIOFactory.getPixelIO(target.getFormat()); + TexturePixel pixel = new TexturePixel(); + + for (int x = 0; x < source.getWidth(); ++x) { + for (int y = 0; y < source.getHeight(); ++y) { + sourceIO.read(source, 0, pixel, x, y); + targetIO.write(target, 0, pixel, targetXPos + x, targetYPos + y); + } + } + } + + /** + * A class that represents an image for a single face of the mesh. + * + * @author Marcin Roguski (Kaelthas) + */ + /* package */static class TriangleTextureElement { + /** The image for the face. */ + public Image image; + /** The UV coordinates for the image. */ + public final Vector2f[] uv; + /** The index of the face this image refers to. */ + public final int faceIndex; + + /** + * Constructor that creates the image element from the given texture and + * UV coordinates (it cuts out the smallest rectasngle possible from the + * given image that will hold the triangle defined by the given UV + * coordinates). After the image is cut out the UV coordinates are + * recalculated to be fit for the new image. + * + * @param faceIndex + * the index of mesh's face this image refers to + * @param sourceImage + * the source image + * @param uvCoordinates + * the UV coordinates that define the image + */ + public TriangleTextureElement(int faceIndex, Image sourceImage, List uvCoordinates, boolean wholeUVList, BlenderContext blenderContext) { + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + this.faceIndex = faceIndex; + + uv = wholeUVList ? new Vector2f[] { uvCoordinates.get(faceIndex * 3).clone(), uvCoordinates.get(faceIndex * 3 + 1).clone(), uvCoordinates.get(faceIndex * 3 + 2).clone() } : new Vector2f[] { uvCoordinates.get(0).clone(), uvCoordinates.get(1).clone(), uvCoordinates.get(2).clone() }; + + // be careful here, floating point operations might cause the + // texture positions to be inapropriate + int[][] texturePosition = new int[3][2]; + for (int i = 0; i < texturePosition.length; ++i) { + texturePosition[i][0] = textureHelper.getPixelPosition(uv[i].x, sourceImage.getWidth()); + texturePosition[i][1] = textureHelper.getPixelPosition(uv[i].y, sourceImage.getHeight()); + } + + // calculating the extent of the texture + int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE; + float minUVX = Float.MAX_VALUE, minUVY = Float.MAX_VALUE; + float maxUVX = Float.MIN_VALUE, maxUVY = Float.MIN_VALUE; + + for (int i = 0; i < texturePosition.length; ++i) { + minX = Math.min(texturePosition[i][0], minX); + minY = Math.min(texturePosition[i][1], minY); + + maxX = Math.max(texturePosition[i][0], maxX); + maxY = Math.max(texturePosition[i][1], maxY); + + minUVX = Math.min(uv[i].x, minUVX); + minUVY = Math.min(uv[i].y, minUVY); + maxUVX = Math.max(uv[i].x, maxUVX); + maxUVY = Math.max(uv[i].y, maxUVY); + } + int width = maxX - minX; + int height = maxY - minY; + + if (width == 0) { + width = 1; + } + if (height == 0) { + height = 1; + } + + // copy the pixel from the texture to the result image + PixelInputOutput pixelReader = PixelIOFactory.getPixelIO(sourceImage.getFormat()); + TexturePixel pixel = new TexturePixel(); + ByteBuffer data = BufferUtils.createByteBuffer(width * height * 4); + for (int y = minY; y < maxY; ++y) { + for (int x = minX; x < maxX; ++x) { + int xPos = x >= sourceImage.getWidth() ? x - sourceImage.getWidth() : x; + int yPos = y >= sourceImage.getHeight() ? y - sourceImage.getHeight() : y; + pixelReader.read(sourceImage, 0, pixel, xPos, yPos); + data.put(pixel.getR8()); + data.put(pixel.getG8()); + data.put(pixel.getB8()); + data.put(pixel.getA8()); + } + } + image = new Image(Format.RGBA8, width, height, data); + + // modify the UV values so that they fit the new image + float heightUV = maxUVY - minUVY; + float widthUV = maxUVX - minUVX; + for (int i = 0; i < uv.length; ++i) { + // first translate it to the image borders + uv[i].x -= minUVX; + uv[i].y -= minUVY; + // then scale so that it fills the whole area + uv[i].x /= widthUV; + uv[i].y /= heightUV; + } + } + + /** + * Constructor that creates an image element from the 3D texture + * (generated texture). It computes a flat smallest rectangle that can + * hold a (3D) triangle defined by the given UV coordinates. Then it + * defines the image pixels for points in 3D space that define the + * calculated rectangle. + * + * @param faceIndex + * the face index this image refers to + * @param boundingBox + * the bounding box of the mesh + * @param texture + * the texture that allows to compute a pixel value in 3D + * space + * @param uv + * the UV coordinates of the mesh + * @param blenderContext + * the blender context + */ + public TriangleTextureElement(int faceIndex, BoundingBox boundingBox, GeneratedTexture texture, Vector3f[] uv, int[] uvIndices, BlenderContext blenderContext) { + this.faceIndex = faceIndex; + + // compute the face vertices from the UV coordinates + float width = boundingBox.getXExtent() * 2; + float height = boundingBox.getYExtent() * 2; + float depth = boundingBox.getZExtent() * 2; + + Vector3f min = boundingBox.getMin(null); + Vector3f v1 = min.add(uv[uvIndices[0]].x * width, uv[uvIndices[0]].y * height, uv[uvIndices[0]].z * depth); + Vector3f v2 = min.add(uv[uvIndices[1]].x * width, uv[uvIndices[1]].y * height, uv[uvIndices[1]].z * depth); + Vector3f v3 = min.add(uv[uvIndices[2]].x * width, uv[uvIndices[2]].y * height, uv[uvIndices[2]].z * depth); + + // get the rectangle envelope for the triangle + RectangleEnvelope envelope = this.getTriangleEnvelope(v1, v2, v3); + + // create the result image + Format imageFormat = texture.getImage().getFormat(); + int imageWidth = (int) (envelope.width * blenderContext.getBlenderKey().getGeneratedTexturePPU()); + if (imageWidth == 0) { + imageWidth = 1; + } + int imageHeight = (int) (envelope.height * blenderContext.getBlenderKey().getGeneratedTexturePPU()); + if (imageHeight == 0) { + imageHeight = 1; + } + ByteBuffer data = BufferUtils.createByteBuffer(imageWidth * imageHeight * (imageFormat.getBitsPerPixel() >> 3)); + image = new Image(texture.getImage().getFormat(), imageWidth, imageHeight, data); + + // computing the pixels + PixelInputOutput pixelWriter = PixelIOFactory.getPixelIO(imageFormat); + TexturePixel pixel = new TexturePixel(); + float[] uvs = new float[3]; + Vector3f point = new Vector3f(envelope.min); + Vector3f vecY = new Vector3f(); + Vector3f wDelta = new Vector3f(envelope.w).multLocal(1.0f / imageWidth); + Vector3f hDelta = new Vector3f(envelope.h).multLocal(1.0f / imageHeight); + for (int x = 0; x < imageWidth; ++x) { + for (int y = 0; y < imageHeight; ++y) { + this.toTextureUV(boundingBox, point, uvs); + texture.getPixel(pixel, uvs[0], uvs[1], uvs[2]); + pixelWriter.write(image, 0, pixel, x, y); + point.addLocal(hDelta); + } + + vecY.addLocal(wDelta); + point.set(envelope.min).addLocal(vecY); + } + + // preparing UV coordinates for the flatted texture + this.uv = new Vector2f[3]; + this.uv[0] = new Vector2f(FastMath.clamp(v1.subtract(envelope.min).length(), 0, Float.MAX_VALUE) / envelope.height, 0); + Vector3f heightDropPoint = v2.subtract(envelope.w);// w is directed from the base to v2 + this.uv[1] = new Vector2f(1, heightDropPoint.subtractLocal(envelope.min).length() / envelope.height); + this.uv[2] = new Vector2f(0, 1); + } + + /** + * This method computes the final UV coordinates for the image (after it + * is combined with other images and drawed on the result image). + * + * @param totalImageWidth + * the result image width + * @param totalImageHeight + * the result image height + * @param xPos + * the most left x coordinate of the image + * @param yPos + * the most top y coordinate of the image + * @param result + * a vector where the result is stored + */ + public void computeFinalUVCoordinates(int totalImageWidth, int totalImageHeight, int xPos, int yPos, Vector2f[] result) { + for (int i = 0; i < 3; ++i) { + result[i] = new Vector2f(); + result[i].x = xPos / (float) totalImageWidth + this.uv[i].x * (this.image.getWidth() / (float) totalImageWidth); + result[i].y = yPos / (float) totalImageHeight + this.uv[i].y * (this.image.getHeight() / (float) totalImageHeight); + } + } + + /** + * This method converts the given point into 3D UV coordinates. + * + * @param boundingBox + * the bounding box of the mesh + * @param point + * the point to be transformed + * @param uvs + * the result UV coordinates + */ + private void toTextureUV(BoundingBox boundingBox, Vector3f point, float[] uvs) { + uvs[0] = (point.x - boundingBox.getCenter().x) / (boundingBox.getXExtent() == 0 ? 1 : boundingBox.getXExtent()); + uvs[1] = (point.y - boundingBox.getCenter().y) / (boundingBox.getYExtent() == 0 ? 1 : boundingBox.getYExtent()); + uvs[2] = (point.z - boundingBox.getCenter().z) / (boundingBox.getZExtent() == 0 ? 1 : boundingBox.getZExtent()); + // UVS cannot go outside <0, 1> range, but since we are generating texture for triangle envelope it might happen that + // some points of the envelope will exceet the bounding box of the mesh thus generating uvs outside the range + for (int i = 0; i < 3; ++i) { + uvs[i] = FastMath.clamp(uvs[i], 0, 1); + } + } + + /** + * This method returns an envelope of a minimal rectangle, that is set + * in 3D space, and contains the given triangle. + * + * @param triangle + * the triangle + * @return a rectangle minimum and maximum point and height and width + */ + private RectangleEnvelope getTriangleEnvelope(Vector3f v1, Vector3f v2, Vector3f v3) { + Vector3f h = v3.subtract(v1);// the height of the resulting rectangle + Vector3f temp = v2.subtract(v1); + + float field = 0.5f * h.cross(temp).length();// the field of the rectangle: Field = 0.5 * ||h x temp|| + if (field <= 0.0f) { + return new RectangleEnvelope(v1);// return single point envelope + } + + float cosAlpha = h.dot(temp) / (h.length() * temp.length());// the cosinus of angle betweenh and temp + + float triangleHeight = 2 * field / h.length();// the base of the height is the h vector + // now calculate the distance between v1 vertex and the point where + // the above calculated height 'touches' the base line (it can be + // settled outside the h vector) + float x = Math.abs((float) Math.sqrt(FastMath.clamp(temp.lengthSquared() - triangleHeight * triangleHeight, 0, Float.MAX_VALUE))) * Math.signum(cosAlpha); + // now get the height base point + Vector3f xPoint = v1.add(h.normalize().multLocal(x)); + + // get the minimum point of the envelope + Vector3f min = x < 0 ? xPoint : v1; + if (x < 0) { + h = v3.subtract(min); + } else if (x > h.length()) { + h = xPoint.subtract(min); + } + + Vector3f envelopeWidth = v2.subtract(xPoint); + return new RectangleEnvelope(min, envelopeWidth, h); + } + } + + /** + * A class that represents a flat rectangle in 3D space that is built on a + * triangle in 3D space. + * + * @author Marcin Roguski (Kaelthas) + */ + private static class RectangleEnvelope { + /** The minimum point of the rectangle. */ + public final Vector3f min; + /** The width vector. */ + public final Vector3f w; + /** The height vector. */ + public final Vector3f h; + /** The width of the rectangle. */ + public final float width; + /** The height of the rectangle. */ + public final float height; + + /** + * Constructs a rectangle that actually holds a point, not a triangle. + * This is a special case that is sometimes used when generating a + * texture where UV coordinates are defined by normals instead of + * vertices. + * + * @param pointPosition + * a position in 3D space + */ + public RectangleEnvelope(Vector3f pointPosition) { + this.min = pointPosition; + this.h = this.w = Vector3f.ZERO; + this.width = this.height = 1; + } + + /** + * Constructs a rectangle envelope. + * + * @param min + * the minimum rectangle point + * @param w + * the width vector + * @param h + * the height vector + */ + public RectangleEnvelope(Vector3f min, Vector3f w, Vector3f h) { + this.min = min; + this.h = h; + this.w = w; + this.width = w.length(); + this.height = h.length(); + } + + @Override + public String toString() { + return "Envelope[min = " + min + ", w = " + w + ", h = " + h + "]"; + } + } + + @Override + public Texture createSimpleClone() { + return null; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java new file mode 100644 index 000000000..f3d192008 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingSphere; +import com.jme3.bounding.BoundingVolume; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.plugins.blender.textures.UVProjectionGenerator.UVProjectionType; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +/** + * This class is used for UV coordinates generation. + * + * @author Marcin Roguski (Kaelthas) + */ +public class UVCoordinatesGenerator { + private static final Logger LOGGER = Logger.getLogger(UVCoordinatesGenerator.class.getName()); + + public static enum UVCoordinatesType { + TEXCO_ORCO(1), TEXCO_REFL(2), TEXCO_NORM(4), TEXCO_GLOB(8), TEXCO_UV(16), TEXCO_OBJECT(32), TEXCO_LAVECTOR(64), TEXCO_VIEW(128), + TEXCO_STICKY(256), TEXCO_OSA(512), TEXCO_WINDOW(1024), NEED_UV(2048), TEXCO_TANGENT(4096), + TEXCO_PARTICLE_OR_STRAND(8192), //TEXCO_PARTICLE (since blender 2.6x) has also the value of: 8192 but is used for halo materials instead of normal materials + TEXCO_STRESS(16384), TEXCO_SPEED(32768); + + public final int blenderValue; + + private UVCoordinatesType(int blenderValue) { + this.blenderValue = blenderValue; + } + + public static UVCoordinatesType valueOf(int blenderValue) { + for (UVCoordinatesType coordinatesType : UVCoordinatesType.values()) { + if (coordinatesType.blenderValue == blenderValue) { + return coordinatesType; + } + } + return null; + } + } + + /** + * Generates a UV coordinates for 2D texture. + * + * @param mesh + * the mesh we generate UV's for + * @param texco + * UV coordinates type + * @param projection + * projection type + * @param geometries + * the geometris the given mesh belongs to (required to compute + * bounding box) + * @return UV coordinates for the given mesh + */ + public static List generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, List geometries) { + List result = new ArrayList(); + BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); + float[] inputData = null;// positions, normals, reflection vectors, etc. + + switch (texco) { + case TEXCO_ORCO: + inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); + break; + case TEXCO_UV:// this should be used if not defined by user explicitly + Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; + for (int i = 0; i < mesh.getVertexCount(); ++i) { + result.add(data[i % 3]); + } + break; + case TEXCO_NORM: + inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); + break; + case TEXCO_REFL: + case TEXCO_GLOB: + case TEXCO_TANGENT: + case TEXCO_STRESS: + case TEXCO_LAVECTOR: + case TEXCO_OBJECT: + case TEXCO_OSA: + case TEXCO_PARTICLE_OR_STRAND: + case TEXCO_SPEED: + case TEXCO_STICKY: + case TEXCO_VIEW: + case TEXCO_WINDOW: + LOGGER.warning("Texture coordinates type not currently supported: " + texco); + break; + default: + throw new IllegalStateException("Unknown texture coordinates value: " + texco); + } + + if (inputData != null) {// make projection calculations + switch (projection) { + case PROJECTION_FLAT: + inputData = UVProjectionGenerator.flatProjection(inputData, bb); + break; + case PROJECTION_CUBE: + inputData = UVProjectionGenerator.cubeProjection(inputData, bb); + break; + case PROJECTION_TUBE: + BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries); + inputData = UVProjectionGenerator.tubeProjection(inputData, bt); + break; + case PROJECTION_SPHERE: + BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries); + inputData = UVProjectionGenerator.sphereProjection(inputData, bs); + break; + default: + throw new IllegalStateException("Unknown projection type: " + projection); + } + for (int i = 0; i < inputData.length; i += 2) { + result.add(new Vector2f(inputData[i], inputData[i + 1])); + } + } + return result; + } + + /** + * Generates a UV coordinates for 3D texture. + * + * @param mesh + * the mesh we generate UV's for + * @param texco + * UV coordinates type + * @param coordinatesSwappingIndexes + * coordinates swapping indexes + * @param geometries + * the geometris the given mesh belongs to (required to compute + * bounding box) + * @return UV coordinates for the given mesh + */ + public static List generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, List geometries) { + List result = new ArrayList(); + BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); + float[] inputData = null;// positions, normals, reflection vectors, etc. + + switch (texco) { + case TEXCO_ORCO: + inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); + break; + case TEXCO_UV: + Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; + for (int i = 0; i < mesh.getVertexCount(); ++i) { + Vector2f uv = data[i % 3]; + result.add(new Vector3f(uv.x, uv.y, 0)); + } + break; + case TEXCO_NORM: + inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); + break; + case TEXCO_REFL: + case TEXCO_GLOB: + case TEXCO_TANGENT: + case TEXCO_STRESS: + case TEXCO_LAVECTOR: + case TEXCO_OBJECT: + case TEXCO_OSA: + case TEXCO_PARTICLE_OR_STRAND: + case TEXCO_SPEED: + case TEXCO_STICKY: + case TEXCO_VIEW: + case TEXCO_WINDOW: + LOGGER.warning("Texture coordinates type not currently supported: " + texco); + break; + default: + throw new IllegalStateException("Unknown texture coordinates value: " + texco); + } + + if (inputData != null) {// make calculations + Vector3f min = bb.getMin(null); + float[] uvCoordsResults = new float[4];// used for coordinates swapping + float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 }; + for (int i = 0; i < ext.length; ++i) { + if (ext[i] == 0) { + ext[i] = 1; + } + } + // now transform the coordinates so that they are in the range of + // <0; 1> + for (int i = 0; i < inputData.length; i += 3) { + uvCoordsResults[1] = (inputData[i] - min.x) / ext[0]; + uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1]; + uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2]; + result.add(new Vector3f(uvCoordsResults[coordinatesSwappingIndexes[0]], uvCoordsResults[coordinatesSwappingIndexes[1]], uvCoordsResults[coordinatesSwappingIndexes[2]])); + } + } + return result; + } + + /** + * This method should be used to determine if the texture will ever be + * computed. If the texture coordinates are not supported then the try of + * flattening the texture might result in runtime exceptions occurence. + * + * @param texco + * the texture coordinates type + * @return true if the type is supported and false otherwise + */ + public static boolean isTextureCoordinateTypeSupported(UVCoordinatesType texco) { + switch (texco) { + case TEXCO_ORCO: + case TEXCO_UV: + case TEXCO_NORM: + return true; + case TEXCO_REFL: + case TEXCO_GLOB: + case TEXCO_TANGENT: + case TEXCO_STRESS: + case TEXCO_LAVECTOR: + case TEXCO_OBJECT: + case TEXCO_OSA: + case TEXCO_PARTICLE_OR_STRAND: + case TEXCO_SPEED: + case TEXCO_STICKY: + case TEXCO_VIEW: + case TEXCO_WINDOW: + return false; + default: + throw new IllegalStateException("Unknown texture coordinates value: " + texco); + } + } + + /** + * This method returns the bounding box of the given geometries. + * + * @param geometries + * the list of geometries + * @return bounding box of the given geometries + */ + public static BoundingBox getBoundingBox(List geometries) { + BoundingBox result = null; + for (Geometry geometry : geometries) { + BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry.getMesh()); + if (result == null) { + result = bb; + } else { + result.merge(bb); + } + } + return result; + } + + /** + * This method returns the bounding box of the given mesh. + * + * @param mesh + * the mesh + * @return bounding box of the given mesh + */ + /* package */static BoundingBox getBoundingBox(Mesh mesh) { + mesh.updateBound(); + BoundingVolume bv = mesh.getBound(); + if (bv instanceof BoundingBox) { + return (BoundingBox) bv; + } else if (bv instanceof BoundingSphere) { + BoundingSphere bs = (BoundingSphere) bv; + float r = bs.getRadius(); + return new BoundingBox(bs.getCenter(), r, r, r); + } else { + throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName()); + } + } + + /** + * This method returns the bounding sphere of the given geometries. + * + * @param geometries + * the list of geometries + * @return bounding sphere of the given geometries + */ + /* package */static BoundingSphere getBoundingSphere(List geometries) { + BoundingSphere result = null; + for (Geometry geometry : geometries) { + BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometry.getMesh()); + if (result == null) { + result = bs; + } else { + result.merge(bs); + } + } + return result; + } + + /** + * This method returns the bounding sphere of the given mesh. + * + * @param mesh + * the mesh + * @return bounding sphere of the given mesh + */ + /* package */static BoundingSphere getBoundingSphere(Mesh mesh) { + mesh.updateBound(); + BoundingVolume bv = mesh.getBound(); + if (bv instanceof BoundingBox) { + BoundingBox bb = (BoundingBox) bv; + float r = Math.max(bb.getXExtent(), bb.getYExtent()); + r = Math.max(r, bb.getZExtent()); + return new BoundingSphere(r, bb.getCenter()); + } else if (bv instanceof BoundingSphere) { + return (BoundingSphere) bv; + } else { + throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName()); + } + } + + /** + * This method returns the bounding tube of the given mesh. + * + * @param mesh + * the mesh + * @return bounding tube of the given mesh + */ + /* package */static BoundingTube getBoundingTube(Mesh mesh) { + Vector3f center = new Vector3f(); + float maxx = -Float.MAX_VALUE, minx = Float.MAX_VALUE; + float maxy = -Float.MAX_VALUE, miny = Float.MAX_VALUE; + float maxz = -Float.MAX_VALUE, minz = Float.MAX_VALUE; + + FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position); + int limit = positions.limit(); + for (int i = 0; i < limit; i += 3) { + float x = positions.get(i); + float y = positions.get(i + 1); + float z = positions.get(i + 2); + center.addLocal(x, y, z); + maxx = x > maxx ? x : maxx; + minx = x < minx ? x : minx; + maxy = y > maxy ? y : maxy; + miny = y < miny ? y : miny; + maxz = z > maxz ? z : maxz; + minz = z < minz ? z : minz; + } + center.divideLocal(limit / 3); + + float radius = Math.max(maxx - minx, maxy - miny) * 0.5f; + return new BoundingTube(radius, maxz - minz, center); + } + + /** + * This method returns the bounding tube of the given geometries. + * + * @param geometries + * the list of geometries + * @return bounding tube of the given geometries + */ + /* package */static BoundingTube getBoundingTube(List geometries) { + BoundingTube result = null; + for (Geometry geometry : geometries) { + BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometry.getMesh()); + if (result == null) { + result = bt; + } else { + result.merge(bt); + } + } + return result; + } + + /** + * A very simple bounding tube. Id holds only the basic data bout the + * bounding tube and does not provide full functionality of a + * BoundingVolume. Should be replaced with a bounding tube that extends the + * BoundingVolume if it is ever created. + * + * @author Marcin Roguski (Kaelthas) + */ + /* package */static class BoundingTube { + private float radius; + private float height; + private Vector3f center; + + /** + * Constructor creates the tube with the given params. + * + * @param radius + * the radius of the tube + * @param height + * the height of the tube + * @param center + * the center of the tube + */ + public BoundingTube(float radius, float height, Vector3f center) { + this.radius = radius; + this.height = height; + this.center = center; + } + + /** + * This method merges two bounding tubes. + * + * @param boundingTube + * bounding tube to be merged woth the current one + * @return new instance of bounding tube representing the tubes' merge + */ + public BoundingTube merge(BoundingTube boundingTube) { + // get tubes (tube1.radius >= tube2.radius) + BoundingTube tube1, tube2; + if (this.radius >= boundingTube.radius) { + tube1 = this; + tube2 = boundingTube; + } else { + tube1 = boundingTube; + tube2 = this; + } + float r1 = tube1.radius; + float r2 = tube2.radius; + + float minZ = Math.min(tube1.center.z - tube1.height * 0.5f, tube2.center.z - tube2.height * 0.5f); + float maxZ = Math.max(tube1.center.z + tube1.height * 0.5f, tube2.center.z + tube2.height * 0.5f); + float height = maxZ - minZ; + Vector3f distance = tube2.center.subtract(tube1.center); + Vector3f center = tube1.center.add(distance.mult(0.5f)); + distance.z = 0;// projecting this vector on XY plane + float d = distance.length(); + // d <= r1 - r2: tube2 is inside tube1 or touches tube1 from the + // inside + // d > r1 - r2: tube2 is outside or touches tube1 or crosses tube1 + float radius = d <= r1 - r2 ? tube1.radius : (d + r1 + r2) * 0.5f; + return new BoundingTube(radius, height, center); + } + + /** + * @return the radius of the tube + */ + public float getRadius() { + return radius; + } + + /** + * @return the height of the tube + */ + public float getHeight() { + return height; + } + + /** + * @return the center of the tube + */ + public Vector3f getCenter() { + return center; + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java new file mode 100644 index 000000000..a213bc626 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java @@ -0,0 +1,240 @@ +package com.jme3.scene.plugins.blender.textures; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingSphere; +import com.jme3.math.FastMath; +import com.jme3.math.Triangle; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.BoundingTube; + +/** + * This class helps with projection calculations. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class UVProjectionGenerator { + /** + * 2D texture mapping (projection) + * @author Marcin Roguski (Kaelthas) + */ + public static enum UVProjectionType { + PROJECTION_FLAT(0), PROJECTION_CUBE(1), PROJECTION_TUBE(2), PROJECTION_SPHERE(3); + + public final int blenderValue; + + private UVProjectionType(int blenderValue) { + this.blenderValue = blenderValue; + } + + public static UVProjectionType valueOf(int blenderValue) { + for (UVProjectionType projectionType : UVProjectionType.values()) { + if (projectionType.blenderValue == blenderValue) { + return projectionType; + } + } + return null; + } + } + + /** + * Flat projection for 2D textures. + * + * @param mesh + * mesh that is to be projected + * @param bb + * the bounding box for projecting + * @return UV coordinates after the projection + */ + public static float[] flatProjection(float[] positions, BoundingBox bb) { + Vector3f min = bb.getMin(null); + float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getZExtent() * 2.0f }; + float[] uvCoordinates = new float[positions.length / 3 * 2]; + for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { + uvCoordinates[j] = (positions[i] - min.x) / ext[0]; + // skip the Y-coordinate + uvCoordinates[j + 1] = (positions[i + 2] - min.z) / ext[1]; + } + return uvCoordinates; + } + + /** + * Cube projection for 2D textures. + * + * @param positions + * points to be projected + * @param bb + * the bounding box for projecting + * @return UV coordinates after the projection + */ + public static float[] cubeProjection(float[] positions, BoundingBox bb) { + Triangle triangle = new Triangle(); + Vector3f x = new Vector3f(1, 0, 0); + Vector3f y = new Vector3f(0, 1, 0); + Vector3f z = new Vector3f(0, 0, 1); + Vector3f min = bb.getMin(null); + float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f, bb.getZExtent() * 2.0f }; + + float[] uvCoordinates = new float[positions.length / 3 * 2]; + float borderAngle = (float) Math.sqrt(2.0f) / 2.0f; + for (int i = 0, pointIndex = 0; i < positions.length; i += 9) { + triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); + triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); + triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); + Vector3f n = triangle.getNormal(); + float dotNX = Math.abs(n.dot(x)); + float dorNY = Math.abs(n.dot(y)); + float dotNZ = Math.abs(n.dot(z)); + if (dotNX > borderAngle) { + if (dotNZ < borderAngle) {// discard X-coordinate + uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2]; + uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2]; + uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2]; + } else {// discard Z-coordinate + uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; + } + } else { + if (dorNY > borderAngle) {// discard Y-coordinate + uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2]; + uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2]; + uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2]; + } else {// discard Z-coordinate + uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; + } + } + triangle.setNormal(null);// clear the previous normal vector + } + return uvCoordinates; + } + + /** + * Tube projection for 2D textures. + * + * @param positions + * points to be projected + * @param bt + * the bounding tube for projecting + * @return UV coordinates after the projection + */ + public static float[] tubeProjection(float[] positions, BoundingTube bt) { + float[] uvCoordinates = new float[positions.length / 3 * 2]; + Vector3f v = new Vector3f(); + float cx = bt.getCenter().x, cz = bt.getCenter().z; + Vector3f uBase = new Vector3f(0, 0, -1); + + float vBase = bt.getCenter().y - bt.getHeight() * 0.5f; + for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { + // calculating U + v.set(positions[i] - cx, 0, positions[i + 2] - cz); + v.normalizeLocal(); + float angle = v.angleBetween(uBase);// result between [0; PI] + if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then + angle = FastMath.TWO_PI - angle; + } + uvCoordinates[j] = angle / FastMath.TWO_PI; + + // calculating V + float y = positions[i + 1]; + uvCoordinates[j + 1] = (y - vBase) / bt.getHeight(); + } + + // looking for splitted triangles + Triangle triangle = new Triangle(); + for (int i = 0; i < positions.length; i += 9) { + triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); + triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); + triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); + float sgn1 = Math.signum(triangle.get1().x - cx); + float sgn2 = Math.signum(triangle.get2().x - cx); + float sgn3 = Math.signum(triangle.get3().x - cx); + float xSideFactor = sgn1 + sgn2 + sgn3; + float ySideFactor = Math.signum(triangle.get1().z - cz) + Math.signum(triangle.get2().z - cz) + Math.signum(triangle.get3().z - cz); + if ((xSideFactor > -3 || xSideFactor < 3) && ySideFactor < 0) {// the triangle is on the splitting plane + if (sgn1 == 1.0f) { + uvCoordinates[i / 3 * 2] += 1.0f; + } + if (sgn2 == 1.0f) { + uvCoordinates[(i / 3 + 1) * 2] += 1.0f; + } + if (sgn3 == 1.0f) { + uvCoordinates[(i / 3 + 2) * 2] += 1.0f; + } + } + } + return uvCoordinates; + } + + /** + * Sphere projection for 2D textures. + * + * @param positions + * points to be projected + * @param bb + * the bounding box for projecting + * @return UV coordinates after the projection + */ + public static float[] sphereProjection(float[] positions, BoundingSphere bs) {// TODO: rotate it to be vertical + float[] uvCoordinates = new float[positions.length / 3 * 2]; + Vector3f v = new Vector3f(); + float cx = bs.getCenter().x, cy = bs.getCenter().y, cz = bs.getCenter().z; + Vector3f uBase = new Vector3f(0, -1, 0); + Vector3f vBase = new Vector3f(0, 0, -1); + + for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { + // calculating U + v.set(positions[i] - cx, positions[i + 1] - cy, 0); + v.normalizeLocal(); + float angle = v.angleBetween(uBase);// result between [0; PI] + if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then + angle = FastMath.TWO_PI - angle; + } + uvCoordinates[j] = angle / FastMath.TWO_PI; + + // calculating V + v.set(positions[i] - cx, positions[i + 1] - cy, positions[i + 2] - cz); + v.normalizeLocal(); + angle = v.angleBetween(vBase);// result between [0; PI] + uvCoordinates[j + 1] = angle / FastMath.PI; + } + + // looking for splitted triangles + Triangle triangle = new Triangle(); + for (int i = 0; i < positions.length; i += 9) { + triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); + triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); + triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); + float sgn1 = Math.signum(triangle.get1().x - cx); + float sgn2 = Math.signum(triangle.get2().x - cx); + float sgn3 = Math.signum(triangle.get3().x - cx); + float xSideFactor = sgn1 + sgn2 + sgn3; + float ySideFactor = Math.signum(triangle.get1().y - cy) + Math.signum(triangle.get2().y - cy) + Math.signum(triangle.get3().y - cy); + if ((xSideFactor > -3 || xSideFactor < 3) && ySideFactor < 0) {// the triangle is on the splitting plane + if (sgn1 == 1.0f) { + uvCoordinates[i / 3 * 2] += 1.0f; + } + if (sgn2 == 1.0f) { + uvCoordinates[(i / 3 + 1) * 2] += 1.0f; + } + if (sgn3 == 1.0f) { + uvCoordinates[(i / 3 + 2) * 2] += 1.0f; + } + } + } + return uvCoordinates; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UserUVCollection.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UserUVCollection.java new file mode 100644 index 000000000..083e5f50d --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UserUVCollection.java @@ -0,0 +1,86 @@ +package com.jme3.scene.plugins.blender.textures; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.jme3.math.Vector2f; + +/** + * A collection of UV coordinates. The coords are stored in groups defined by the material index and their UV set name. + * + * @author Kaelthas (Marcin Roguski) + */ +public class UserUVCollection { + /** A map between material number and UV coordinates of mesh that has this material applied. */ + private Map>> uvCoordinates = new HashMap>>(); + /** A map between vertex index and its UV coordinates. */ + private Map> uvsMap = new HashMap>(); + + /** + * Adds a single UV coordinates for a specified vertex index. + * @param materialIndex + * the material index + * @param uvSetName + * the UV set name + * @param uv + * the added UV coordinates + * @param jmeVertexIndex + * the index of the vertex in result jme mesh + */ + public void addUV(int materialIndex, String uvSetName, Vector2f uv, int jmeVertexIndex) { + // first get all UV sets for the specified material ... + LinkedHashMap> uvsForMaterial = uvCoordinates.get(materialIndex); + if (uvsForMaterial == null) { + uvsForMaterial = new LinkedHashMap>(); + uvCoordinates.put(materialIndex, uvsForMaterial); + } + + // ... then fetch the UVS for the specified UV set name ... + List uvsForName = uvsForMaterial.get(uvSetName); + if (uvsForName == null) { + uvsForName = new ArrayList(); + uvsForMaterial.put(uvSetName, uvsForName); + } + + // ... add the UV coordinates to the proper list ... + uvsForName.add(uv); + + // ... and add the mapping of the UV coordinates to a vertex index for the specified UV set + Map uvToVertexIndexMapping = uvsMap.get(uvSetName); + if (uvToVertexIndexMapping == null) { + uvToVertexIndexMapping = new HashMap(); + uvsMap.put(uvSetName, uvToVertexIndexMapping); + } + uvToVertexIndexMapping.put(jmeVertexIndex, uv); + } + + /** + * @param uvSetName + * the name of the UV set + * @param vertexIndex + * the vertex index corresponds to the index in jme mesh and not the original one in blender + * @return + */ + public Vector2f getUVForVertex(String uvSetName, int vertexIndex) { + return uvsMap.get(uvSetName).get(vertexIndex); + } + + /** + * @param materialNumber + * the material number that is appied to the mesh + * @return UV coordinates of vertices that belong to the required mesh part + */ + public LinkedHashMap> getUVCoordinates(int materialNumber) { + return uvCoordinates.get(materialNumber); + } + + /** + * @return indicates if the mesh has UV coordinates + */ + public boolean hasUVCoordinates() { + return uvCoordinates.size() > 0; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java new file mode 100644 index 000000000..5c3504771 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java @@ -0,0 +1,136 @@ +package com.jme3.scene.plugins.blender.textures.blending; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.materials.MaterialHelper; +import com.jme3.texture.Image; +import java.util.logging.Logger; +import jme3tools.converters.MipMapGenerator; + +/** + * An abstract class that contains the basic methods used by the classes that + * will derive from it. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */abstract class AbstractTextureBlender implements TextureBlender { + private static final Logger LOGGER = Logger.getLogger(AbstractTextureBlender.class.getName()); + + protected int flag; + protected boolean negateTexture; + protected int blendType; + protected float[] materialColor; + protected float[] color; + protected float blendFactor; + + public AbstractTextureBlender(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { + this.flag = flag; + this.negateTexture = negateTexture; + this.blendType = blendType; + this.materialColor = materialColor; + this.color = color; + this.blendFactor = blendFactor; + } + + /** + * The method that performs the ramp blending. + * + * @param type + * the blend type + * @param materialRGB + * the rgb value of the material, here the result is stored too + * @param fac + * color affection factor + * @param pixelColor + * the texture color + * @param blenderContext + * the blender context + */ + protected void blendHSV(int type, float[] materialRGB, float fac, float[] pixelColor, BlenderContext blenderContext) { + float oneMinusFactor = 1.0f - fac; + MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + + switch (type) { + case MTEX_BLEND_HUE: {// FIXME: not working well for image textures (works fine for generated textures) + float[] colorTransformResult = new float[3]; + materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult); + if (colorTransformResult[0] != 0.0f) { + float colH = colorTransformResult[0]; + materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult); + materialHelper.hsvToRgb(colH, colorTransformResult[1], colorTransformResult[2], colorTransformResult); + materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * colorTransformResult[0]; + materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * colorTransformResult[1]; + materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * colorTransformResult[2]; + } + break; + } + case MTEX_BLEND_SAT: { + float[] colorTransformResult = new float[3]; + materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult); + float h = colorTransformResult[0]; + float s = colorTransformResult[1]; + float v = colorTransformResult[2]; + if (s != 0.0f) { + materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult); + materialHelper.hsvToRgb(h, oneMinusFactor * s + fac * colorTransformResult[1], v, materialRGB); + } + break; + } + case MTEX_BLEND_VAL: { + float[] rgbToHsv = new float[3]; + float[] colToHsv = new float[3]; + materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv); + materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv); + materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], oneMinusFactor * rgbToHsv[2] + fac * colToHsv[2], materialRGB); + break; + } + case MTEX_BLEND_COLOR: {// FIXME: not working well for image textures (works fine for generated textures) + float[] rgbToHsv = new float[3]; + float[] colToHsv = new float[3]; + materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv); + if (colToHsv[2] != 0) { + materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv); + materialHelper.hsvToRgb(colToHsv[0], colToHsv[1], rgbToHsv[2], rgbToHsv); + materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * rgbToHsv[0]; + materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * rgbToHsv[1]; + materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * rgbToHsv[2]; + } + break; + } + default: + throw new IllegalStateException("Unknown ramp type: " + type); + } + } + + public void copyBlendingData(TextureBlender textureBlender) { + if (textureBlender instanceof AbstractTextureBlender) { + this.flag = ((AbstractTextureBlender) textureBlender).flag; + this.negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture; + this.blendType = ((AbstractTextureBlender) textureBlender).blendType; + this.materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone(); + this.color = ((AbstractTextureBlender) textureBlender).color.clone(); + this.blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor; + } else { + LOGGER.warning("Cannot copy blending data from other types than " + this.getClass()); + } + } + + /** + * The method prepares images for blending. It generates mipmaps if one of + * the images has them defined and the other one has not. + * + * @param target + * the image where the blending result is stored + * @param source + * the image that is being read only + */ + protected void prepareImagesForBlending(Image target, Image source) { + LOGGER.fine("Generating mipmaps if needed!"); + boolean targetHasMipmaps = target == null ? false : target.getMipMapSizes() != null && target.getMipMapSizes().length > 0; + boolean sourceHasMipmaps = source == null ? false : source.getMipMapSizes() != null && source.getMipMapSizes().length > 0; + if (target != null && !targetHasMipmaps && sourceHasMipmaps) { + MipMapGenerator.generateMipMaps(target); + } else if (source != null && !sourceHasMipmaps && targetHasMipmaps) { + MipMapGenerator.generateMipMaps(source); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java new file mode 100644 index 000000000..7ba2b98f2 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java @@ -0,0 +1,53 @@ +package com.jme3.scene.plugins.blender.textures.blending; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.texture.Image; + +/** + * An interface for texture blending classes (the classes that mix the texture + * pixels with the material colors). + * + * @author Marcin Roguski (Kaelthas) + */ +public interface TextureBlender { + // types of blending + int MTEX_BLEND = 0; + int MTEX_MUL = 1; + int MTEX_ADD = 2; + int MTEX_SUB = 3; + int MTEX_DIV = 4; + int MTEX_DARK = 5; + int MTEX_DIFF = 6; + int MTEX_LIGHT = 7; + int MTEX_SCREEN = 8; + int MTEX_OVERLAY = 9; + int MTEX_BLEND_HUE = 10; + int MTEX_BLEND_SAT = 11; + int MTEX_BLEND_VAL = 12; + int MTEX_BLEND_COLOR = 13; + int MTEX_NUM_BLENDTYPES = 14; + + /** + * This method blends the given texture with material color and the defined + * color in 'map to' panel. As a result of this method a new texture is + * created. The input texture is NOT. + * + * @param image + * the image we use in blending + * @param baseImage + * the texture that is underneath the current texture (its pixels + * will be used instead of material color) + * @param blenderContext + * the blender context + * @return new image that was created after the blending + */ + Image blend(Image image, Image baseImage, BlenderContext blenderContext); + + /** + * Copies blending data. Used for blending type format changing. + * + * @param textureBlender + * the blend data that should be copied + */ + void copyBlendingData(TextureBlender textureBlender); +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java new file mode 100644 index 000000000..9ebf4eeac --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.blending; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * The class that is responsible for blending the following texture types:
  • RGBA8
  • ABGR8
  • BGR8
  • RGB8 Not yet supported (but will be):
  • ARGB4444:
  • RGB10:
  • RGB111110F:
  • RGB16:
  • RGB16F:
  • RGB16F_to_RGB111110F:
  • RGB16F_to_RGB9E5:
  • RGB32F:
  • RGB565:
  • RGB5A1:
  • RGB9E5:
  • RGBA16:
  • RGBA16F + * @author Marcin Roguski (Kaelthas) + */ +public class TextureBlenderAWT extends AbstractTextureBlender { + public TextureBlenderAWT(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { + super(flag, negateTexture, blendType, materialColor, color, blendFactor); + } + + @Override + public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { + this.prepareImagesForBlending(image, baseImage); + + float[] pixelColor = new float[] { color[0], color[1], color[2], 1.0f }; + Format format = image.getFormat(); + + PixelInputOutput basePixelIO = null, pixelReader = PixelIOFactory.getPixelIO(format); + TexturePixel basePixel = null, pixel = new TexturePixel(); + float[] materialColor = this.materialColor; + if (baseImage != null) { + basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); + materialColor = new float[this.materialColor.length]; + basePixel = new TexturePixel(); + } + + int width = image.getWidth(); + int height = image.getHeight(); + int depth = image.getDepth(); + if (depth == 0) { + depth = 1; + } + int bytesPerPixel = image.getFormat().getBitsPerPixel() >> 3; + ArrayList dataArray = new ArrayList(depth); + + float[] resultPixel = new float[4]; + for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { + ByteBuffer data = image.getData(dataLayerIndex); + data.rewind(); + int imagePixelCount = data.limit() / bytesPerPixel; + ByteBuffer newData = BufferUtils.createByteBuffer(imagePixelCount * 4); + + int dataIndex = 0, x = 0, y = 0, index = 0; + while (index < data.limit()) { + // getting the proper material color if the base texture is applied + if (basePixelIO != null) { + basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y); + basePixel.toRGBA(materialColor); + ++x; + if (x >= width) { + x = 0; + ++y; + } + } + + // reading the current texture's pixel + pixelReader.read(image, dataLayerIndex, pixel, index); + index += bytesPerPixel; + pixel.toRGBA(pixelColor); + if (negateTexture) { + pixel.negate(); + } + + this.blendPixel(resultPixel, materialColor, pixelColor, blenderContext); + newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); + newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); + newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); + newData.put(dataIndex++, (byte) (pixelColor[3] * 255.0f)); + } + dataArray.add(newData); + } + + Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0)); + if (image.getMipMapSizes() != null) { + result.setMipMapSizes(image.getMipMapSizes().clone()); + } + return result; + } + + /** + * This method blends the single pixel depending on the blending type. + * + * @param result + * the result pixel + * @param materialColor + * the material color + * @param pixelColor + * the pixel color + * @param blenderContext + * the blender context + */ + protected void blendPixel(float[] result, float[] materialColor, float[] pixelColor, BlenderContext blenderContext) { + float blendFactor = this.blendFactor * pixelColor[3]; + float oneMinusFactor = 1.0f - blendFactor, col; + + switch (blendType) { + case MTEX_BLEND: + result[0] = blendFactor * pixelColor[0] + oneMinusFactor * materialColor[0]; + result[1] = blendFactor * pixelColor[1] + oneMinusFactor * materialColor[1]; + result[2] = blendFactor * pixelColor[2] + oneMinusFactor * materialColor[2]; + break; + case MTEX_MUL: + result[0] = (oneMinusFactor + blendFactor * materialColor[0]) * pixelColor[0]; + result[1] = (oneMinusFactor + blendFactor * materialColor[1]) * pixelColor[1]; + result[2] = (oneMinusFactor + blendFactor * materialColor[2]) * pixelColor[2]; + break; + case MTEX_DIV: + if (pixelColor[0] != 0.0) { + result[0] = (oneMinusFactor * materialColor[0] + blendFactor * materialColor[0] / pixelColor[0]) * 0.5f; + } + if (pixelColor[1] != 0.0) { + result[1] = (oneMinusFactor * materialColor[1] + blendFactor * materialColor[1] / pixelColor[1]) * 0.5f; + } + if (pixelColor[2] != 0.0) { + result[2] = (oneMinusFactor * materialColor[2] + blendFactor * materialColor[2] / pixelColor[2]) * 0.5f; + } + break; + case MTEX_SCREEN: + result[0] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]); + result[1] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]); + result[2] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]); + break; + case MTEX_OVERLAY: + if (materialColor[0] < 0.5f) { + result[0] = pixelColor[0] * (oneMinusFactor + 2.0f * blendFactor * materialColor[0]); + } else { + result[0] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]); + } + if (materialColor[1] < 0.5f) { + result[1] = pixelColor[1] * (oneMinusFactor + 2.0f * blendFactor * materialColor[1]); + } else { + result[1] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]); + } + if (materialColor[2] < 0.5f) { + result[2] = pixelColor[2] * (oneMinusFactor + 2.0f * blendFactor * materialColor[2]); + } else { + result[2] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]); + } + break; + case MTEX_SUB: + result[0] = materialColor[0] - blendFactor * pixelColor[0]; + result[1] = materialColor[1] - blendFactor * pixelColor[1]; + result[2] = materialColor[2] - blendFactor * pixelColor[2]; + result[0] = FastMath.clamp(result[0], 0.0f, 1.0f); + result[1] = FastMath.clamp(result[1], 0.0f, 1.0f); + result[2] = FastMath.clamp(result[2], 0.0f, 1.0f); + break; + case MTEX_ADD: + result[0] = (blendFactor * pixelColor[0] + materialColor[0]) * 0.5f; + result[1] = (blendFactor * pixelColor[1] + materialColor[1]) * 0.5f; + result[2] = (blendFactor * pixelColor[2] + materialColor[2]) * 0.5f; + break; + case MTEX_DIFF: + result[0] = oneMinusFactor * materialColor[0] + blendFactor * Math.abs(materialColor[0] - pixelColor[0]); + result[1] = oneMinusFactor * materialColor[1] + blendFactor * Math.abs(materialColor[1] - pixelColor[1]); + result[2] = oneMinusFactor * materialColor[2] + blendFactor * Math.abs(materialColor[2] - pixelColor[2]); + break; + case MTEX_DARK: + col = blendFactor * pixelColor[0]; + result[0] = col < materialColor[0] ? col : materialColor[0]; + col = blendFactor * pixelColor[1]; + result[1] = col < materialColor[1] ? col : materialColor[1]; + col = blendFactor * pixelColor[2]; + result[2] = col < materialColor[2] ? col : materialColor[2]; + break; + case MTEX_LIGHT: + col = blendFactor * pixelColor[0]; + result[0] = col > materialColor[0] ? col : materialColor[0]; + col = blendFactor * pixelColor[1]; + result[1] = col > materialColor[1] ? col : materialColor[1]; + col = blendFactor * pixelColor[2]; + result[2] = col > materialColor[2] ? col : materialColor[2]; + break; + case MTEX_BLEND_HUE: + case MTEX_BLEND_SAT: + case MTEX_BLEND_VAL: + case MTEX_BLEND_COLOR: + System.arraycopy(materialColor, 0, result, 0, 3); + this.blendHSV(blendType, result, blendFactor, pixelColor, blenderContext); + break; + default: + throw new IllegalStateException("Unknown blend type: " + blendType); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java new file mode 100644 index 000000000..f6a2e8a91 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java @@ -0,0 +1,120 @@ +package com.jme3.scene.plugins.blender.textures.blending; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import jme3tools.converters.RGB565; + +/** + * The class that is responsible for blending the following texture types:
  • DXT1
  • DXT1A
  • DXT3
  • DXT5 + * @author Marcin Roguski (Kaelthas) + */ +public class TextureBlenderDDS extends TextureBlenderAWT { + public TextureBlenderDDS(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { + super(flag, negateTexture, blendType, materialColor, color, blendFactor); + } + + @Override + public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { + this.prepareImagesForBlending(image, baseImage); + + Format format = image.getFormat(); + int width = image.getWidth(); + int height = image.getHeight(); + int depth = image.getDepth(); + if (depth == 0) { + depth = 1; + } + ArrayList dataArray = new ArrayList(depth); + + PixelInputOutput basePixelIO = null; + float[][] compressedMaterialColor = null; + TexturePixel[] baseTextureColors = null; + if (baseImage != null) { + basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); + compressedMaterialColor = new float[2][4]; + baseTextureColors = new TexturePixel[] { new TexturePixel(), new TexturePixel() }; + } + + float[] resultPixel = new float[4]; + float[] pixelColor = new float[4]; + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel() }; + int baseXTexelIndex = 0, baseYTexelIndex = 0; + float[] alphas = new float[] { 1, 1 }; + for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { + ByteBuffer data = image.getData(dataLayerIndex); + data.rewind(); + ByteBuffer newData = BufferUtils.createByteBuffer(data.remaining()); + while (data.hasRemaining()) { + if (format == Format.DXT3) { + long alpha = data.getLong(); + // get alpha for first and last pixel that is compressed in the texel + byte alpha0 = (byte) (alpha << 4 & 0xFF); + byte alpha1 = (byte) (alpha >> 60 & 0xFF); + alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f; + alphas[1] = alpha1 >= 0 ? alpha1 / 255.0f : 1.0f - ~alpha1 / 255.0f; + newData.putLong(alpha); + } else if (format == Format.DXT5) { + byte alpha0 = data.get(); + byte alpha1 = data.get(); + alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f; + alphas[1] = alpha1 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f; + newData.put(alpha0); + newData.put(alpha1); + // only read the next 6 bytes (these are alpha indexes) + newData.putInt(data.getInt()); + newData.putShort(data.getShort()); + } + int col0 = RGB565.RGB565_to_ARGB8(data.getShort()); + int col1 = RGB565.RGB565_to_ARGB8(data.getShort()); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + // compressing 16 pixels from the base texture as if they belonged to a texel + if (baseImage != null) { + // reading pixels (first and last of the 16 colors array) + basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[0], baseXTexelIndex << 2, baseYTexelIndex << 2);// first pixel + basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[1], baseXTexelIndex << 2 + 4, baseYTexelIndex << 2 + 4);// last pixel + baseTextureColors[0].toRGBA(compressedMaterialColor[0]); + baseTextureColors[1].toRGBA(compressedMaterialColor[1]); + } + + // blending colors + for (int i = 0; i < colors.length; ++i) { + if (negateTexture) { + colors[i].negate(); + } + colors[i].toRGBA(pixelColor); + pixelColor[3] = alphas[i]; + this.blendPixel(resultPixel, compressedMaterialColor != null ? compressedMaterialColor[i] : materialColor, pixelColor, blenderContext); + colors[i].fromARGB(1, resultPixel[0], resultPixel[1], resultPixel[2]); + int argb8 = colors[i].toARGB8(); + short rgb565 = RGB565.ARGB8_to_RGB565(argb8); + newData.putShort(rgb565); + } + + // just copy the remaining 4 bytes of the current texel + newData.putInt(data.getInt()); + + ++baseXTexelIndex; + if (baseXTexelIndex > image.getWidth() >> 2) { + baseXTexelIndex = 0; + ++baseYTexelIndex; + } + } + dataArray.add(newData); + } + + Image result = dataArray.size() > 1 ? new Image(format, width, height, depth, dataArray) : new Image(format, width, height, dataArray.get(0)); + if (image.getMipMapSizes() != null) { + result.setMipMapSizes(image.getMipMapSizes().clone()); + } + return result; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java new file mode 100644 index 000000000..88262b8a3 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.blending; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class creates the texture blending class depending on the texture type. + * + * @author Marcin Roguski (Kaelthas) + */ +public class TextureBlenderFactory { + private static final Logger LOGGER = Logger.getLogger(TextureBlenderFactory.class.getName()); + + /** + * A blender that does not change the image. Used for none supported image types. + */ + private static final TextureBlender NON_CHANGING_BLENDER = new TextureBlender() { + @Override + public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { + return image; + } + + @Override + public void copyBlendingData(TextureBlender textureBlender) { } + }; + + /** + * This method creates the blending class. + * + * @param format + * the texture format + * @return texture blending class + */ + @SuppressWarnings("deprecation") + public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) { + switch (format) { + case Luminance8: + case Luminance8Alpha8: + case Luminance16: + case Luminance16Alpha16: + case Luminance16F: + case Luminance16FAlpha16F: + case Luminance32F: + return new TextureBlenderLuminance(flag, negate, blendType, materialColor, color, colfac); + case RGBA8: + case ABGR8: + case BGR8: + case RGB8: + case RGB10: + case RGB111110F: + case RGB16: + case RGB16F: + case RGB16F_to_RGB111110F: + case RGB16F_to_RGB9E5: + case RGB32F: + case RGB565: + case RGB5A1: + case RGB9E5: + case RGBA16: + case RGBA16F: + case RGBA32F: + return new TextureBlenderAWT(flag, negate, blendType, materialColor, color, colfac); + case DXT1: + case DXT1A: + case DXT3: + case DXT5: + return new TextureBlenderDDS(flag, negate, blendType, materialColor, color, colfac); + case Alpha16: + case Alpha8: + case ARGB4444: + case Depth: + case Depth16: + case Depth24: + case Depth32: + case Depth32F: + case Intensity16: + case Intensity8: + case LATC: + case LTC: + case Depth24Stencil8: + LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}. Returning a blender that does not change the texture.", format); + return NON_CHANGING_BLENDER; + default: + throw new IllegalStateException("Unknown image format type: " + format); + } + } + + /** + * This method changes the image format in the texture blender. + * + * @param format + * the new image format + * @param textureBlender + * the texture blender that will be altered + * @return altered texture blender + */ + public static TextureBlender alterTextureType(Format format, TextureBlender textureBlender) { + TextureBlender result = TextureBlenderFactory.createTextureBlender(format, 0, false, 0, null, null, 0); + result.copyBlendingData(textureBlender); + return result; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java new file mode 100644 index 000000000..eac0ae0d5 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java @@ -0,0 +1,241 @@ +package com.jme3.scene.plugins.blender.textures.blending; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; +import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The class that is responsible for blending the following texture types:
  • Luminance8
  • Luminance8Alpha8 Not yet supported (but will be):
  • Luminance16:
  • Luminance16Alpha16:
  • Luminance16F:
  • Luminance16FAlpha16F:
  • Luminance32F: + * @author Marcin Roguski (Kaelthas) + */ +public class TextureBlenderLuminance extends AbstractTextureBlender { + private static final Logger LOGGER = Logger.getLogger(TextureBlenderLuminance.class.getName()); + + public TextureBlenderLuminance(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { + super(flag, negateTexture, blendType, materialColor, color, blendFactor); + } + + public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { + this.prepareImagesForBlending(image, baseImage); + + Format format = image.getFormat(); + PixelInputOutput basePixelIO = null; + TexturePixel basePixel = null; + float[] materialColor = this.materialColor; + if (baseImage != null) { + basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); + materialColor = new float[this.materialColor.length]; + basePixel = new TexturePixel(); + } + + int width = image.getWidth(); + int height = image.getHeight(); + int depth = image.getDepth(); + if (depth == 0) { + depth = 1; + } + ArrayList dataArray = new ArrayList(depth); + + float[] resultPixel = new float[4]; + float[] tinAndAlpha = new float[2]; + for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { + ByteBuffer data = image.getData(dataLayerIndex); + data.rewind(); + ByteBuffer newData = BufferUtils.createByteBuffer(data.limit() * 4); + + int dataIndex = 0, x = 0, y = 0; + while (data.hasRemaining()) { + // getting the proper material color if the base texture is applied + if (basePixelIO != null) { + basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y); + basePixel.toRGBA(materialColor); + + ++x; + if (x >= width) { + x = 0; + ++y; + } + } + + this.getTinAndAlpha(data, format, negateTexture, tinAndAlpha); + this.blendPixel(resultPixel, materialColor, color, tinAndAlpha[0], blendFactor, blendType, blenderContext); + newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); + newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); + newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); + newData.put(dataIndex++, (byte) (tinAndAlpha[1] * 255.0f)); + } + dataArray.add(newData); + } + + Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0)); + if (image.getMipMapSizes() != null) { + result.setMipMapSizes(image.getMipMapSizes().clone()); + } + return result; + } + + /** + * This method return texture intensity and alpha value. + * + * @param data + * the texture data + * @param imageFormat + * the image format + * @param neg + * indicates if the texture is negated + * @param result + * the table (2 elements) where the result is being stored + */ + protected void getTinAndAlpha(ByteBuffer data, Format imageFormat, boolean neg, float[] result) { + byte pixelValue = data.get();// at least one byte is always taken + float firstPixelValue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; + switch (imageFormat) { + case Luminance8: + result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue; + result[1] = 1.0f; + break; + case Luminance8Alpha8: + result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue; + pixelValue = data.get(); + result[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; + break; + case Luminance16: + case Luminance16Alpha16: + case Luminance16F: + case Luminance16FAlpha16F: + case Luminance32F: + LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", imageFormat); + break; + default: + throw new IllegalStateException("Invalid image format type for Luminance texture blender: " + imageFormat); + } + } + + /** + * This method blends the texture with an appropriate color. + * + * @param result + * the result color (variable 'in' in blender source code) + * @param materialColor + * the texture color (variable 'out' in blender source coude) + * @param color + * the previous color (variable 'tex' in blender source code) + * @param textureIntensity + * texture intensity (variable 'fact' in blender source code) + * @param textureFactor + * texture affection factor (variable 'facg' in blender source + * code) + * @param blendtype + * the blend type + * @param blenderContext + * the blender context + */ + protected void blendPixel(float[] result, float[] materialColor, float[] color, float textureIntensity, float textureFactor, int blendtype, BlenderContext blenderContext) { + float oneMinusFactor, col; + textureIntensity *= textureFactor; + + switch (blendtype) { + case MTEX_BLEND: + oneMinusFactor = 1.0f - textureIntensity; + result[0] = textureIntensity * color[0] + oneMinusFactor * materialColor[0]; + result[1] = textureIntensity * color[1] + oneMinusFactor * materialColor[1]; + result[2] = textureIntensity * color[2] + oneMinusFactor * materialColor[2]; + break; + case MTEX_MUL: + oneMinusFactor = 1.0f - textureFactor; + result[0] = (oneMinusFactor + textureIntensity * materialColor[0]) * color[0]; + result[1] = (oneMinusFactor + textureIntensity * materialColor[1]) * color[1]; + result[2] = (oneMinusFactor + textureIntensity * materialColor[2]) * color[2]; + break; + case MTEX_DIV: + oneMinusFactor = 1.0f - textureIntensity; + if (color[0] != 0.0) { + result[0] = (oneMinusFactor * materialColor[0] + textureIntensity * materialColor[0] / color[0]) * 0.5f; + } + if (color[1] != 0.0) { + result[1] = (oneMinusFactor * materialColor[1] + textureIntensity * materialColor[1] / color[1]) * 0.5f; + } + if (color[2] != 0.0) { + result[2] = (oneMinusFactor * materialColor[2] + textureIntensity * materialColor[2] / color[2]) * 0.5f; + } + break; + case MTEX_SCREEN: + oneMinusFactor = 1.0f - textureFactor; + result[0] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); + result[1] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); + result[2] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); + break; + case MTEX_OVERLAY: + oneMinusFactor = 1.0f - textureFactor; + if (materialColor[0] < 0.5f) { + result[0] = color[0] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[0]); + } else { + result[0] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); + } + if (materialColor[1] < 0.5f) { + result[1] = color[1] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[1]); + } else { + result[1] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); + } + if (materialColor[2] < 0.5f) { + result[2] = color[2] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[2]); + } else { + result[2] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); + } + break; + case MTEX_SUB: + result[0] = materialColor[0] - textureIntensity * color[0]; + result[1] = materialColor[1] - textureIntensity * color[1]; + result[2] = materialColor[2] - textureIntensity * color[2]; + result[0] = FastMath.clamp(result[0], 0.0f, 1.0f); + result[1] = FastMath.clamp(result[1], 0.0f, 1.0f); + result[2] = FastMath.clamp(result[2], 0.0f, 1.0f); + break; + case MTEX_ADD: + result[0] = (textureIntensity * color[0] + materialColor[0]) * 0.5f; + result[1] = (textureIntensity * color[1] + materialColor[1]) * 0.5f; + result[2] = (textureIntensity * color[2] + materialColor[2]) * 0.5f; + break; + case MTEX_DIFF: + oneMinusFactor = 1.0f - textureIntensity; + result[0] = oneMinusFactor * materialColor[0] + textureIntensity * Math.abs(materialColor[0] - color[0]); + result[1] = oneMinusFactor * materialColor[1] + textureIntensity * Math.abs(materialColor[1] - color[1]); + result[2] = oneMinusFactor * materialColor[2] + textureIntensity * Math.abs(materialColor[2] - color[2]); + break; + case MTEX_DARK: + col = textureIntensity * color[0]; + result[0] = col < materialColor[0] ? col : materialColor[0]; + col = textureIntensity * color[1]; + result[1] = col < materialColor[1] ? col : materialColor[1]; + col = textureIntensity * color[2]; + result[2] = col < materialColor[2] ? col : materialColor[2]; + break; + case MTEX_LIGHT: + col = textureIntensity * color[0]; + result[0] = col > materialColor[0] ? col : materialColor[0]; + col = textureIntensity * color[1]; + result[1] = col > materialColor[1] ? col : materialColor[1]; + col = textureIntensity * color[2]; + result[2] = col > materialColor[2] ? col : materialColor[2]; + break; + case MTEX_BLEND_HUE: + case MTEX_BLEND_SAT: + case MTEX_BLEND_VAL: + case MTEX_BLEND_COLOR: + System.arraycopy(materialColor, 0, result, 0, 3); + this.blendHSV(blendtype, result, textureIntensity, color, blenderContext); + break; + default: + throw new IllegalStateException("Unknown blend type: " + blendtype); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/NoiseGenerator.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/NoiseGenerator.java new file mode 100644 index 000000000..f3b1c30b9 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/NoiseGenerator.java @@ -0,0 +1,782 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.generating; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.textures.generating.TextureGeneratorMusgrave.MusgraveData; + +/** + * This generator is responsible for creating various noises used to create + * generated textures loaded from blender. + * It is only used by TextureHelper. + * Take note that these functions are not thread safe. + * @author Marcin Roguski (Kaelthas) + */ +/* package */class NoiseGenerator { + private static final Logger LOGGER = Logger.getLogger(NoiseGenerator.class.getName()); + + // tex->stype + protected static final int TEX_PLASTIC = 0; + protected static final int TEX_WALLIN = 1; + protected static final int TEX_WALLOUT = 2; + + // musgrave stype + protected static final int TEX_MFRACTAL = 0; + protected static final int TEX_RIDGEDMF = 1; + protected static final int TEX_HYBRIDMF = 2; + protected static final int TEX_FBM = 3; + protected static final int TEX_HTERRAIN = 4; + + // keyblock->type + protected static final int KEY_LINEAR = 0; + protected static final int KEY_CARDINAL = 1; + protected static final int KEY_BSPLINE = 2; + + // CONSTANTS (read from file) + protected static float[] hashpntf; + protected static short[] hash; + protected static float[] hashvectf; + protected static short[] p; + protected static float[][] g; + + /** + * Constructor. Loads the constants needed for computations. They are exactly like the ones the blender uses. Each + * deriving class should override this method and load its own constraints. + */ + public NoiseGenerator() { + LOGGER.fine("Loading noise constants."); + InputStream is = NoiseGenerator.class.getResourceAsStream("noiseconstants.dat"); + try { + ObjectInputStream ois = new ObjectInputStream(is); + hashpntf = (float[]) ois.readObject(); + hash = (short[]) ois.readObject(); + hashvectf = (float[]) ois.readObject(); + p = (short[]) ois.readObject(); + g = (float[][]) ois.readObject(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); + } catch (ClassNotFoundException e) { + assert false : "Constants' classes should be arrays of primitive types, so they are ALWAYS known!"; + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + LOGGER.log(Level.WARNING, e.getLocalizedMessage()); + } + } + } + } + + protected static Map noiseFunctions = new HashMap(); + static { + noiseFunctions.put(Integer.valueOf(0), new NoiseFunction() { + // originalBlenderNoise + public float execute(float x, float y, float z) { + return NoiseFunctions.originalBlenderNoise(x, y, z); + } + + public float executeSigned(float x, float y, float z) { + return 2.0f * NoiseFunctions.originalBlenderNoise(x, y, z) - 1.0f; + } + }); + noiseFunctions.put(Integer.valueOf(1), new NoiseFunction() { + // orgPerlinNoise + public float execute(float x, float y, float z) { + return 0.5f + 0.5f * NoiseFunctions.noise3Perlin(x, y, z); + } + + public float executeSigned(float x, float y, float z) { + return NoiseFunctions.noise3Perlin(x, y, z); + } + }); + noiseFunctions.put(Integer.valueOf(2), new NoiseFunction() { + // newPerlin + public float execute(float x, float y, float z) { + return 0.5f + 0.5f * NoiseFunctions.newPerlin(x, y, z); + } + + public float executeSigned(float x, float y, float z) { + return this.execute(x, y, z); + } + }); + noiseFunctions.put(Integer.valueOf(3), new NoiseFunction() { + private final float[] da = new float[4]; + private final float[] pa = new float[12]; + + // voronoi_F1 + public float execute(float x, float y, float z) { + NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); + return da[0]; + } + + public float executeSigned(float x, float y, float z) { + NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); + return 2.0f * da[0] - 1.0f; + } + }); + noiseFunctions.put(Integer.valueOf(4), new NoiseFunction() { + private final float[] da = new float[4]; + private final float[] pa = new float[12]; + + // voronoi_F2 + public float execute(float x, float y, float z) { + NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); + return da[1]; + } + + public float executeSigned(float x, float y, float z) { + NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); + return 2.0f * da[1] - 1.0f; + } + }); + noiseFunctions.put(Integer.valueOf(5), new NoiseFunction() { + private final float[] da = new float[4]; + private final float[] pa = new float[12]; + + // voronoi_F3 + public float execute(float x, float y, float z) { + NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); + return da[2]; + } + + public float executeSigned(float x, float y, float z) { + NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); + return 2.0f * da[2] - 1.0f; + } + }); + noiseFunctions.put(Integer.valueOf(6), new NoiseFunction() { + private final float[] da = new float[4]; + private final float[] pa = new float[12]; + + // voronoi_F4 + public float execute(float x, float y, float z) { + NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); + return da[3]; + } + + public float executeSigned(float x, float y, float z) { + NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); + return 2.0f * da[3] - 1.0f; + } + }); + noiseFunctions.put(Integer.valueOf(7), new NoiseFunction() { + private final float[] da = new float[4]; + private final float[] pa = new float[12]; + + // voronoi_F1F2 + public float execute(float x, float y, float z) { + NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); + return da[1] - da[0]; + } + + public float executeSigned(float x, float y, float z) { + NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); + return 2.0f * (da[1] - da[0]) - 1.0f; + } + }); + noiseFunctions.put(Integer.valueOf(8), new NoiseFunction() { + private final NoiseFunction voronoiF1F2NoiseFunction = noiseFunctions.get(Integer.valueOf(7)); + + // voronoi_Cr + public float execute(float x, float y, float z) { + float t = 10 * voronoiF1F2NoiseFunction.execute(x, y, z); + return t > 1.0f ? 1.0f : t; + } + + public float executeSigned(float x, float y, float z) { + float t = 10.0f * voronoiF1F2NoiseFunction.execute(x, y, z); + return t > 1.0f ? 1.0f : 2.0f * t - 1.0f; + } + }); + noiseFunctions.put(Integer.valueOf(14), new NoiseFunction() { + // cellNoise + public float execute(float x, float y, float z) { + int xi = (int) FastMath.floor(x); + int yi = (int) FastMath.floor(y); + int zi = (int) FastMath.floor(z); + long n = xi + yi * 1301 + zi * 314159; + n ^= n << 13; + return (n * (n * n * 15731 + 789221) + 1376312589) / 4294967296.0f; + } + + public float executeSigned(float x, float y, float z) { + return 2.0f * this.execute(x, y, z) - 1.0f; + } + }); + } + /** Distance metrics for voronoi. e parameter only used in Minkovsky. */ + protected static Map distanceFunctions = new HashMap(); + private static DistanceFunction NATURAL_DISTANCE_FUNCTION; // often used in noise functions, se I store it here to avoid fetching it every time + static { + distanceFunctions.put(Integer.valueOf(0), new DistanceFunction() { + // real distance + public float execute(float x, float y, float z, float e) { + return (float) Math.sqrt(x * x + y * y + z * z); + } + }); + distanceFunctions.put(Integer.valueOf(1), new DistanceFunction() { + // distance squared + public float execute(float x, float y, float z, float e) { + return x * x + y * y + z * z; + } + }); + distanceFunctions.put(Integer.valueOf(2), new DistanceFunction() { + // manhattan/taxicab/cityblock distance + public float execute(float x, float y, float z, float e) { + return FastMath.abs(x) + FastMath.abs(y) + FastMath.abs(z); + } + }); + distanceFunctions.put(Integer.valueOf(3), new DistanceFunction() { + // Chebychev + public float execute(float x, float y, float z, float e) { + x = FastMath.abs(x); + y = FastMath.abs(y); + z = FastMath.abs(z); + float t = x > y ? x : y; + return z > t ? z : t; + } + }); + distanceFunctions.put(Integer.valueOf(4), new DistanceFunction() { + // Minkovsky, preset exponent 0.5 (MinkovskyH) + public float execute(float x, float y, float z, float e) { + float d = (float) (Math.sqrt(FastMath.abs(x)) + Math.sqrt(FastMath.abs(y)) + Math.sqrt(FastMath.abs(z))); + return d * d; + } + }); + distanceFunctions.put(Integer.valueOf(5), new DistanceFunction() { + // Minkovsky, preset exponent 0.25 (Minkovsky4) + public float execute(float x, float y, float z, float e) { + x *= x; + y *= y; + z *= z; + return (float) Math.sqrt(Math.sqrt(x * x + y * y + z * z)); + } + }); + distanceFunctions.put(Integer.valueOf(6), new DistanceFunction() { + // Minkovsky, general case + public float execute(float x, float y, float z, float e) { + return (float) Math.pow(Math.pow(FastMath.abs(x), e) + Math.pow(FastMath.abs(y), e) + Math.pow(FastMath.abs(z), e), 1.0f / e); + } + }); + NATURAL_DISTANCE_FUNCTION = distanceFunctions.get(Integer.valueOf(0)); + } + + protected static Map musgraveFunctions = new HashMap(); + static { + musgraveFunctions.put(Integer.valueOf(TEX_MFRACTAL), new MusgraveFunction() { + + public float execute(MusgraveData musgraveData, float x, float y, float z) { + float rmd, value = 1.0f, pwr = 1.0f, pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h); + + for (int i = 0; i < (int) musgraveData.octaves; ++i) { + value *= pwr * musgraveData.noiseFunction.executeSigned(x, y, z) + 1.0f; + pwr *= pwHL; + x *= musgraveData.lacunarity; + y *= musgraveData.lacunarity; + z *= musgraveData.lacunarity; + } + rmd = musgraveData.octaves - FastMath.floor(musgraveData.octaves); + if (rmd != 0.0f) { + value *= rmd * musgraveData.noiseFunction.executeSigned(x, y, z) * pwr + 1.0f; + } + return value; + } + }); + musgraveFunctions.put(Integer.valueOf(TEX_RIDGEDMF), new MusgraveFunction() { + + public float execute(MusgraveData musgraveData, float x, float y, float z) { + float result, signal, weight; + float pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h); + float pwr = pwHL; + + signal = musgraveData.offset - FastMath.abs(musgraveData.noiseFunction.executeSigned(x, y, z)); + signal *= signal; + result = signal; + weight = 1.0f; + + for (int i = 1; i < (int) musgraveData.octaves; ++i) { + x *= musgraveData.lacunarity; + y *= musgraveData.lacunarity; + z *= musgraveData.lacunarity; + weight = signal * musgraveData.gain; + if (weight > 1.0f) { + weight = 1.0f; + } else if (weight < 0.0) { + weight = 0.0f; + } + signal = musgraveData.offset - FastMath.abs(musgraveData.noiseFunction.executeSigned(x, y, z)); + signal *= signal; + signal *= weight; + result += signal * pwr; + pwr *= pwHL; + } + return result; + } + }); + musgraveFunctions.put(Integer.valueOf(TEX_HYBRIDMF), new MusgraveFunction() { + + public float execute(MusgraveData musgraveData, float x, float y, float z) { + float result, signal, weight, rmd; + float pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h); + float pwr = pwHL; + + result = musgraveData.noiseFunction.executeSigned(x, y, z) + musgraveData.offset; + weight = musgraveData.gain * result; + x *= musgraveData.lacunarity; + y *= musgraveData.lacunarity; + z *= musgraveData.lacunarity; + + for (int i = 1; weight > 0.001f && i < (int) musgraveData.octaves; ++i) { + if (weight > 1.0f) { + weight = 1.0f; + } + signal = (musgraveData.noiseFunction.executeSigned(x, y, z) + musgraveData.offset) * pwr; + pwr *= pwHL; + result += weight * signal; + weight *= musgraveData.gain * signal; + x *= musgraveData.lacunarity; + y *= musgraveData.lacunarity; + z *= musgraveData.lacunarity; + } + + rmd = musgraveData.octaves - FastMath.floor(musgraveData.octaves); + if (rmd != 0.0f) { + result += rmd * (musgraveData.noiseFunction.executeSigned(x, y, z) + musgraveData.offset) * pwr; + } + return result; + } + }); + musgraveFunctions.put(Integer.valueOf(TEX_FBM), new MusgraveFunction() { + + public float execute(MusgraveData musgraveData, float x, float y, float z) { + float rmd, value = 0.0f, pwr = 1.0f, pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h); + + for (int i = 0; i < (int) musgraveData.octaves; ++i) { + value += musgraveData.noiseFunction.executeSigned(x, y, z) * pwr; + pwr *= pwHL; + x *= musgraveData.lacunarity; + y *= musgraveData.lacunarity; + z *= musgraveData.lacunarity; + } + + rmd = musgraveData.octaves - FastMath.floor(musgraveData.octaves); + if (rmd != 0.f) { + value += rmd * musgraveData.noiseFunction.executeSigned(x, y, z) * pwr; + } + return value; + } + }); + musgraveFunctions.put(Integer.valueOf(TEX_HTERRAIN), new MusgraveFunction() { + + public float execute(MusgraveData musgraveData, float x, float y, float z) { + float value, increment, rmd; + float pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h); + float pwr = pwHL; + + value = musgraveData.offset + musgraveData.noiseFunction.executeSigned(x, y, z); + x *= musgraveData.lacunarity; + y *= musgraveData.lacunarity; + z *= musgraveData.lacunarity; + + for (int i = 1; i < (int) musgraveData.octaves; ++i) { + increment = (musgraveData.noiseFunction.executeSigned(x, y, z) + musgraveData.offset) * pwr * value; + value += increment; + pwr *= pwHL; + x *= musgraveData.lacunarity; + y *= musgraveData.lacunarity; + z *= musgraveData.lacunarity; + } + + rmd = musgraveData.octaves - FastMath.floor(musgraveData.octaves); + if (rmd != 0.0) { + increment = (musgraveData.noiseFunction.executeSigned(x, y, z) + musgraveData.offset) * pwr * value; + value += rmd * increment; + } + return value; + } + }); + } + + public static class NoiseFunctions { + public static float noise(float x, float y, float z, float noiseSize, int noiseDepth, NoiseFunction noiseFunction, boolean isHard) { + if (noiseSize != 0.0) { + noiseSize = 1.0f / noiseSize; + x *= noiseSize; + y *= noiseSize; + z *= noiseSize; + } + float result = noiseFunction.execute(x, y, z); + return isHard ? FastMath.abs(2.0f * result - 1.0f) : result; + } + + public static float turbulence(float x, float y, float z, float noiseSize, int noiseDepth, NoiseFunction noiseFunction, boolean isHard) { + if (noiseSize != 0.0) { + noiseSize = 1.0f / noiseSize; + x *= noiseSize; + y *= noiseSize; + z *= noiseSize; + } + + float sum = 0, t, amp = 1, fscale = 1; + for (int i = 0; i <= noiseDepth; ++i, amp *= 0.5f, fscale *= 2f) { + t = noiseFunction.execute(fscale * x, fscale * y, fscale * z); + if (isHard) { + t = FastMath.abs(2.0f * t - 1.0f); + } + sum += t * amp; + } + + sum *= (float) (1 << noiseDepth) / (float) ((1 << noiseDepth + 1) - 1); + return sum; + } + + private static final float[] voronoiP = new float[3]; + + public static void voronoi(float x, float y, float z, float[] da, float[] pa, float distanceExponent, DistanceFunction distanceFunction) { + float xd, yd, zd, d; + + int xi = (int) FastMath.floor(x); + int yi = (int) FastMath.floor(y); + int zi = (int) FastMath.floor(z); + da[0] = da[1] = da[2] = da[3] = Float.MAX_VALUE;// 1e10f; + for (int i = xi - 1; i <= xi + 1; ++i) { + for (int j = yi - 1; j <= yi + 1; ++j) { + for (int k = zi - 1; k <= zi + 1; ++k) { + NoiseMath.hash(i, j, k, voronoiP); + xd = x - (voronoiP[0] + i); + yd = y - (voronoiP[1] + j); + zd = z - (voronoiP[2] + k); + d = distanceFunction.execute(xd, yd, zd, distanceExponent); + if (d < da[0]) { + da[3] = da[2]; + da[2] = da[1]; + da[1] = da[0]; + da[0] = d; + pa[9] = pa[6]; + pa[10] = pa[7]; + pa[11] = pa[8]; + pa[6] = pa[3]; + pa[7] = pa[4]; + pa[8] = pa[5]; + pa[3] = pa[0]; + pa[4] = pa[1]; + pa[5] = pa[2]; + pa[0] = voronoiP[0] + i; + pa[1] = voronoiP[1] + j; + pa[2] = voronoiP[2] + k; + } else if (d < da[1]) { + da[3] = da[2]; + da[2] = da[1]; + da[1] = d; + pa[9] = pa[6]; + pa[10] = pa[7]; + pa[11] = pa[8]; + pa[6] = pa[3]; + pa[7] = pa[4]; + pa[8] = pa[5]; + pa[3] = voronoiP[0] + i; + pa[4] = voronoiP[1] + j; + pa[5] = voronoiP[2] + k; + } else if (d < da[2]) { + da[3] = da[2]; + da[2] = d; + pa[9] = pa[6]; + pa[10] = pa[7]; + pa[11] = pa[8]; + pa[6] = voronoiP[0] + i; + pa[7] = voronoiP[1] + j; + pa[8] = voronoiP[2] + k; + } else if (d < da[3]) { + da[3] = d; + pa[9] = voronoiP[0] + i; + pa[10] = voronoiP[1] + j; + pa[11] = voronoiP[2] + k; + } + } + } + } + } + + // instead of adding another permutation array, just use hash table defined above + public static float newPerlin(float x, float y, float z) { + int A, AA, AB, B, BA, BB; + float floorX = FastMath.floor(x), floorY = FastMath.floor(y), floorZ = FastMath.floor(z); + int intX = (int) floorX & 0xFF, intY = (int) floorY & 0xFF, intZ = (int) floorZ & 0xFF; + x -= floorX; + y -= floorY; + z -= floorZ; + // computing fading curves + floorX = NoiseMath.npfade(x); + floorY = NoiseMath.npfade(y); + floorZ = NoiseMath.npfade(z); + A = hash[intX] + intY; + AA = hash[A] + intZ; + AB = hash[A + 1] + intZ; + B = hash[intX + 1] + intY; + BA = hash[B] + intZ; + BB = hash[B + 1] + intZ; + return NoiseMath.lerp(floorZ, NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA], x, y, z), NoiseMath.grad(hash[BA], x - 1, y, z)), NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB], x, y - 1, z), NoiseMath.grad(hash[BB], x - 1, y - 1, z))), + NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA + 1], x, y, z - 1), NoiseMath.grad(hash[BA + 1], x - 1, y, z - 1)), NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB + 1], x, y - 1, z - 1), NoiseMath.grad(hash[BB + 1], x - 1, y - 1, z - 1)))); + } + + public static float noise3Perlin(float x, float y, float z) { + float t = x + 10000.0f; + int bx0 = (int) t & 0xFF; + int bx1 = bx0 + 1 & 0xFF; + float rx0 = t - (int) t; + float rx1 = rx0 - 1.0f; + + t = y + 10000.0f; + int by0 = (int) t & 0xFF; + int by1 = by0 + 1 & 0xFF; + float ry0 = t - (int) t; + float ry1 = ry0 - 1.0f; + + t = z + 10000.0f; + int bz0 = (int) t & 0xFF; + int bz1 = bz0 + 1 & 0xFF; + float rz0 = t - (int) t; + float rz1 = rz0 - 1.0f; + + int i = p[bx0]; + int j = p[bx1]; + + int b00 = p[i + by0]; + int b10 = p[j + by0]; + int b01 = p[i + by1]; + int b11 = p[j + by1]; + + float sx = NoiseMath.surve(rx0); + float sy = NoiseMath.surve(ry0); + float sz = NoiseMath.surve(rz0); + + float[] q = g[b00 + bz0]; + float u = NoiseMath.at(rx0, ry0, rz0, q); + q = g[b10 + bz0]; + float v = NoiseMath.at(rx1, ry0, rz0, q); + float a = NoiseMath.lerp(sx, u, v); + + q = g[b01 + bz0]; + u = NoiseMath.at(rx0, ry1, rz0, q); + q = g[b11 + bz0]; + v = NoiseMath.at(rx1, ry1, rz0, q); + float b = NoiseMath.lerp(sx, u, v); + + float c = NoiseMath.lerp(sy, a, b); + + q = g[b00 + bz1]; + u = NoiseMath.at(rx0, ry0, rz1, q); + q = g[b10 + bz1]; + v = NoiseMath.at(rx1, ry0, rz1, q); + a = NoiseMath.lerp(sx, u, v); + + q = g[b01 + bz1]; + u = NoiseMath.at(rx0, ry1, rz1, q); + q = g[b11 + bz1]; + v = NoiseMath.at(rx1, ry1, rz1, q); + b = NoiseMath.lerp(sx, u, v); + + float d = NoiseMath.lerp(sy, a, b); + return 1.5f * NoiseMath.lerp(sz, c, d); + } + + private static final float[] cn = new float[8]; + private static final int[] b1 = new int[8]; + private static final int[] b2 = new int[2]; + private static final float[] xFactor = new float[8]; + private static final float[] yFactor = new float[8]; + private static final float[] zFactor = new float[8]; + + public static float originalBlenderNoise(float x, float y, float z) { + float n = 0.5f; + + int ix = (int) FastMath.floor(x); + int iy = (int) FastMath.floor(y); + int iz = (int) FastMath.floor(z); + + float ox = x - ix; + float oy = y - iy; + float oz = z - iz; + + float jx = ox - 1; + float jy = oy - 1; + float jz = oz - 1; + + float cn1 = ox * ox; + float cn2 = oy * oy; + float cn3 = oz * oz; + float cn4 = jx * jx; + float cn5 = jy * jy; + float cn6 = jz * jz; + + cn1 = 1.0f - 3.0f * cn1 + 2.0f * cn1 * ox; + cn2 = 1.0f - 3.0f * cn2 + 2.0f * cn2 * oy; + cn3 = 1.0f - 3.0f * cn3 + 2.0f * cn3 * oz; + cn4 = 1.0f - 3.0f * cn4 - 2.0f * cn4 * jx; + cn5 = 1.0f - 3.0f * cn5 - 2.0f * cn5 * jy; + cn6 = 1.0f - 3.0f * cn6 - 2.0f * cn6 * jz; + + cn[0] = cn1 * cn2 * cn3; + cn[1] = cn1 * cn2 * cn6; + cn[2] = cn1 * cn5 * cn3; + cn[3] = cn1 * cn5 * cn6; + cn[4] = cn4 * cn2 * cn3; + cn[5] = cn4 * cn2 * cn6; + cn[6] = cn4 * cn5 * cn3; + cn[7] = cn4 * cn5 * cn6; + + b1[0] = b1[1] = hash[hash[ix & 0xFF] + (iy & 0xFF)]; + b1[2] = b1[3] = hash[hash[ix & 0xFF] + (iy + 1 & 0xFF)]; + b1[4] = b1[5] = hash[hash[ix + 1 & 0xFF] + (iy & 0xFF)]; + b1[6] = b1[7] = hash[hash[ix + 1 & 0xFF] + (iy + 1 & 0xFF)]; + + b2[0] = iz & 0xFF; + b2[1] = iz + 1 & 0xFF; + + xFactor[0] = xFactor[1] = xFactor[2] = xFactor[3] = ox; + xFactor[4] = xFactor[5] = xFactor[6] = xFactor[7] = jx; + yFactor[0] = yFactor[1] = yFactor[4] = yFactor[5] = oy; + yFactor[2] = yFactor[3] = yFactor[6] = yFactor[7] = jy; + zFactor[0] = zFactor[2] = zFactor[4] = zFactor[6] = oz; + zFactor[1] = zFactor[3] = zFactor[5] = zFactor[7] = jz; + + for (int i = 0; i < cn.length; ++i) { + int hIndex = 3 * hash[b1[i] + b2[i % 2]]; + n += cn[i] * (hashvectf[hIndex] * xFactor[i] + hashvectf[hIndex + 1] * yFactor[i] + hashvectf[hIndex + 2] * zFactor[i]); + } + + if (n < 0.0f) { + n = 0.0f; + } else if (n > 1.0f) { + n = 1.0f; + } + return n; + } + } + + /** + * This class is abstract to the noise functions computations. It has two methods. One calculates the Signed (with + * 'S' at the end) and the other Unsigned value. + * @author Marcin Roguski (Kaelthas) + */ + interface NoiseFunction { + + /** + * This method calculates the unsigned value of the noise. + * @param x + * the x texture coordinate + * @param y + * the y texture coordinate + * @param z + * the z texture coordinate + * @return value of the noise + */ + float execute(float x, float y, float z); + + /** + * This method calculates the signed value of the noise. + * @param x + * the x texture coordinate + * @param y + * the y texture coordinate + * @param z + * the z texture coordinate + * @return value of the noise + */ + float executeSigned(float x, float y, float z); + } + + public static class NoiseMath { + public static float lerp(float t, float a, float b) { + return a + t * (b - a); + } + + public static float npfade(float t) { + return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); + } + + public static float grad(int hash, float x, float y, float z) { + int h = hash & 0x0F; + float u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z; + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } + + public static float surve(float t) { + return t * t * (3.0f - 2.0f * t); + } + + public static float at(float x, float y, float z, float[] q) { + return x * q[0] + y * q[1] + z * q[2]; + } + + public static void hash(int x, int y, int z, float[] result) { + result[0] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF]]; + result[1] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF] + 1]; + result[2] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF] + 2]; + } + } + + /** + * This interface is used for distance calculation classes. Distance metrics for voronoi. e parameter only used in + * Minkovsky. + */ + interface DistanceFunction { + + /** + * This method calculates the distance for voronoi algorithms. + * @param x + * the x coordinate + * @param y + * the y coordinate + * @param z + * the z coordinate + * @param e + * this parameter used in Monkovsky (no idea what it really is ;) + * @return + */ + float execute(float x, float y, float z, float e); + } + + interface MusgraveFunction { + + float execute(MusgraveData musgraveData, float x, float y, float z); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGenerator.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGenerator.java new file mode 100644 index 000000000..c001e463c --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGenerator.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.generating; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.ColorBand; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.texture.Image.Format; + +/** + * This class is a base class for texture generators. + * @author Marcin Roguski (Kaelthas) + */ +public abstract class TextureGenerator { + protected NoiseGenerator noiseGenerator; + protected int flag; + protected float[][] colorBand; + protected BrightnessAndContrastData bacd; + protected Format imageFormat; + + public TextureGenerator(NoiseGenerator noiseGenerator, Format imageFormat) { + this.noiseGenerator = noiseGenerator; + this.imageFormat = imageFormat; + } + + public Format getImageFormat() { + return imageFormat; + } + + public void readData(Structure tex, BlenderContext blenderContext) { + flag = ((Number) tex.getFieldValue("flag")).intValue(); + colorBand = new ColorBand(tex, blenderContext).computeValues(); + bacd = new BrightnessAndContrastData(tex); + if (colorBand != null) { + imageFormat = Format.RGBA8; + } + } + + public abstract void getPixel(TexturePixel pixel, float x, float y, float z); + + /** + * This method applies brightness and contrast for RGB textures. + * @param tex + * texture structure + * @param texres + */ + protected void applyBrightnessAndContrast(BrightnessAndContrastData bacd, TexturePixel texres) { + texres.red = (texres.red - 0.5f) * bacd.contrast + bacd.brightness; + if (texres.red < 0.0f) { + texres.red = 0.0f; + } + texres.green = (texres.green - 0.5f) * bacd.contrast + bacd.brightness; + if (texres.green < 0.0f) { + texres.green = 0.0f; + } + texres.blue = (texres.blue - 0.5f) * bacd.contrast + bacd.brightness; + if (texres.blue < 0.0f) { + texres.blue = 0.0f; + } + } + + /** + * This method applies brightness and contrast for Luminance textures. + * @param texres + * @param contrast + * @param brightness + */ + protected void applyBrightnessAndContrast(TexturePixel texres, float contrast, float brightness) { + texres.intensity = (texres.intensity - 0.5f) * contrast + brightness; + if (texres.intensity < 0.0f) { + texres.intensity = 0.0f; + } else if (texres.intensity > 1.0f) { + texres.intensity = 1.0f; + } + } + + /** + * This class contains brightness and contrast data. + * @author Marcin Roguski (Kaelthas) + */ + protected static class BrightnessAndContrastData { + public final float contrast; + public final float brightness; + public final float rFactor; + public final float gFactor; + public final float bFactor; + + /** + * Constructor reads the required data from the given structure. + * @param tex + * texture structure + */ + public BrightnessAndContrastData(Structure tex) { + contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); + brightness = ((Number) tex.getFieldValue("bright")).floatValue() - 0.5f; + rFactor = ((Number) tex.getFieldValue("rfac")).floatValue(); + gFactor = ((Number) tex.getFieldValue("gfac")).floatValue(); + bFactor = ((Number) tex.getFieldValue("bfac")).floatValue(); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorBlend.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorBlend.java new file mode 100644 index 000000000..eabe6fa74 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorBlend.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.generating; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.texture.Image.Format; + +/** + * This class generates the 'blend' texture. + * @author Marcin Roguski (Kaelthas) + */ +public final class TextureGeneratorBlend extends TextureGenerator { + + private static final IntensityFunction INTENSITY_FUNCTION[] = new IntensityFunction[7]; + static { + INTENSITY_FUNCTION[0] = new IntensityFunction() {// Linear: stype = 0 (TEX_LIN) + public float getIntensity(float x, float y, float z) { + return (1.0f + x) * 0.5f; + } + }; + INTENSITY_FUNCTION[1] = new IntensityFunction() {// Quad: stype = 1 (TEX_QUAD) + public float getIntensity(float x, float y, float z) { + float result = (1.0f + x) * 0.5f; + return result * result; + } + }; + INTENSITY_FUNCTION[2] = new IntensityFunction() {// Ease: stype = 2 (TEX_EASE) + public float getIntensity(float x, float y, float z) { + float result = (1.0f + x) * 0.5f; + if (result <= 0.0f) { + return 0.0f; + } else if (result >= 1.0f) { + return 1.0f; + } else { + return result * result * (3.0f - 2.0f * result); + } + } + }; + INTENSITY_FUNCTION[3] = new IntensityFunction() {// Diagonal: stype = 3 (TEX_DIAG) + public float getIntensity(float x, float y, float z) { + return (2.0f + x + y) * 0.25f; + } + }; + INTENSITY_FUNCTION[4] = new IntensityFunction() {// Sphere: stype = 4 (TEX_SPHERE) + public float getIntensity(float x, float y, float z) { + float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z); + return result < 0.0f ? 0.0f : result; + } + }; + INTENSITY_FUNCTION[5] = new IntensityFunction() {// Halo: stype = 5 (TEX_HALO) + public float getIntensity(float x, float y, float z) { + float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z); + return result <= 0.0f ? 0.0f : result * result; + } + }; + INTENSITY_FUNCTION[6] = new IntensityFunction() {// Radial: stype = 6 (TEX_RAD) + public float getIntensity(float x, float y, float z) { + return (float) Math.atan2(y, x) * FastMath.INV_TWO_PI + 0.5f; + } + }; + } + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorBlend(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + protected int stype; + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + stype = ((Number) tex.getFieldValue("stype")).intValue(); + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + pixel.intensity = INTENSITY_FUNCTION[stype].getIntensity(x, y, z); + + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + + this.applyBrightnessAndContrast(bacd, pixel); + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } + + private static interface IntensityFunction { + float getIntensity(float x, float y, float z); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorClouds.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorClouds.java new file mode 100644 index 000000000..0d1cf2c54 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorClouds.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.generating; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction; +import com.jme3.texture.Image.Format; + +/** + * This class generates the 'clouds' texture. + * @author Marcin Roguski (Kaelthas) + */ +public class TextureGeneratorClouds extends TextureGenerator { + // noiseType + protected static final int TEX_NOISESOFT = 0; + protected static final int TEX_NOISEPERL = 1; + + // sType + protected static final int TEX_DEFAULT = 0; + protected static final int TEX_COLOR = 1; + + protected float noisesize; + protected int noiseDepth; + protected int noiseBasis; + protected NoiseFunction noiseFunction; + protected int noiseType; + protected boolean isHard; + protected int sType; + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorClouds(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + noiseDepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); + noiseBasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + noiseType = ((Number) tex.getFieldValue("noisetype")).intValue(); + isHard = noiseType != TEX_NOISESOFT; + sType = ((Number) tex.getFieldValue("stype")).intValue(); + if (sType == TEX_COLOR) { + imageFormat = Format.RGBA8; + } + + noiseFunction = NoiseGenerator.noiseFunctions.get(noiseBasis); + if (noiseFunction == null) { + noiseFunction = NoiseGenerator.noiseFunctions.get(0); + noiseBasis = 0; + } + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + if (noiseBasis == 0) { + ++x; + ++y; + ++z; + } + pixel.intensity = NoiseGenerator.NoiseFunctions.turbulence(x, y, z, noisesize, noiseDepth, noiseFunction, isHard); + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + pixel.alpha = colorBand[colorbandIndex][3]; + + this.applyBrightnessAndContrast(bacd, pixel); + } else if (sType == TEX_COLOR) { + pixel.red = pixel.intensity; + pixel.green = NoiseGenerator.NoiseFunctions.turbulence(y, x, z, noisesize, noiseDepth, noiseFunction, isHard); + pixel.blue = NoiseGenerator.NoiseFunctions.turbulence(y, z, x, noisesize, noiseDepth, noiseFunction, isHard); + + pixel.green = FastMath.clamp(pixel.green, 0.0f, 1.0f); + pixel.blue = FastMath.clamp(pixel.blue, 0.0f, 1.0f); + pixel.alpha = 1.0f; + + this.applyBrightnessAndContrast(bacd, pixel); + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorDistnoise.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorDistnoise.java new file mode 100644 index 000000000..232ccbeb3 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorDistnoise.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.generating; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction; +import com.jme3.texture.Image.Format; + +/** + * This class generates the 'distorted noise' texture. + * @author Marcin Roguski (Kaelthas) + */ +public class TextureGeneratorDistnoise extends TextureGenerator { + protected float noisesize; + protected float distAmount; + protected int noisebasis; + protected int noisebasis2; + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorDistnoise(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + distAmount = ((Number) tex.getFieldValue("dist_amount")).floatValue(); + noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + noisebasis2 = ((Number) tex.getFieldValue("noisebasis2")).intValue(); + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + pixel.intensity = this.musgraveVariableLunacrityNoise(x * 4, y * 4, z * 4, distAmount, noisebasis, noisebasis2); + pixel.intensity = FastMath.clamp(pixel.intensity, 0.0f, 1.0f); + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + + this.applyBrightnessAndContrast(bacd, pixel); + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } + + /** + * "Variable Lacunarity Noise" A distorted variety of Perlin noise. This method is used to calculate distorted noise + * texture. + * @param x + * @param y + * @param z + * @param distortion + * @param nbas1 + * @param nbas2 + * @return + */ + private float musgraveVariableLunacrityNoise(float x, float y, float z, float distortion, int nbas1, int nbas2) { + NoiseFunction abstractNoiseFunc1 = NoiseGenerator.noiseFunctions.get(Integer.valueOf(nbas1)); + if (abstractNoiseFunc1 == null) { + abstractNoiseFunc1 = NoiseGenerator.noiseFunctions.get(Integer.valueOf(0)); + } + NoiseFunction abstractNoiseFunc2 = NoiseGenerator.noiseFunctions.get(Integer.valueOf(nbas2)); + if (abstractNoiseFunc2 == null) { + abstractNoiseFunc2 = NoiseGenerator.noiseFunctions.get(Integer.valueOf(0)); + } + // get a random vector and scale the randomization + float rx = abstractNoiseFunc1.execute(x + 13.5f, y + 13.5f, z + 13.5f) * distortion; + float ry = abstractNoiseFunc1.execute(x, y, z) * distortion; + float rz = abstractNoiseFunc1.execute(x - 13.5f, y - 13.5f, z - 13.5f) * distortion; + return abstractNoiseFunc2.executeSigned(x + rx, y + ry, z + rz); // distorted-domain noise + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorFactory.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorFactory.java new file mode 100644 index 000000000..a0e29f1b3 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorFactory.java @@ -0,0 +1,35 @@ +package com.jme3.scene.plugins.blender.textures.generating; + +import com.jme3.scene.plugins.blender.textures.TextureHelper; + +public class TextureGeneratorFactory { + + private NoiseGenerator noiseGenerator = new NoiseGenerator(); + + public TextureGenerator createTextureGenerator(int generatedTexture) { + switch (generatedTexture) { + case TextureHelper.TEX_BLEND: + return new TextureGeneratorBlend(noiseGenerator); + case TextureHelper.TEX_CLOUDS: + return new TextureGeneratorClouds(noiseGenerator); + case TextureHelper.TEX_DISTNOISE: + return new TextureGeneratorDistnoise(noiseGenerator); + case TextureHelper.TEX_MAGIC: + return new TextureGeneratorMagic(noiseGenerator); + case TextureHelper.TEX_MARBLE: + return new TextureGeneratorMarble(noiseGenerator); + case TextureHelper.TEX_MUSGRAVE: + return new TextureGeneratorMusgrave(noiseGenerator); + case TextureHelper.TEX_NOISE: + return new TextureGeneratorNoise(noiseGenerator); + case TextureHelper.TEX_STUCCI: + return new TextureGeneratorStucci(noiseGenerator); + case TextureHelper.TEX_VORONOI: + return new TextureGeneratorVoronoi(noiseGenerator); + case TextureHelper.TEX_WOOD: + return new TextureGeneratorWood(noiseGenerator); + default: + throw new IllegalStateException("Unknown generated texture type: " + generatedTexture); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMagic.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMagic.java new file mode 100644 index 000000000..6f3d7b599 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMagic.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.generating; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.texture.Image.Format; + +/** + * This class generates the 'magic' texture. + * @author Marcin Roguski (Kaelthas) + */ +public class TextureGeneratorMagic extends TextureGenerator { + private static NoiseDepthFunction[] noiseDepthFunctions = new NoiseDepthFunction[10]; + static { + noiseDepthFunctions[0] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[1] = -(float) Math.cos(xyz[0] - xyz[1] + xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[1] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[0] = (float) Math.cos(xyz[0] - xyz[1] - xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[2] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[2] = (float) Math.sin(-xyz[0] - xyz[1] - xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[3] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[0] = -(float) Math.cos(-xyz[0] + xyz[1] - xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[4] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[1] = -(float) Math.sin(-xyz[0] + xyz[1] + xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[5] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[1] = -(float) Math.cos(-xyz[0] + xyz[1] + xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[6] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[0] = (float) Math.cos(xyz[0] + xyz[1] + xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[7] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[2] = (float) Math.sin(xyz[0] + xyz[1] - xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[8] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[0] = -(float) Math.cos(-xyz[0] - xyz[1] + xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[9] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[1] = -(float) Math.sin(xyz[0] - xyz[1] + xyz[2]) * turbulence; + } + }; + } + + protected int noisedepth; + protected float turbul; + protected float[] xyz = new float[3]; + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorMagic(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.RGBA8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); + turbul = ((Number) tex.getFieldValue("turbul")).floatValue() / 5.0f; + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + float turb = turbul; + xyz[0] = (float) Math.sin((x + y + z) * 5.0f); + xyz[1] = (float) Math.cos((-x + y - z) * 5.0f); + xyz[2] = -(float) Math.cos((-x - y + z) * 5.0f); + + if (colorBand != null) { + pixel.intensity = FastMath.clamp(0.3333f * (xyz[0] + xyz[1] + xyz[2]), 0.0f, 1.0f); + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + pixel.alpha = colorBand[colorbandIndex][3]; + } else { + if (noisedepth > 0) { + xyz[0] *= turb; + xyz[1] *= turb; + xyz[2] *= turb; + for (int m = 0; m < noisedepth; ++m) { + noiseDepthFunctions[m].compute(xyz, turb); + } + } + + if (turb != 0.0f) { + turb *= 2.0f; + xyz[0] /= turb; + xyz[1] /= turb; + xyz[2] /= turb; + } + pixel.red = 0.5f - xyz[0]; + pixel.green = 0.5f - xyz[1]; + pixel.blue = 0.5f - xyz[2]; + pixel.alpha = 1.0f; + } + this.applyBrightnessAndContrast(bacd, pixel); + } + + private static interface NoiseDepthFunction { + void compute(float[] xyz, float turbulence); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMarble.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMarble.java new file mode 100644 index 000000000..9dc7403b1 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMarble.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.generating; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction; + +/** + * This class generates the 'marble' texture. + * @author Marcin Roguski (Kaelthas) + */ +public class TextureGeneratorMarble extends TextureGeneratorWood { + // tex->stype + protected static final int TEX_SOFT = 0; + protected static final int TEX_SHARP = 1; + protected static final int TEX_SHARPER = 2; + + protected MarbleData marbleData; + protected int noisebasis; + protected NoiseFunction noiseFunction; + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorMarble(NoiseGenerator noiseGenerator) { + super(noiseGenerator); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + marbleData = new MarbleData(tex); + noisebasis = marbleData.noisebasis; + noiseFunction = NoiseGenerator.noiseFunctions.get(noisebasis); + if (noiseFunction == null) { + noiseFunction = NoiseGenerator.noiseFunctions.get(0); + noisebasis = 0; + } + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + pixel.intensity = this.marbleInt(marbleData, x, y, z); + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + + this.applyBrightnessAndContrast(bacd, pixel); + pixel.alpha = colorBand[colorbandIndex][3]; + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } + + public float marbleInt(MarbleData marbleData, float x, float y, float z) { + int waveform; + if (marbleData.waveform > TEX_TRI || marbleData.waveform < TEX_SIN) { + waveform = 0; + } else { + waveform = marbleData.waveform; + } + + float n = 5.0f * (x + y + z); + if (noisebasis == 0) { + ++x; + ++y; + ++z; + } + float mi = n + marbleData.turbul * NoiseGenerator.NoiseFunctions.turbulence(x, y, z, marbleData.noisesize, marbleData.noisedepth, noiseFunction, marbleData.isHard); + + if (marbleData.stype >= TEX_SOFT) { + mi = waveformFunctions[waveform].execute(mi); + if (marbleData.stype == TEX_SHARP) { + mi = (float) Math.sqrt(mi); + } else if (marbleData.stype == TEX_SHARPER) { + mi = (float) Math.sqrt(Math.sqrt(mi)); + } + } + return mi; + } + + private static class MarbleData { + public final float noisesize; + public final int noisebasis; + public final int noisedepth; + public final int stype; + public final float turbul; + public final int waveform; + public final boolean isHard; + + public MarbleData(Structure tex) { + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); + stype = ((Number) tex.getFieldValue("stype")).intValue(); + turbul = ((Number) tex.getFieldValue("turbul")).floatValue(); + int noisetype = ((Number) tex.getFieldValue("noisetype")).intValue(); + waveform = ((Number) tex.getFieldValue("noisebasis2")).intValue(); + isHard = noisetype != TEX_NOISESOFT; + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMusgrave.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMusgrave.java new file mode 100644 index 000000000..707d4dfe0 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMusgrave.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.generating; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.MusgraveFunction; +import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction; +import com.jme3.texture.Image.Format; + +/** + * This class generates the 'musgrave' texture. + * @author Marcin Roguski (Kaelthas) + */ +public class TextureGeneratorMusgrave extends TextureGenerator { + protected MusgraveData musgraveData; + protected MusgraveFunction musgraveFunction; + protected int stype; + protected float noisesize; + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorMusgrave(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + musgraveData = new MusgraveData(tex); + stype = ((Number) tex.getFieldValue("stype")).intValue(); + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + musgraveFunction = NoiseGenerator.musgraveFunctions.get(Integer.valueOf(musgraveData.stype)); + if (musgraveFunction == null) { + throw new IllegalStateException("Unknown type of musgrave texture: " + stype); + } + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + pixel.intensity = musgraveData.outscale * musgraveFunction.execute(musgraveData, x, y, z); + if (pixel.intensity > 1) { + pixel.intensity = 1.0f; + } else if (pixel.intensity < 0) { + pixel.intensity = 0.0f; + } + + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + pixel.alpha = colorBand[colorbandIndex][3]; + } else { + this.applyBrightnessAndContrast(bacd, pixel); + } + } + + protected static class MusgraveData { + public final int stype; + public final float outscale; + public final float h; + public final float lacunarity; + public final float octaves; + public final int noisebasis; + public final NoiseFunction noiseFunction; + public final float offset; + public final float gain; + + public MusgraveData(Structure tex) { + stype = ((Number) tex.getFieldValue("stype")).intValue(); + outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue(); + h = ((Number) tex.getFieldValue("mg_H")).floatValue(); + lacunarity = ((Number) tex.getFieldValue("mg_lacunarity")).floatValue(); + octaves = ((Number) tex.getFieldValue("mg_octaves")).floatValue(); + offset = ((Number) tex.getFieldValue("mg_offset")).floatValue(); + gain = ((Number) tex.getFieldValue("mg_gain")).floatValue(); + + int noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + NoiseFunction noiseFunction = NoiseGenerator.noiseFunctions.get(noisebasis); + if (noiseFunction == null) { + noiseFunction = NoiseGenerator.noiseFunctions.get(0); + noisebasis = 0; + } + this.noisebasis = noisebasis; + this.noiseFunction = noiseFunction; + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorNoise.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorNoise.java new file mode 100644 index 000000000..aa8b7b18d --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorNoise.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.generating; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.texture.Image.Format; + +/** + * This class generates the 'noise' texture. + * @author Marcin Roguski (Kaelthas) + */ +public class TextureGeneratorNoise extends TextureGenerator { + protected int noisedepth; + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorNoise(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + int random = FastMath.rand.nextInt(); + int val = random & 3; + + int loop = noisedepth; + while (loop-- != 0) { + random >>= 2; + val *= random & 3; + } + pixel.intensity = FastMath.clamp(val, 0.0f, 1.0f); + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + + this.applyBrightnessAndContrast(bacd, pixel); + pixel.alpha = colorBand[colorbandIndex][3]; + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorStucci.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorStucci.java new file mode 100644 index 000000000..dd07ef1c1 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorStucci.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.generating; + +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction; +import com.jme3.texture.Image.Format; + +/** + * This class generates the 'stucci' texture. + * @author Marcin Roguski (Kaelthas) + */ +public class TextureGeneratorStucci extends TextureGenerator { + protected static final int TEX_NOISESOFT = 0; + + protected float noisesize; + protected int noisebasis; + protected NoiseFunction noiseFunction; + protected int noisetype; + protected float turbul; + protected boolean isHard; + protected int stype; + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorStucci(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + + noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + noiseFunction = NoiseGenerator.noiseFunctions.get(noisebasis); + if (noiseFunction == null) { + noiseFunction = NoiseGenerator.noiseFunctions.get(0); + noisebasis = 0; + } + + noisetype = ((Number) tex.getFieldValue("noisetype")).intValue(); + turbul = ((Number) tex.getFieldValue("turbul")).floatValue(); + isHard = noisetype != TEX_NOISESOFT; + stype = ((Number) tex.getFieldValue("stype")).intValue(); + if (noisesize <= 0.001f) {// the texture goes black if this value is lower than 0.001f + noisesize = 0.001f; + } + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + if (noisebasis == 0) { + ++x; + ++y; + ++z; + } + float noiseValue = NoiseGenerator.NoiseFunctions.noise(x, y, z, noisesize, 0, noiseFunction, isHard); + float ofs = turbul / 200.0f; + if (stype != 0) { + ofs *= noiseValue * noiseValue; + } + + pixel.intensity = NoiseGenerator.NoiseFunctions.noise(x, y, z + ofs, noisesize, 0, noiseFunction, isHard); + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + pixel.alpha = colorBand[colorbandIndex][3]; + } + + if (stype == NoiseGenerator.TEX_WALLOUT) { + pixel.intensity = 1.0f - pixel.intensity; + } + if (pixel.intensity < 0.0f) { + pixel.intensity = 0.0f; + } + // no brightness and contrast needed for stucci (it doesn't affect the texture) + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorVoronoi.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorVoronoi.java new file mode 100644 index 000000000..e53a6e73b --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorVoronoi.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures.generating; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.DistanceFunction; +import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseMath; +import com.jme3.texture.Image.Format; + +/** + * This class generates the 'voronoi' texture. + * @author Marcin Roguski (Kaelthas) + */ +public class TextureGeneratorVoronoi extends TextureGenerator { + protected float noisesize; + protected float outscale; + protected float mexp; + protected DistanceFunction distanceFunction; + protected int voronoiColorType; + protected float[] da = new float[4], pa = new float[12]; + protected float[] hashPoint; + protected float[] voronoiWeights; + protected float weightSum; + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorVoronoi(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + voronoiWeights = new float[4]; + voronoiWeights[0] = ((Number) tex.getFieldValue("vn_w1")).floatValue(); + voronoiWeights[1] = ((Number) tex.getFieldValue("vn_w2")).floatValue(); + voronoiWeights[2] = ((Number) tex.getFieldValue("vn_w3")).floatValue(); + voronoiWeights[3] = ((Number) tex.getFieldValue("vn_w4")).floatValue(); + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue(); + mexp = ((Number) tex.getFieldValue("vn_mexp")).floatValue(); + int distanceType = ((Number) tex.getFieldValue("vn_distm")).intValue(); + distanceFunction = NoiseGenerator.distanceFunctions.get(distanceType); + voronoiColorType = ((Number) tex.getFieldValue("vn_coltype")).intValue(); + hashPoint = voronoiColorType != 0 ? new float[3] : null; + weightSum = voronoiWeights[0] + voronoiWeights[1] + voronoiWeights[2] + voronoiWeights[3]; + if (weightSum != 0.0f) { + weightSum = outscale / weightSum; + } + if (voronoiColorType != 0 || colorBand != null) { + imageFormat = Format.RGBA8; + } + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + // for voronoi we need to widen the range a little + NoiseGenerator.NoiseFunctions.voronoi(x * 4, y * 4, z * 4, da, pa, mexp, distanceFunction); + pixel.intensity = weightSum * FastMath.abs(voronoiWeights[0] * da[0] + voronoiWeights[1] * da[1] + voronoiWeights[2] * da[2] + voronoiWeights[3] * da[3]); + if (pixel.intensity > 1.0f) { + pixel.intensity = 1.0f; + } else if (pixel.intensity < 0.0f) { + pixel.intensity = 0.0f; + } + + if (colorBand != null) {// colorband ALWAYS goes first and covers the color (if set) + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + pixel.alpha = colorBand[colorbandIndex][3]; + } else if (voronoiColorType != 0) { + pixel.red = pixel.green = pixel.blue = 0.0f; + pixel.alpha = 1.0f; + for (int m = 0; m < 12; m += 3) { + float weight = voronoiWeights[m / 3]; + NoiseMath.hash((int) pa[m], (int) pa[m + 1], (int) pa[m + 2], hashPoint); + pixel.red += weight * hashPoint[0]; + pixel.green += weight * hashPoint[1]; + pixel.blue += weight * hashPoint[2]; + } + if (voronoiColorType >= 2) { + float t1 = (da[1] - da[0]) * 10.0f; + if (t1 > 1.0f) { + t1 = 1.0f; + } + if (voronoiColorType == 3) { + t1 *= pixel.intensity; + } else { + t1 *= weightSum; + } + pixel.red *= t1; + pixel.green *= t1; + pixel.blue *= t1; + } else { + pixel.red *= weightSum; + pixel.green *= weightSum; + pixel.blue *= weightSum; + } + } + + if (voronoiColorType != 0 || colorBand != null) { + this.applyBrightnessAndContrast(bacd, pixel); + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorWood.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorWood.java new file mode 100644 index 000000000..94cf2af4c --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorWood.java @@ -0,0 +1,206 @@ +/* + * + * $Id: noise.c 14611 2008-04-29 08:24:33Z campbellbarton $ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): none yet. + * + * ***** END GPL LICENSE BLOCK ***** + * + */ +package com.jme3.scene.plugins.blender.textures.generating; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction; +import com.jme3.texture.Image.Format; + +/** + * This class generates the 'wood' texture. + * @author Marcin Roguski (Kaelthas) + */ +public class TextureGeneratorWood extends TextureGenerator { + // tex->noisebasis2 + protected static final int TEX_SIN = 0; + protected static final int TEX_SAW = 1; + protected static final int TEX_TRI = 2; + + // tex->stype + protected static final int TEX_BAND = 0; + protected static final int TEX_RING = 1; + protected static final int TEX_BANDNOISE = 2; + protected static final int TEX_RINGNOISE = 3; + + // tex->noisetype + protected static final int TEX_NOISESOFT = 0; + protected static final int TEX_NOISEPERL = 1; + + protected WoodIntensityData woodIntensityData; + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorWood(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + woodIntensityData = new WoodIntensityData(tex); + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + pixel.intensity = this.woodIntensity(woodIntensityData, x, y, z); + + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + + this.applyBrightnessAndContrast(bacd, pixel); + pixel.alpha = colorBand[colorbandIndex][3]; + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } + + protected static WaveForm[] waveformFunctions = new WaveForm[3]; + static { + waveformFunctions[0] = new WaveForm() {// sinus (TEX_SIN) + + public float execute(float x) { + return 0.5f + 0.5f * (float) Math.sin(x); + } + }; + waveformFunctions[1] = new WaveForm() {// saw (TEX_SAW) + + public float execute(float x) { + int n = (int) (x * FastMath.INV_TWO_PI); + x -= n * FastMath.TWO_PI; + if (x < 0.0f) { + x += FastMath.TWO_PI; + } + return x * FastMath.INV_TWO_PI; + } + }; + waveformFunctions[2] = new WaveForm() {// triangle (TEX_TRI) + + public float execute(float x) { + return 1.0f - 2.0f * FastMath.abs((float) Math.floor(x * FastMath.INV_TWO_PI + 0.5f) - x * FastMath.INV_TWO_PI); + } + }; + } + + /** + * Computes basic wood intensity value at x,y,z. + * @param woodIntData + * @param x + * X coordinate of the texture pixel + * @param y + * Y coordinate of the texture pixel + * @param z + * Z coordinate of the texture pixel + * @return wood intensity at position [x, y, z] + */ + public float woodIntensity(WoodIntensityData woodIntData, float x, float y, float z) { + float result; + + switch (woodIntData.woodType) { + case TEX_BAND: + result = woodIntData.waveformFunction.execute((x + y + z) * 10.0f); + break; + case TEX_RING: + result = woodIntData.waveformFunction.execute((float) Math.sqrt(x * x + y * y + z * z) * 20.0f); + break; + case TEX_BANDNOISE: + if (woodIntData.noisebasis == 0) { + ++x; + ++y; + ++z; + } + result = woodIntData.turbul * NoiseGenerator.NoiseFunctions.noise(x, y, z, woodIntData.noisesize, 0, woodIntData.noiseFunction, woodIntData.isHard); + result = woodIntData.waveformFunction.execute((x + y + z) * 10.0f + result); + break; + case TEX_RINGNOISE: + if (woodIntData.noisebasis == 0) { + ++x; + ++y; + ++z; + } + result = woodIntData.turbul * NoiseGenerator.NoiseFunctions.noise(x, y, z, woodIntData.noisesize, 0, woodIntData.noiseFunction, woodIntData.isHard); + result = woodIntData.waveformFunction.execute((float) Math.sqrt(x * x + y * y + z * z) * 20.0f + result); + break; + default: + result = 0; + } + return result; + } + + /** + * A class that collects the data for wood intensity calculations. + * @author Marcin Roguski (Kaelthas) + */ + private static class WoodIntensityData { + public final WaveForm waveformFunction; + public final int noisebasis; + public NoiseFunction noiseFunction; + + public final float noisesize; + public final float turbul; + public final int noiseType; + public final int woodType; + public final boolean isHard; + + public WoodIntensityData(Structure tex) { + int waveform = ((Number) tex.getFieldValue("noisebasis2")).intValue();// wave form: TEX_SIN=0, TEX_SAW=1, TEX_TRI=2 + if (waveform > TEX_TRI || waveform < TEX_SIN) { + waveform = 0; // check to be sure noisebasis2 is initialized ahead of time + } + waveformFunction = waveformFunctions[waveform]; + int noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + if (noiseFunction == null) { + noiseFunction = NoiseGenerator.noiseFunctions.get(0); + noisebasis = 0; + } + this.noisebasis = noisebasis; + + woodType = ((Number) tex.getFieldValue("stype")).intValue(); + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + turbul = ((Number) tex.getFieldValue("turbul")).floatValue(); + noiseType = ((Number) tex.getFieldValue("noisetype")).intValue(); + isHard = noiseType != TEX_NOISESOFT; + } + } + + protected static interface WaveForm { + + float execute(float x); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java new file mode 100644 index 000000000..002c16746 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java @@ -0,0 +1,157 @@ +package com.jme3.scene.plugins.blender.textures.io; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.texture.Image; +import java.nio.ByteBuffer; +import jme3tools.converters.RGB565; + +/** + * Implemens read/write operations for AWT images. + * @author Marcin Roguski (Kaelthas) + */ +/* package */class AWTPixelInputOutput implements PixelInputOutput { + public void read(Image image, int layer, TexturePixel pixel, int index) { + ByteBuffer data = image.getData(layer); + switch (image.getFormat()) { + case RGBA8: + pixel.fromARGB8(data.get(index + 3), data.get(index), data.get(index + 1), data.get(index + 2)); + break; + case ABGR8: + pixel.fromARGB8(data.get(index), data.get(index + 3), data.get(index + 2), data.get(index + 1)); + break; + case BGR8: + pixel.fromARGB8((byte) 0xFF, data.get(index + 2), data.get(index + 1), data.get(index)); + break; + case RGB8: + pixel.fromARGB8((byte) 0xFF, data.get(index), data.get(index + 1), data.get(index + 2)); + break; + case RGB565: + pixel.fromARGB8(RGB565.RGB565_to_ARGB8(data.getShort(index))); + break; + case RGB5A1: + short rgb5a1 = data.getShort(index); + byte a = (byte) (rgb5a1 & 0x01); + int r = (rgb5a1 & 0xf800) >> 11 << 3; + int g = (rgb5a1 & 0x07c0) >> 6 << 3; + int b = (rgb5a1 & 0x001f) >> 1 << 3; + pixel.fromARGB8(a == 1 ? (byte) 255 : 0, (byte) r, (byte) g, (byte) b); + break; + case RGB16: + pixel.fromARGB16((short) 0xFFFF, data.getShort(index), data.getShort(index + 2), data.getShort(index + 4)); + break; + case RGBA16: + pixel.fromARGB16(data.getShort(index + 6), data.getShort(index), data.getShort(index + 2), data.getShort(index + 4)); + break; + case RGB16F: + case RGB16F_to_RGB111110F: + case RGB16F_to_RGB9E5: + pixel.fromARGB(1, FastMath.convertHalfToFloat(data.getShort(index)), FastMath.convertHalfToFloat(data.getShort(index + 2)), FastMath.convertHalfToFloat(data.getShort(index + 4))); + break; + case RGBA16F: + pixel.fromARGB(FastMath.convertHalfToFloat(data.getShort(index + 6)), FastMath.convertHalfToFloat(data.getShort(index)), FastMath.convertHalfToFloat(data.getShort(index + 2)), FastMath.convertHalfToFloat(data.getShort(index + 4))); + break; + case RGBA32F: + pixel.fromARGB(Float.intBitsToFloat(data.getInt(index + 12)), Float.intBitsToFloat(data.getInt(index)), Float.intBitsToFloat(data.getInt(index + 4)), Float.intBitsToFloat(data.getInt(index + 8))); + break; + case RGB111110F:// the data is stored as 32-bit unsigned int, that is why we cast the read data to long and remove MSB-bytes to get the positive value + pixel.fromARGB(1, (float) Double.longBitsToDouble((long) data.getInt(index) & 0x00000000FFFFFFFF), (float) Double.longBitsToDouble((long) data.getInt(index + 4) & 0x00000000FFFFFFFF), (float) Double.longBitsToDouble((long) data.getInt(index + 8) & 0x00000000FFFFFFFF)); + break; + case RGB10: + case RGB9E5:// TODO: support these + throw new IllegalStateException("Not supported image type for IO operations: " + image.getFormat()); + default: + throw new IllegalStateException("Unknown image format: " + image.getFormat()); + } + } + + public void read(Image image, int layer, TexturePixel pixel, int x, int y) { + int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3); + this.read(image, layer, pixel, index); + } + + public void write(Image image, int layer, TexturePixel pixel, int index) { + ByteBuffer data = image.getData(layer); + switch (image.getFormat()) { + case RGBA8: + data.put(index, pixel.getR8()); + data.put(index + 1, pixel.getG8()); + data.put(index + 2, pixel.getB8()); + data.put(index + 3, pixel.getA8()); + break; + case ABGR8: + data.put(index, pixel.getA8()); + data.put(index + 1, pixel.getB8()); + data.put(index + 2, pixel.getG8()); + data.put(index + 3, pixel.getR8()); + break; + case BGR8: + data.put(index, pixel.getB8()); + data.put(index + 1, pixel.getG8()); + data.put(index + 2, pixel.getR8()); + break; + case RGB8: + data.put(index, pixel.getR8()); + data.put(index + 1, pixel.getG8()); + data.put(index + 2, pixel.getB8()); + break; + case RGB565: + data.putShort(RGB565.ARGB8_to_RGB565(pixel.toARGB8())); + break; + case RGB5A1: + int argb8 = pixel.toARGB8(); + short r = (short) ((argb8 & 0x00F80000) >> 8); + short g = (short) ((argb8 & 0x0000F800) >> 5); + short b = (short) ((argb8 & 0x000000F8) >> 2); + short a = (short) ((short) ((argb8 & 0xFF000000) >> 24) > 0 ? 1 : 0); + data.putShort(index, (short) (r | g | b | a)); + break; + case RGB16: + data.putShort(index, pixel.getR16()); + data.putShort(index + 2, pixel.getG16()); + data.putShort(index + 4, pixel.getB16()); + break; + case RGBA16: + data.putShort(index, pixel.getR16()); + data.putShort(index + 2, pixel.getG16()); + data.putShort(index + 4, pixel.getB16()); + data.putShort(index + 6, pixel.getA16()); + break; + case RGB16F: + case RGB16F_to_RGB111110F: + case RGB16F_to_RGB9E5: + data.putShort(index, FastMath.convertFloatToHalf(pixel.red)); + data.putShort(index + 2, FastMath.convertFloatToHalf(pixel.green)); + data.putShort(index + 4, FastMath.convertFloatToHalf(pixel.blue)); + break; + case RGBA16F: + data.putShort(index, FastMath.convertFloatToHalf(pixel.red)); + data.putShort(index + 2, FastMath.convertFloatToHalf(pixel.green)); + data.putShort(index + 4, FastMath.convertFloatToHalf(pixel.blue)); + data.putShort(index + 6, FastMath.convertFloatToHalf(pixel.blue)); + break; + case RGB32F: + case RGB111110F:// this data is stored as 32-bit unsigned int + data.putInt(index, Float.floatToIntBits(pixel.red)); + data.putInt(index + 2, Float.floatToIntBits(pixel.green)); + data.putInt(index + 4, Float.floatToIntBits(pixel.blue)); + break; + case RGBA32F: + data.putInt(index, Float.floatToIntBits(pixel.red)); + data.putInt(index + 2, Float.floatToIntBits(pixel.green)); + data.putInt(index + 4, Float.floatToIntBits(pixel.blue)); + data.putInt(index + 6, Float.floatToIntBits(pixel.alpha)); + break; + case RGB10: + case RGB9E5:// TODO: support these + throw new IllegalStateException("Not supported image type for IO operations: " + image.getFormat()); + default: + throw new IllegalStateException("Unknown image format: " + image.getFormat()); + } + } + + public void write(Image image, int layer, TexturePixel pixel, int x, int y) { + int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3); + this.write(image, layer, pixel, index); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/DDSPixelInputOutput.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/DDSPixelInputOutput.java new file mode 100644 index 000000000..d82f164a1 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/DDSPixelInputOutput.java @@ -0,0 +1,171 @@ +package com.jme3.scene.plugins.blender.textures.io; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.texture.Image; +import java.nio.ByteBuffer; +import jme3tools.converters.RGB565; + +/** + * Implemens read/write operations for DDS images. + * This class currently implements only read operation. + * @author Marcin Roguski (Kaelthas) + */ +/* package */class DDSPixelInputOutput implements PixelInputOutput { + /** + * For this class the index should be considered as a pixel index in AWT image format. + */ + public void read(Image image, int layer, TexturePixel pixel, int index) { + this.read(image, layer, pixel, index % image.getWidth(), index / image.getWidth()); + } + + public void read(Image image, int layer, TexturePixel pixel, int x, int y) { + int xTexetlIndex = x % image.getWidth() >> 2; + int yTexelIndex = y % image.getHeight() >> 2; + int xTexelCount = image.getWidth() >> 2; + int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex; + + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; + int indexes = 0; + long alphaIndexes = 0; + float[] alphas = null; + ByteBuffer data = image.getData().get(layer); + + switch (image.getFormat()) { + case DXT1: // BC1 + case DXT1A: { + data.position(texelIndex * 8); + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + if (col0 > col1) { + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + } else { + // creating color2 = 1/2color0 + 1/2color1 + colors[2].fromPixel(colors[0]); + colors[2].add(colors[1]); + colors[2].mult(0.5f); + + colors[3].fromARGB8(0); + } + indexes = data.getInt();// 4-byte table with color indexes in decompressed table + break; + } + case DXT3: {// BC2 + data.position(texelIndex * 16); + long alpha = data.getLong(); + alphas = new float[16]; + for (int i = 0; i < 16; ++i) { + alphaIndexes |= i << i * 4; + byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4); + alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; + } + + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + + indexes = data.getInt();// 4-byte table with color indexes in decompressed table + break; + } + case DXT5: {// BC3 + data.position(texelIndex * 16); + alphas = new float[8]; + alphas[0] = data.get() * 255.0f; + alphas[1] = data.get() * 255.0f; + // the casts to long must be done here because otherwise 32-bit integers would be shifetd by 32 and 40 bits which would result in improper values + alphaIndexes = (long)data.get() | (long)data.get() << 8 | (long)data.get() << 16 | (long)data.get() << 24 | (long)data.get() << 32 | (long)data.get() << 40; + if (alphas[0] > alphas[1]) {// 6 interpolated alpha values. + alphas[2] = (6 * alphas[0] + alphas[1]) / 7; + alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7; + alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7; + alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7; + alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7; + alphas[7] = (alphas[0] + 6 * alphas[1]) / 7; + } else { + alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f; + alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f; + alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f; + alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f; + alphas[6] = 0; + alphas[7] = 1; + } + + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + + indexes = data.getInt();// 4-byte table with color indexes in decompressed table + break; + } + default: + throw new IllegalStateException("Unsupported decompression format."); + } + + // coordinates of the pixel in the selected texel + x = x - 4 * xTexetlIndex;// pixels are arranged from left to right + y = 3 - y - 4 * yTexelIndex;// pixels are arranged from bottom to top (that is why '3 - ...' is at the start) + + int pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2); + int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0; + + // getting the pixel + int indexMask = colors.length - 1; + int colorIndex = indexes >> pixelIndexInTexel & indexMask; + float alpha = alphas != null ? alphas[(int) (alphaIndexes >> alphaIndexInTexel & 0x07)] : colors[colorIndex].alpha; + pixel.fromPixel(colors[colorIndex]); + pixel.alpha = alpha; + } + + public void write(Image image, int layer, TexturePixel pixel, int index) { + throw new UnsupportedOperationException("Cannot put the DXT pixel by index because not every index contains the pixel color!"); + } + + public void write(Image image, int layer, TexturePixel pixel, int x, int y) { + throw new UnsupportedOperationException("Writing to DDS texture pixel by pixel is not yet supported!"); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/LuminancePixelInputOutput.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/LuminancePixelInputOutput.java new file mode 100644 index 000000000..dc71f9e68 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/LuminancePixelInputOutput.java @@ -0,0 +1,88 @@ +package com.jme3.scene.plugins.blender.textures.io; + +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.texture.Image; +import java.nio.ByteBuffer; + +/** + * Implemens read/write operations for luminance images. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class LuminancePixelInputOutput implements PixelInputOutput { + public void read(Image image, int layer, TexturePixel pixel, int index) { + ByteBuffer data = image.getData(layer); + switch (image.getFormat()) { + case Luminance8: + pixel.fromIntensity(data.get(index)); + break; + case Luminance8Alpha8: + pixel.fromIntensity(data.get(index)); + pixel.setAlpha(data.get(index + 1)); + break; + case Luminance16: + pixel.fromIntensity(data.getShort(index)); + break; + case Luminance16Alpha16: + pixel.fromIntensity(data.getShort(index)); + pixel.setAlpha(data.getShort(index + 2)); + break; + case Luminance16F: + pixel.intensity = FastMath.convertHalfToFloat(data.getShort(index)); + break; + case Luminance16FAlpha16F: + pixel.intensity = FastMath.convertHalfToFloat(data.getShort(index)); + pixel.alpha = FastMath.convertHalfToFloat(data.getShort(index + 2)); + break; + case Luminance32F: + pixel.intensity = Float.intBitsToFloat(data.getInt(index)); + break; + default: + throw new IllegalStateException("Unknown luminance format type."); + } + } + + public void read(Image image, int layer, TexturePixel pixel, int x, int y) { + int index = y * image.getWidth() + x; + this.read(image, layer, pixel, index); + } + + public void write(Image image, int layer, TexturePixel pixel, int index) { + ByteBuffer data = image.getData(layer); + data.put(index, pixel.getInt()); + switch (image.getFormat()) { + case Luminance8: + data.put(index, pixel.getInt()); + break; + case Luminance8Alpha8: + data.put(index, pixel.getInt()); + data.put(index + 1, pixel.getA8()); + break; + case Luminance16: + data.putShort(index, (short) (pixel.intensity * 65535.0f)); + break; + case Luminance16Alpha16: + data.putShort(index, (short) (pixel.intensity * 65535.0f)); + data.putShort(index + 2, (short) (pixel.alpha * 65535.0f)); + break; + case Luminance16F: + data.putShort(index, FastMath.convertFloatToHalf(pixel.intensity)); + break; + case Luminance16FAlpha16F: + data.putShort(index, FastMath.convertFloatToHalf(pixel.intensity)); + data.putShort(index + 2, FastMath.convertFloatToHalf(pixel.alpha)); + break; + case Luminance32F: + data.putInt(index, Float.floatToIntBits(pixel.intensity)); + break; + default: + throw new IllegalStateException("Unknown luminance format type."); + } + } + + public void write(Image image, int layer, TexturePixel pixel, int x, int y) { + int index = y * image.getWidth() + x; + this.write(image, layer, pixel, index); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java new file mode 100644 index 000000000..3ef284e8a --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java @@ -0,0 +1,69 @@ +package com.jme3.scene.plugins.blender.textures.io; + +import com.jme3.texture.Image.Format; +import java.util.HashMap; +import java.util.Map; + +/** + * This class creates a pixel IO object for the specified image format. + * + * @author Marcin Roguski (Kaelthas) + */ +public class PixelIOFactory { + private static final Map PIXEL_INPUT_OUTPUT = new HashMap(); + + /** + * This method returns pixel IO object for the specified format. + * + * @param format + * the format of the image + * @return pixel IO object + */ + public static PixelInputOutput getPixelIO(Format format) { + PixelInputOutput result = PIXEL_INPUT_OUTPUT.get(format); + if (result == null) { + switch (format) { + case ABGR8: + case RGBA8: + case BGR8: + case RGB8: + case RGB10: + case RGB111110F: + case RGB16: + case RGB16F: + case RGB16F_to_RGB111110F: + case RGB16F_to_RGB9E5: + case RGB32F: + case RGB565: + case RGB5A1: + case RGB9E5: + case RGBA16: + case RGBA16F: + case RGBA32F: + result = new AWTPixelInputOutput(); + break; + case Luminance8: + case Luminance16: + case Luminance16Alpha16: + case Luminance16F: + case Luminance16FAlpha16F: + case Luminance32F: + case Luminance8Alpha8: + result = new LuminancePixelInputOutput(); + break; + case DXT1: + case DXT1A: + case DXT3: + case DXT5: + result = new DDSPixelInputOutput(); + break; + default: + throw new IllegalStateException("Unsupported image format for IO operations: " + format); + } + synchronized (PIXEL_INPUT_OUTPUT) { + PIXEL_INPUT_OUTPUT.put(format, result); + } + } + return result; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelInputOutput.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelInputOutput.java new file mode 100644 index 000000000..3ff592047 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelInputOutput.java @@ -0,0 +1,65 @@ +package com.jme3.scene.plugins.blender.textures.io; + +import com.jme3.scene.plugins.blender.textures.TexturePixel; +import com.jme3.texture.Image; + +/** + * Implemens read/write operations for images. + * + * @author Marcin Roguski (Kaelthas) + */ +public interface PixelInputOutput { + /** + * This method reads a pixel that starts at the given index. + * + * @param image + * the image we read pixel from + * @param pixel + * the pixel where the result is stored + * @param index + * the index where the pixel begins in the image data + */ + void read(Image image, int layer, TexturePixel pixel, int index); + + /** + * This method reads a pixel that is located at the given position on the + * image. + * + * @param image + * the image we read pixel from + * @param pixel + * the pixel where the result is stored + * @param x + * the X coordinate of the pixel + * @param y + * the Y coordinate of the pixel + */ + void read(Image image, int layer, TexturePixel pixel, int x, int y); + + /** + * This method writes a pixel that starts at the given index. + * + * @param image + * the image we read pixel from + * @param pixel + * the pixel where the result is stored + * @param index + * the index where the pixel begins in the image data + */ + void write(Image image, int layer, TexturePixel pixel, int index); + + /** + * This method writes a pixel that is located at the given position on the + * image. + * + * @param image + * the image we read pixel from + * @param pixel + * the pixel where the result is stored + * @param x + * the X coordinate of the pixel + * @param y + * the Y coordinate of the pixel + */ + void write(Image image, int layer, TexturePixel pixel, int x, int y); +} diff --git a/jme3-blender/src/main/resources/com/jme3/scene/plugins/blender/textures/generating/noiseconstants.dat b/jme3-blender/src/main/resources/com/jme3/scene/plugins/blender/textures/generating/noiseconstants.dat new file mode 100644 index 000000000..81fea0b61 Binary files /dev/null and b/jme3-blender/src/main/resources/com/jme3/scene/plugins/blender/textures/generating/noiseconstants.dat differ diff --git a/jme3-bullet-native/src/native/android-mk/Android.mk b/jme3-bullet-native/src/native/android-mk/Android.mk new file mode 100644 index 000000000..45258d04b --- /dev/null +++ b/jme3-bullet-native/src/native/android-mk/Android.mk @@ -0,0 +1,260 @@ +# /* +# Bullet Continuous Collision Detection and Physics Library for Android NDK +# Copyright (c) 2006-2009 Noritsuna Imamura http://www.siprop.org/ +# +# This software is provided 'as-is', without any express or implied warranty. +# In no event will the authors be held liable for any damages arising from the use of this software. +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it freely, +# subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +# 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +# 3. This notice may not be removed or altered from any source distribution. +# */ +LOCAL_PATH:= $(call my-dir) +JME3_PATH:= +BULLET_PATH:= + +include $(CLEAR_VARS) + +LOCAL_MODULE := bulletjme +LOCAL_C_INCLUDES := $(BULLET_PATH)/\ + $(BULLET_PATH)/BulletCollision/BroadphaseCollision\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch\ + $(BULLET_PATH)/BulletCollision/CollisionShapes\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver\ + $(BULLET_PATH)/BulletDynamics/Dynamics\ + $(BULLET_PATH)/BulletDynamics/Vehicle\ + $(BULLET_PATH)/LinearMath\ + $(BULLET_PATH)/BulletCollision\ + $(BULLET_PATH)/BulletDynamics\ + $(BULLET_PATH)/BulletMultiThreaded\ + $(BULLET_PATH)/BulletSoftBody\ + $(BULLET_PATH)/ibmsdk\ + $(BULLET_PATH)/LinearMath\ + $(BULLET_PATH)/MiniCL\ + $(BULLET_PATH)/vectormath\ + $(BULLET_PATH)/BulletCollision/BroadphaseCollision\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch\ + $(BULLET_PATH)/BulletCollision/CollisionShapes\ + $(BULLET_PATH)/BulletCollision/Gimpact\ + $(BULLET_PATH)/BulletCollision/ibmsdk\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision\ + $(BULLET_PATH)/BulletDynamics/Character\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver\ + $(BULLET_PATH)/BulletDynamics/Dynamics\ + $(BULLET_PATH)/BulletDynamics/ibmsdk\ + $(BULLET_PATH)/BulletDynamics/Vehicle\ + $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers\ + $(BULLET_PATH)/BulletMultiThreaded/out\ + $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask\ + $(BULLET_PATH)/BulletMultiThreaded/SpuSampleTask\ + $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/CPU\ + $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/DX11\ + $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL\ + $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/DX11/HLSL\ + $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/AMD\ + $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/Apple\ + $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/MiniCL\ + $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/NVidia\ + $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/OpenCLC\ + $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/OpenCLC10\ + $(BULLET_PATH)/LinearMath/ibmsdk\ + $(BULLET_PATH)/MiniCL/MiniCLTask\ + $(BULLET_PATH)/vectormath/scalar\ + $(BULLET_PATH)/vectormath/sse + +LOCAL_CFLAGS := $(LOCAL_C_INCLUDES:%=-I%) +LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -ldl -lm -llog + +LOCAL_SRC_FILES := $(JME3_PATH)/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_PhysicsCollisionObject.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_BoxCollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_CollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_CompoundCollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_ConeCollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_CylinderCollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_GImpactCollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_MeshCollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_PlaneCollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_SimplexCollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_collision_shapes_SphereCollisionShape.cpp\ + $(JME3_PATH)/com_jme3_bullet_joints_ConeJoint.cpp\ + $(JME3_PATH)/com_jme3_bullet_joints_HingeJoint.cpp\ + $(JME3_PATH)/com_jme3_bullet_joints_motors_RotationalLimitMotor.cpp\ + $(JME3_PATH)/com_jme3_bullet_joints_motors_TranslationalLimitMotor.cpp\ + $(JME3_PATH)/com_jme3_bullet_joints_PhysicsJoint.cpp\ + $(JME3_PATH)/com_jme3_bullet_joints_Point2PointJoint.cpp\ + $(JME3_PATH)/com_jme3_bullet_joints_SixDofJoint.cpp\ + $(JME3_PATH)/com_jme3_bullet_joints_SixDofSpringJoint.cpp\ + $(JME3_PATH)/com_jme3_bullet_joints_SliderJoint.cpp\ + $(JME3_PATH)/com_jme3_bullet_objects_infos_RigidBodyMotionState.cpp\ + $(JME3_PATH)/com_jme3_bullet_objects_PhysicsCharacter.cpp\ + $(JME3_PATH)/com_jme3_bullet_objects_PhysicsGhostObject.cpp\ + $(JME3_PATH)/com_jme3_bullet_objects_PhysicsRigidBody.cpp\ + $(JME3_PATH)/com_jme3_bullet_objects_PhysicsVehicle.cpp\ + $(JME3_PATH)/com_jme3_bullet_objects_VehicleWheel.cpp\ + $(JME3_PATH)/com_jme3_bullet_PhysicsSpace.cpp\ + $(JME3_PATH)/com_jme3_bullet_util_DebugShapeFactory.cpp\ + $(JME3_PATH)/com_jme3_bullet_util_NativeMeshUtil.cpp\ + $(JME3_PATH)/jmeBulletUtil.cpp\ + $(JME3_PATH)/jmeClasses.cpp\ + $(JME3_PATH)/jmeMotionState.cpp\ + $(JME3_PATH)/jmePhysicsSpace.cpp\ + $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btAxisSweep3.cpp\ + $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btBroadphaseProxy.cpp\ + $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btDbvt.cpp\ + $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btDbvtBroadphase.cpp\ + $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btDispatcher.cpp\ + $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btMultiSapBroadphase.cpp\ + $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btOverlappingPairCache.cpp\ + $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btQuantizedBvh.cpp\ + $(BULLET_PATH)/BulletCollision/BroadphaseCollision/btSimpleBroadphase.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btActivatingCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btBox2dBox2dCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btBoxBoxCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btBoxBoxDetector.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btCollisionDispatcher.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btCollisionObject.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btCollisionWorld.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btCompoundCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btConvex2dConvex2dAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btConvexConcaveCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btConvexConvexAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btConvexPlaneCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btDefaultCollisionConfiguration.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btEmptyCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btGhostObject.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btInternalEdgeUtility.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btManifoldResult.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btSimulationIslandManager.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btSphereBoxCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btSphereSphereCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btSphereTriangleCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/btUnionFind.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionDispatch/SphereTriangleDetector.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btBox2dShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btBoxShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btBvhTriangleMeshShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btCapsuleShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btCollisionShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btCompoundShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btConcaveShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btConeShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvex2dShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvexHullShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvexInternalShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvexPointCloudShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvexPolyhedron.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvexShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btConvexTriangleMeshShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btCylinderShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btEmptyShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btHeightfieldTerrainShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btMinkowskiSumShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btMultimaterialTriangleMeshShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btMultiSphereShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btOptimizedBvh.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btPolyhedralConvexShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btScaledBvhTriangleMeshShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btShapeHull.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btSphereShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btStaticPlaneShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btStridingMeshInterface.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btTetrahedronShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btTriangleBuffer.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btTriangleCallback.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btTriangleIndexVertexArray.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btTriangleIndexVertexMaterialArray.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btTriangleMesh.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btTriangleMeshShape.cpp\ + $(BULLET_PATH)/BulletCollision/CollisionShapes/btUniformScalingShape.cpp\ + $(BULLET_PATH)/BulletCollision/Gimpact/btContactProcessing.cpp\ + $(BULLET_PATH)/BulletCollision/Gimpact/btGenericPoolAllocator.cpp\ + $(BULLET_PATH)/BulletCollision/Gimpact/btGImpactBvh.cpp\ + $(BULLET_PATH)/BulletCollision/Gimpact/btGImpactCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletCollision/Gimpact/btGImpactQuantizedBvh.cpp\ + $(BULLET_PATH)/BulletCollision/Gimpact/btGImpactShape.cpp\ + $(BULLET_PATH)/BulletCollision/Gimpact/btTriangleShapeEx.cpp\ + $(BULLET_PATH)/BulletCollision/Gimpact/gim_box_set.cpp\ + $(BULLET_PATH)/BulletCollision/Gimpact/gim_contact.cpp\ + $(BULLET_PATH)/BulletCollision/Gimpact/gim_memory.cpp\ + $(BULLET_PATH)/BulletCollision/Gimpact/gim_tri_collision.cpp\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btContinuousConvexCollision.cpp\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btConvexCast.cpp\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btGjkConvexCast.cpp\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btGjkEpa2.cpp\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btGjkEpaPenetrationDepthSolver.cpp\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btGjkPairDetector.cpp\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btMinkowskiPenetrationDepthSolver.cpp\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btPersistentManifold.cpp\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btPolyhedralContactClipping.cpp\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btRaycastCallback.cpp\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btSubSimplexConvexCast.cpp\ + $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision/btVoronoiSimplexSolver.cpp\ + $(BULLET_PATH)/BulletDynamics/Character/btKinematicCharacterController.cpp\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btConeTwistConstraint.cpp\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btContactConstraint.cpp\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btGeneric6DofConstraint.cpp\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btGeneric6DofSpringConstraint.cpp\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btHinge2Constraint.cpp\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btHingeConstraint.cpp\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btPoint2PointConstraint.cpp\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btSequentialImpulseConstraintSolver.cpp\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btSliderConstraint.cpp\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btSolve2LinearConstraint.cpp\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btTypedConstraint.cpp\ + $(BULLET_PATH)/BulletDynamics/ConstraintSolver/btUniversalConstraint.cpp\ + $(BULLET_PATH)/BulletDynamics/Dynamics/btDiscreteDynamicsWorld.cpp\ + $(BULLET_PATH)/BulletDynamics/Dynamics/btRigidBody.cpp\ + $(BULLET_PATH)/BulletDynamics/Dynamics/btSimpleDynamicsWorld.cpp\ + $(BULLET_PATH)/BulletDynamics/Dynamics/Bullet-C-API.cpp\ + $(BULLET_PATH)/BulletDynamics/Vehicle/btRaycastVehicle.cpp\ + $(BULLET_PATH)/BulletDynamics/Vehicle/btWheelInfo.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/btGpu3DGridBroadphase.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/btParallelConstraintSolver.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/btThreadSupportInterface.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/PosixThreadSupport.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SequentialThreadSupport.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuCollisionObjectWrapper.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuCollisionTaskProcess.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuContactManifoldCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuFakeDma.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuGatheringCollisionDispatcher.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuLibspe2Support.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuSampleTaskProcess.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/Win32ThreadSupport.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask/boxBoxDistance.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask/SpuCollisionShapes.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask/SpuContactResult.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask/SpuGatheringCollisionTask.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask/SpuMinkowskiPenetrationDepthSolver.cpp\ + $(BULLET_PATH)/BulletMultiThreaded/SpuSampleTask/SpuSampleTask.cpp\ + $(BULLET_PATH)/BulletSoftBody/btDefaultSoftBodySolver.cpp\ + $(BULLET_PATH)/BulletSoftBody/btSoftBody.cpp\ + $(BULLET_PATH)/BulletSoftBody/btSoftBodyConcaveCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletSoftBody/btSoftBodyHelpers.cpp\ + $(BULLET_PATH)/BulletSoftBody/btSoftBodyRigidBodyCollisionConfiguration.cpp\ + $(BULLET_PATH)/BulletSoftBody/btSoftRigidCollisionAlgorithm.cpp\ + $(BULLET_PATH)/BulletSoftBody/btSoftRigidDynamicsWorld.cpp\ + $(BULLET_PATH)/BulletSoftBody/btSoftSoftCollisionAlgorithm.cpp\ + $(BULLET_PATH)/LinearMath/btAlignedAllocator.cpp\ + $(BULLET_PATH)/LinearMath/btConvexHull.cpp\ + $(BULLET_PATH)/LinearMath/btConvexHullComputer.cpp\ + $(BULLET_PATH)/LinearMath/btGeometryUtil.cpp\ + $(BULLET_PATH)/LinearMath/btQuickprof.cpp\ + $(BULLET_PATH)/LinearMath/btSerializer.cpp\ + $(BULLET_PATH)/LinearMath/btPolarDecomposition.cpp\ + $(BULLET_PATH)/MiniCL/MiniCL.cpp\ + $(BULLET_PATH)/MiniCL/MiniCLTaskScheduler.cpp\ + $(BULLET_PATH)/MiniCL/MiniCLTask/MiniCLTask.cpp +# $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/CPU/btSoftBodySolver_CPU.cpp\ +# $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/MiniCL/MiniCLTaskWrap.cpp\ + +include $(BUILD_SHARED_LIBRARY) diff --git a/jme3-bullet-native/src/native/android-mk/Application.mk b/jme3-bullet-native/src/native/android-mk/Application.mk new file mode 100644 index 000000000..9e8be3571 --- /dev/null +++ b/jme3-bullet-native/src/native/android-mk/Application.mk @@ -0,0 +1,2 @@ +APP_MODULES := bulletjme +APP_ABI := all \ No newline at end of file diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp new file mode 100644 index 000000000..e20448d5a --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "com_jme3_bullet_PhysicsSpace.h" +#include "jmePhysicsSpace.h" +#include "jmeBulletUtil.h" + +/** + * Author: Normen Hansen + */ +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: createPhysicsSpace + * Signature: (FFFFFFI)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_PhysicsSpace_createPhysicsSpace + (JNIEnv * env, jobject object, jfloat minX, jfloat minY, jfloat minZ, jfloat maxX, jfloat maxY, jfloat maxZ, jint broadphase, jboolean threading) { + jmeClasses::initJavaClasses(env); + jmePhysicsSpace* space = new jmePhysicsSpace(env, object); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space has not been created."); + return 0; + } + space->createPhysicsSpace(minX, minY, minZ, maxX, maxY, maxZ, broadphase, threading); + return reinterpret_cast(space); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: stepSimulation + * Signature: (JFIF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_stepSimulation + (JNIEnv * env, jobject object, jlong spaceId, jfloat tpf, jint maxSteps, jfloat accuracy) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + space->stepSimulation(tpf, maxSteps, accuracy); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addCollisionObject + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addCollisionObject + (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btCollisionObject* collisionObject = reinterpret_cast(objectId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (collisionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The collision object does not exist."); + return; + } + jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); + userPointer -> space = space; + + space->getDynamicsWorld()->addCollisionObject(collisionObject); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: removeCollisionObject + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeCollisionObject + (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btCollisionObject* collisionObject = reinterpret_cast(objectId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (collisionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The collision object does not exist."); + return; + } + space->getDynamicsWorld()->removeCollisionObject(collisionObject); + jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); + userPointer -> space = NULL; + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addRigidBody + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addRigidBody + (JNIEnv * env, jobject object, jlong spaceId, jlong rigidBodyId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btRigidBody* collisionObject = reinterpret_cast(rigidBodyId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (collisionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The collision object does not exist."); + return; + } + jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); + userPointer -> space = space; + space->getDynamicsWorld()->addRigidBody(collisionObject); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: removeRigidBody + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeRigidBody + (JNIEnv * env, jobject object, jlong spaceId, jlong rigidBodyId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btRigidBody* collisionObject = reinterpret_cast(rigidBodyId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (collisionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The collision object does not exist."); + return; + } + jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); + userPointer -> space = NULL; + space->getDynamicsWorld()->removeRigidBody(collisionObject); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addCharacterObject + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addCharacterObject + (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btCollisionObject* collisionObject = reinterpret_cast(objectId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (collisionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The collision object does not exist."); + return; + } + jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); + userPointer -> space = space; + space->getDynamicsWorld()->addCollisionObject(collisionObject, + btBroadphaseProxy::CharacterFilter, + btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter + ); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: removeCharacterObject + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeCharacterObject + (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btCollisionObject* collisionObject = reinterpret_cast(objectId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (collisionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The collision object does not exist."); + return; + } + jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); + userPointer -> space = NULL; + space->getDynamicsWorld()->removeCollisionObject(collisionObject); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addAction + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addAction + (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btActionInterface* actionObject = reinterpret_cast(objectId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (actionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The action object does not exist."); + return; + } + space->getDynamicsWorld()->addAction(actionObject); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: removeAction + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeAction + (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btActionInterface* actionObject = reinterpret_cast(objectId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (actionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The action object does not exist."); + return; + } + space->getDynamicsWorld()->removeAction(actionObject); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addVehicle + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addVehicle + (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btActionInterface* actionObject = reinterpret_cast(objectId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (actionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The vehicle object does not exist."); + return; + } + space->getDynamicsWorld()->addVehicle(actionObject); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: removeVehicle + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeVehicle + (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btActionInterface* actionObject = reinterpret_cast(objectId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (actionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The action object does not exist."); + return; + } + space->getDynamicsWorld()->removeVehicle(actionObject); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addConstraint + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addConstraint + (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btTypedConstraint* constraint = reinterpret_cast(objectId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (constraint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The constraint object does not exist."); + return; + } + space->getDynamicsWorld()->addConstraint(constraint); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addConstraint + * Signature: (JJZ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addConstraintC + (JNIEnv * env, jobject object, jlong spaceId, jlong objectId, jboolean collision) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btTypedConstraint* constraint = reinterpret_cast(objectId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (constraint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The constraint object does not exist."); + return; + } + space->getDynamicsWorld()->addConstraint(constraint, collision); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: removeConstraint + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeConstraint + (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + btTypedConstraint* constraint = reinterpret_cast(objectId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + if (constraint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The constraint object does not exist."); + return; + } + space->getDynamicsWorld()->removeConstraint(constraint); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: setGravity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_setGravity + (JNIEnv * env, jobject object, jlong spaceId, jobject vector) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + btVector3 gravity = btVector3(); + jmeBulletUtil::convert(env, vector, &gravity); + space->getDynamicsWorld()->setGravity(gravity); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: initNativePhysics + * Signature: ()V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_initNativePhysics + (JNIEnv * env, jclass clazz) { + jmeClasses::initJavaClasses(env); + } + + /* + * Class: com_jme3_bullet_PhysicsSpace + * Method: finalizeNative + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_finalizeNative + (JNIEnv * env, jobject object, jlong spaceId) { + jmePhysicsSpace* space = reinterpret_cast(spaceId); + if (space == NULL) { + return; + } + delete(space); + } + + JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_rayTest_1native + (JNIEnv * env, jobject object, jobject from, jobject to, jlong spaceId, jobject resultlist, jint flags) { + + jmePhysicsSpace* space = reinterpret_cast (spaceId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The physics space does not exist."); + return; + } + + struct AllRayResultCallback : public btCollisionWorld::RayResultCallback { + + AllRayResultCallback(const btVector3& rayFromWorld, const btVector3 & rayToWorld) : m_rayFromWorld(rayFromWorld), m_rayToWorld(rayToWorld) { + } + jobject resultlist; + JNIEnv* env; + btVector3 m_rayFromWorld; //used to calculate hitPointWorld from hitFraction + btVector3 m_rayToWorld; + + btVector3 m_hitNormalWorld; + btVector3 m_hitPointWorld; + + virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { + if (normalInWorldSpace) { + m_hitNormalWorld = rayResult.m_hitNormalLocal; + } else { + m_hitNormalWorld = m_collisionObject->getWorldTransform().getBasis() * rayResult.m_hitNormalLocal; + } + m_hitPointWorld.setInterpolate3(m_rayFromWorld, m_rayToWorld, rayResult.m_hitFraction); + + jmeBulletUtil::addResult(env, resultlist, m_hitNormalWorld, m_hitPointWorld, rayResult.m_hitFraction, rayResult.m_collisionObject); + + return 1.f; + } + }; + + btVector3 native_to = btVector3(); + jmeBulletUtil::convert(env, to, &native_to); + + btVector3 native_from = btVector3(); + jmeBulletUtil::convert(env, from, &native_from); + + AllRayResultCallback resultCallback(native_from, native_to); + resultCallback.env = env; + resultCallback.resultlist = resultlist; + resultCallback.m_flags = flags; + space->getDynamicsWorld()->rayTest(native_from, native_to, resultCallback); + return; + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h new file mode 100644 index 000000000..e040f8da8 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h @@ -0,0 +1,171 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_PhysicsSpace */ + +#ifndef _Included_com_jme3_bullet_PhysicsSpace +#define _Included_com_jme3_bullet_PhysicsSpace +#ifdef __cplusplus +extern "C" { +#endif +#undef com_jme3_bullet_PhysicsSpace_AXIS_X +#define com_jme3_bullet_PhysicsSpace_AXIS_X 0L +#undef com_jme3_bullet_PhysicsSpace_AXIS_Y +#define com_jme3_bullet_PhysicsSpace_AXIS_Y 1L +#undef com_jme3_bullet_PhysicsSpace_AXIS_Z +#define com_jme3_bullet_PhysicsSpace_AXIS_Z 2L +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: createPhysicsSpace + * Signature: (FFFFFFIZ)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_PhysicsSpace_createPhysicsSpace + (JNIEnv *, jobject, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jint, jboolean); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: stepSimulation + * Signature: (JFIF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_stepSimulation + (JNIEnv *, jobject, jlong, jfloat, jint, jfloat); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addCollisionObject + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addCollisionObject + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: removeCollisionObject + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeCollisionObject + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addRigidBody + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addRigidBody + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: removeRigidBody + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeRigidBody + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addCharacterObject + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addCharacterObject + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: removeCharacterObject + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeCharacterObject + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addAction + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addAction + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: removeAction + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeAction + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addVehicle + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addVehicle + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: removeVehicle + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeVehicle + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addConstraint + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addConstraint + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: addConstraintC + * Signature: (JJZ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addConstraintC + (JNIEnv *, jobject, jlong, jlong, jboolean); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: removeConstraint + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeConstraint + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: setGravity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_setGravity + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: rayTest_native + * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;JLjava/util/List;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_rayTest_1native + (JNIEnv *, jobject, jobject, jobject, jlong, jobject, jint); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: initNativePhysics + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_initNativePhysics + (JNIEnv *, jclass); + +/* + * Class: com_jme3_bullet_PhysicsSpace + * Method: finalizeNative + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_finalizeNative + (JNIEnv *, jobject, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace_BroadphaseType.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace_BroadphaseType.h new file mode 100644 index 000000000..04eb0f45f --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace_BroadphaseType.h @@ -0,0 +1,13 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_PhysicsSpace_BroadphaseType */ + +#ifndef _Included_com_jme3_bullet_PhysicsSpace_BroadphaseType +#define _Included_com_jme3_bullet_PhysicsSpace_BroadphaseType +#ifdef __cplusplus +extern "C" { +#endif +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp new file mode 100644 index 000000000..0705e8519 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "jmeBulletUtil.h" +#include "BulletCollision/NarrowPhaseCollision/btManifoldPoint.h" +#include "com_jme3_bullet_collision_PhysicsCollisionEvent.h" + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getAppliedImpulse + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulse + (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return 0; + } + return mp -> m_appliedImpulse; +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getAppliedImpulseLateral1 + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulseLateral1 + (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return 0; + } + return mp -> m_appliedImpulseLateral1; +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getAppliedImpulseLateral2 + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulseLateral2 + (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return 0; + } + return mp -> m_appliedImpulseLateral2; +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getCombinedFriction + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getCombinedFriction + (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return 0; + } + return mp -> m_combinedFriction; +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getCombinedRestitution + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getCombinedRestitution + (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return 0; + } + return mp -> m_combinedRestitution; +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getDistance1 + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getDistance1 + (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return 0; + } + return mp -> m_distance1; +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getIndex0 + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getIndex0 + (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return 0; + } + return mp -> m_index0; +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getIndex1 + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getIndex1 + (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return 0; + } + return mp -> m_index1; +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getLateralFrictionDir1 + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLateralFrictionDir1 + (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject lateralFrictionDir1) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return; + } + jmeBulletUtil::convert(env, &mp -> m_lateralFrictionDir1, lateralFrictionDir1); +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getLateralFrictionDir2 + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLateralFrictionDir2 + (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject lateralFrictionDir2) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return; + } + jmeBulletUtil::convert(env, &mp -> m_lateralFrictionDir2, lateralFrictionDir2); +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: isLateralFrictionInitialized + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_isLateralFrictionInitialized + (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return 0; + } + return mp -> m_lateralFrictionInitialized; +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getLifeTime + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLifeTime + (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return 0; + } + return mp -> m_lifeTime; +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getLocalPointA + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLocalPointA + (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject localPointA) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return; + } + jmeBulletUtil::convert(env, &mp -> m_localPointA, localPointA); +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getLocalPointB + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLocalPointB + (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject localPointB) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return; + } + jmeBulletUtil::convert(env, &mp -> m_localPointB, localPointB); +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getNormalWorldOnB + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getNormalWorldOnB + (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject normalWorldOnB) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return; + } + jmeBulletUtil::convert(env, &mp -> m_normalWorldOnB, normalWorldOnB); +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getPartId0 + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPartId0 + (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return 0; + } + return mp -> m_partId0; +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getPartId1 + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPartId1 + (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return 0; + } + return mp -> m_partId1; +} + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getPositionWorldOnA + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPositionWorldOnA + (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject positionWorldOnA) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return; + } + jmeBulletUtil::convert(env, &mp -> m_positionWorldOnA, positionWorldOnA); +} + + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getPositionWorldOnB + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPositionWorldOnB + (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject positionWorldOnB) { + btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); + if (mp == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The manifoldPoint does not exist."); + return; + } + jmeBulletUtil::convert(env, &mp -> m_positionWorldOnB, positionWorldOnB); +} diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionEvent.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionEvent.h new file mode 100644 index 000000000..2dd9dcf3b --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionEvent.h @@ -0,0 +1,173 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_PhysicsCollisionEvent */ + +#ifndef _Included_com_jme3_bullet_collision_PhysicsCollisionEvent +#define _Included_com_jme3_bullet_collision_PhysicsCollisionEvent +#ifdef __cplusplus +extern "C" { +#endif +#undef com_jme3_bullet_collision_PhysicsCollisionEvent_serialVersionUID +#define com_jme3_bullet_collision_PhysicsCollisionEvent_serialVersionUID 5516075349620653480LL +#undef com_jme3_bullet_collision_PhysicsCollisionEvent_TYPE_ADDED +#define com_jme3_bullet_collision_PhysicsCollisionEvent_TYPE_ADDED 0L +#undef com_jme3_bullet_collision_PhysicsCollisionEvent_TYPE_PROCESSED +#define com_jme3_bullet_collision_PhysicsCollisionEvent_TYPE_PROCESSED 1L +#undef com_jme3_bullet_collision_PhysicsCollisionEvent_TYPE_DESTROYED +#define com_jme3_bullet_collision_PhysicsCollisionEvent_TYPE_DESTROYED 2L +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getAppliedImpulse + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulse + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getAppliedImpulseLateral1 + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulseLateral1 + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getAppliedImpulseLateral2 + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulseLateral2 + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getCombinedFriction + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getCombinedFriction + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getCombinedRestitution + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getCombinedRestitution + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getDistance1 + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getDistance1 + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getIndex0 + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getIndex0 + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getIndex1 + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getIndex1 + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getLateralFrictionDir1 + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLateralFrictionDir1 + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getLateralFrictionDir2 + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLateralFrictionDir2 + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: isLateralFrictionInitialized + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_isLateralFrictionInitialized + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getLifeTime + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLifeTime + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getLocalPointA + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLocalPointA + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getLocalPointB + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLocalPointB + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getNormalWorldOnB + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getNormalWorldOnB + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getPartId0 + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPartId0 + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getPartId1 + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPartId1 + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getPositionWorldOnA + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPositionWorldOnA + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionEvent + * Method: getPositionWorldOnB + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPositionWorldOnB + (JNIEnv *, jobject, jlong, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.cpp new file mode 100644 index 000000000..e3852c76a --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_PhysicsCollisionObject.h" +#include "jmeBulletUtil.h" +#include "jmePhysicsSpace.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_PhysicsCollisionObject + * Method: attachCollisionShape + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_attachCollisionShape + (JNIEnv * env, jobject object, jlong objectId, jlong shapeId) { + btCollisionObject* collisionObject = reinterpret_cast(objectId); + if (collisionObject == NULL) { + jclass newExc = env->FindClass("java/lang/IllegalStateException"); + env->ThrowNew(newExc, "The collision object does not exist."); + return; + } + btCollisionShape* collisionShape = reinterpret_cast(shapeId); + if (collisionShape == NULL) { + jclass newExc = env->FindClass("java/lang/IllegalStateException"); + env->ThrowNew(newExc, "The collision shape does not exist."); + return; + } + collisionObject->setCollisionShape(collisionShape); + } + + /* + * Class: com_jme3_bullet_collision_PhysicsCollisionObject + * Method: finalizeNative + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_finalizeNative + (JNIEnv * env, jobject object, jlong objectId) { + btCollisionObject* collisionObject = reinterpret_cast(objectId); + if (collisionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + if (collisionObject -> getUserPointer() != NULL){ + jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); + delete(userPointer); + } + delete(collisionObject); + } + /* + * Class: com_jme3_bullet_collision_PhysicsCollisionObject + * Method: initUserPointer + * Signature: (JII)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_initUserPointer + (JNIEnv *env, jobject object, jlong objectId, jint group, jint groups) { + btCollisionObject* collisionObject = reinterpret_cast(objectId); + if (collisionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); + if (userPointer != NULL) { +// delete(userPointer); + } + userPointer = new jmeUserPointer(); + userPointer -> javaCollisionObject = env->NewWeakGlobalRef(object); + userPointer -> group = group; + userPointer -> groups = groups; + userPointer -> space = NULL; + collisionObject -> setUserPointer(userPointer); + } + /* + * Class: com_jme3_bullet_collision_PhysicsCollisionObject + * Method: setCollisionGroup + * Signature: (JI)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_setCollisionGroup + (JNIEnv *env, jobject object, jlong objectId, jint group) { + btCollisionObject* collisionObject = reinterpret_cast(objectId); + if (collisionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); + if (userPointer != NULL){ + userPointer -> group = group; + } + } + /* + * Class: com_jme3_bullet_collision_PhysicsCollisionObject + * Method: setCollideWithGroups + * Signature: (JI)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_setCollideWithGroups + (JNIEnv *env, jobject object, jlong objectId, jint groups) { + btCollisionObject* collisionObject = reinterpret_cast(objectId); + if (collisionObject == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); + if (userPointer != NULL){ + userPointer -> groups = groups; + } + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.h new file mode 100644 index 000000000..db5bf7aaf --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.h @@ -0,0 +1,87 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_PhysicsCollisionObject */ + +#ifndef _Included_com_jme3_bullet_collision_PhysicsCollisionObject +#define _Included_com_jme3_bullet_collision_PhysicsCollisionObject +#ifdef __cplusplus +extern "C" { +#endif +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_NONE +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_NONE 0L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_01 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_01 1L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_02 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_02 2L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_03 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_03 4L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_04 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_04 8L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_05 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_05 16L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_06 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_06 32L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_07 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_07 64L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_08 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_08 128L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_09 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_09 256L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_10 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_10 512L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_11 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_11 1024L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_12 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_12 2048L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_13 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_13 4096L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_14 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_14 8192L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_15 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_15 16384L +#undef com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_16 +#define com_jme3_bullet_collision_PhysicsCollisionObject_COLLISION_GROUP_16 32768L +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionObject + * Method: initUserPointer + * Signature: (JII)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_initUserPointer + (JNIEnv *, jobject, jlong, jint, jint); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionObject + * Method: attachCollisionShape + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_attachCollisionShape + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionObject + * Method: setCollisionGroup + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_setCollisionGroup + (JNIEnv *, jobject, jlong, jint); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionObject + * Method: setCollideWithGroups + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_setCollideWithGroups + (JNIEnv *, jobject, jlong, jint); + +/* + * Class: com_jme3_bullet_collision_PhysicsCollisionObject + * Method: finalizeNative + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_finalizeNative + (JNIEnv *, jobject, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_BoxCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_BoxCollisionShape.cpp new file mode 100644 index 000000000..d5af17930 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_BoxCollisionShape.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_shapes_BoxCollisionShape.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_shapes_BoxCollisionShape + * Method: createShape + * Signature: (Lcom/jme3/math/Vector3f;)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_BoxCollisionShape_createShape + (JNIEnv *env, jobject object, jobject halfExtents) { + jmeClasses::initJavaClasses(env); + btVector3 extents = btVector3(); + jmeBulletUtil::convert(env, halfExtents, &extents); + btBoxShape* shape = new btBoxShape(extents); + return reinterpret_cast(shape); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_BoxCollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_BoxCollisionShape.h new file mode 100644 index 000000000..5602a0dd3 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_BoxCollisionShape.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_BoxCollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_BoxCollisionShape +#define _Included_com_jme3_bullet_collision_shapes_BoxCollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_BoxCollisionShape + * Method: createShape + * Signature: (Lcom/jme3/math/Vector3f;)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_BoxCollisionShape_createShape + (JNIEnv *, jobject, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.cpp new file mode 100644 index 000000000..fd3957ba1 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_shapes_CapsuleCollisionShape.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_shapes_CapsuleCollisionShape + * Method: createShape + * Signature: (IFF)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CapsuleCollisionShape_createShape + (JNIEnv * env, jobject object, jint axis, jfloat radius, jfloat height) { + jmeClasses::initJavaClasses(env); + btCollisionShape* shape; + switch(axis){ + case 0: + shape = new btCapsuleShapeX(radius, height); + break; + case 1: + shape = new btCapsuleShape(radius, height); + break; + case 2: + shape = new btCapsuleShapeZ(radius, height); + break; + } + return reinterpret_cast(shape); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.h new file mode 100644 index 000000000..4d706748d --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_CapsuleCollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_CapsuleCollisionShape +#define _Included_com_jme3_bullet_collision_shapes_CapsuleCollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_CapsuleCollisionShape + * Method: createShape + * Signature: (IFF)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CapsuleCollisionShape_createShape + (JNIEnv *, jobject, jint, jfloat, jfloat); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CollisionShape.cpp new file mode 100644 index 000000000..dc48be257 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CollisionShape.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_shapes_CollisionShape.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_shapes_CollisionShape + * Method: getMargin + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_getMargin + (JNIEnv * env, jobject object, jlong shapeId) { + btCollisionShape* shape = reinterpret_cast(shapeId); + if (shape == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return shape->getMargin(); + } + + /* + * Class: com_jme3_bullet_collision_shapes_CollisionShape + * Method: setLocalScaling + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_setLocalScaling + (JNIEnv * env, jobject object, jlong shapeId, jobject scale) { + btCollisionShape* shape = reinterpret_cast(shapeId); + if (shape == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 scl = btVector3(); + jmeBulletUtil::convert(env, scale, &scl); + shape->setLocalScaling(scl); + } + + /* + * Class: com_jme3_bullet_collision_shapes_CollisionShape + * Method: setMargin + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_setMargin + (JNIEnv * env, jobject object, jlong shapeId, jfloat newMargin) { + btCollisionShape* shape = reinterpret_cast(shapeId); + if (shape == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + shape->setMargin(newMargin); + } + + /* + * Class: com_jme3_bullet_collision_shapes_CollisionShape + * Method: finalizeNative + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_finalizeNative + (JNIEnv * env, jobject object, jlong shapeId) { + btCollisionShape* shape = reinterpret_cast(shapeId); + if (shape == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + delete(shape); + } +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CollisionShape.h new file mode 100644 index 000000000..cd5d70f21 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CollisionShape.h @@ -0,0 +1,45 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_CollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_CollisionShape +#define _Included_com_jme3_bullet_collision_shapes_CollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_CollisionShape + * Method: getMargin + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_getMargin + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_shapes_CollisionShape + * Method: setLocalScaling + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_setLocalScaling + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_collision_shapes_CollisionShape + * Method: setMargin + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_setMargin + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_collision_shapes_CollisionShape + * Method: finalizeNative + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_finalizeNative + (JNIEnv *, jobject, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CompoundCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CompoundCollisionShape.cpp new file mode 100644 index 000000000..c3b07f45d --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CompoundCollisionShape.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_shapes_CompoundCollisionShape.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Class: com_jme3_bullet_collision_shapes_CompoundCollisionShape + * Method: createShape + * Signature: ()J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_createShape + (JNIEnv *env, jobject object) { + jmeClasses::initJavaClasses(env); + btCompoundShape* shape = new btCompoundShape(); + return reinterpret_cast(shape); + } + + /* + * Class: com_jme3_bullet_collision_shapes_CompoundCollisionShape + * Method: addChildShape + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_addChildShape + (JNIEnv *env, jobject object, jlong compoundId, jlong childId, jobject childLocation, jobject childRotation) { + btCompoundShape* shape = reinterpret_cast(compoundId); + if (shape == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + btCollisionShape* child = reinterpret_cast(childId); + if (shape == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + btMatrix3x3 mtx = btMatrix3x3(); + btTransform trans = btTransform(mtx); + jmeBulletUtil::convert(env, childLocation, &trans.getOrigin()); + jmeBulletUtil::convert(env, childRotation, &trans.getBasis()); + shape->addChildShape(trans, child); + return 0; + } + + /* + * Class: com_jme3_bullet_collision_shapes_CompoundCollisionShape + * Method: removeChildShape + * Signature: (JJ)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_removeChildShape + (JNIEnv * env, jobject object, jlong compoundId, jlong childId) { + btCompoundShape* shape = reinterpret_cast(compoundId); + if (shape == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + btCollisionShape* child = reinterpret_cast(childId); + if (shape == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + shape->removeChildShape(child); + return 0; + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CompoundCollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CompoundCollisionShape.h new file mode 100644 index 000000000..18783ce1d --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CompoundCollisionShape.h @@ -0,0 +1,37 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_CompoundCollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_CompoundCollisionShape +#define _Included_com_jme3_bullet_collision_shapes_CompoundCollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_CompoundCollisionShape + * Method: createShape + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_createShape + (JNIEnv *, jobject); + +/* + * Class: com_jme3_bullet_collision_shapes_CompoundCollisionShape + * Method: addChildShape + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_addChildShape + (JNIEnv *, jobject, jlong, jlong, jobject, jobject); + +/* + * Class: com_jme3_bullet_collision_shapes_CompoundCollisionShape + * Method: removeChildShape + * Signature: (JJ)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_removeChildShape + (JNIEnv *, jobject, jlong, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_ConeCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_ConeCollisionShape.cpp new file mode 100644 index 000000000..d218975b0 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_ConeCollisionShape.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_shapes_ConeCollisionShape.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_shapes_ConeCollisionShape + * Method: createShape + * Signature: (IFF)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_ConeCollisionShape_createShape + (JNIEnv * env, jobject object, jint axis, jfloat radius, jfloat height) { + jmeClasses::initJavaClasses(env); + btCollisionShape* shape; + switch (axis) { + case 0: + shape = new btConeShapeX(radius, height); + break; + case 1: + shape = new btConeShape(radius, height); + break; + case 2: + shape = new btConeShapeZ(radius, height); + break; + } + return reinterpret_cast(shape); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_ConeCollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_ConeCollisionShape.h new file mode 100644 index 000000000..711276ebb --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_ConeCollisionShape.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_ConeCollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_ConeCollisionShape +#define _Included_com_jme3_bullet_collision_shapes_ConeCollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_ConeCollisionShape + * Method: createShape + * Signature: (IFF)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_ConeCollisionShape_createShape + (JNIEnv *, jobject, jint, jfloat, jfloat); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CylinderCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CylinderCollisionShape.cpp new file mode 100644 index 000000000..fbab5baf7 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CylinderCollisionShape.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_shapes_CylinderCollisionShape.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_shapes_CylinderCollisionShape + * Method: createShape + * Signature: (ILcom/jme3/math/Vector3f;)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CylinderCollisionShape_createShape + (JNIEnv * env, jobject object, jint axis, jobject halfExtents) { + jmeClasses::initJavaClasses(env); + btVector3 extents = btVector3(); + jmeBulletUtil::convert(env, halfExtents, &extents); + btCollisionShape* shape; + switch (axis) { + case 0: + shape = new btCylinderShapeX(extents); + break; + case 1: + shape = new btCylinderShape(extents); + break; + case 2: + shape = new btCylinderShapeZ(extents); + break; + } + return reinterpret_cast(shape); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CylinderCollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CylinderCollisionShape.h new file mode 100644 index 000000000..48a665ac7 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CylinderCollisionShape.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_CylinderCollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_CylinderCollisionShape +#define _Included_com_jme3_bullet_collision_shapes_CylinderCollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_CylinderCollisionShape + * Method: createShape + * Signature: (ILcom/jme3/math/Vector3f;)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CylinderCollisionShape_createShape + (JNIEnv *, jobject, jint, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_GImpactCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_GImpactCollisionShape.cpp new file mode 100644 index 000000000..d3e0ba719 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_GImpactCollisionShape.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_shapes_GImpactCollisionShape.h" +#include "jmeBulletUtil.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_shapes_GImpactCollisionShape + * Method: createShape + * Signature: (J)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_GImpactCollisionShape_createShape + (JNIEnv * env, jobject object, jlong meshId) { + jmeClasses::initJavaClasses(env); + btTriangleIndexVertexArray* array = reinterpret_cast(meshId); + btGImpactMeshShape* shape = new btGImpactMeshShape(array); + return reinterpret_cast(shape); + } + + /* + * Class: com_jme3_bullet_collision_shapes_GImpactCollisionShape + * Method: finalizeNative + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_GImpactCollisionShape_finalizeNative + (JNIEnv * env, jobject object, jlong meshId) { + btTriangleIndexVertexArray* array = reinterpret_cast (meshId); + delete(array); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_GImpactCollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_GImpactCollisionShape.h new file mode 100644 index 000000000..f08d3ebf8 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_GImpactCollisionShape.h @@ -0,0 +1,29 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_GImpactCollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_GImpactCollisionShape +#define _Included_com_jme3_bullet_collision_shapes_GImpactCollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_GImpactCollisionShape + * Method: createShape + * Signature: (J)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_GImpactCollisionShape_createShape + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_shapes_GImpactCollisionShape + * Method: finalizeNative + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_GImpactCollisionShape_finalizeNative + (JNIEnv *, jobject, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.cpp new file mode 100644 index 000000000..c217e3a2d --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.h" +#include "jmeBulletUtil.h" +#include "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_shapes_HeightfieldCollisionShape + * Method: createShape + * Signature: (II[FFFFIZ)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_HeightfieldCollisionShape_createShape + (JNIEnv * env, jobject object, jint heightStickWidth, jint heightStickLength, jobject heightfieldData, jfloat heightScale, jfloat minHeight, jfloat maxHeight, jint upAxis, jboolean flipQuadEdges) { + jmeClasses::initJavaClasses(env); + void* data = env->GetDirectBufferAddress(heightfieldData); + btHeightfieldTerrainShape* shape=new btHeightfieldTerrainShape(heightStickWidth, heightStickLength, data, heightScale, minHeight, maxHeight, upAxis, PHY_FLOAT, flipQuadEdges); + return reinterpret_cast(shape); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.h new file mode 100644 index 000000000..a3d1621b8 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_HeightfieldCollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_HeightfieldCollisionShape +#define _Included_com_jme3_bullet_collision_shapes_HeightfieldCollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_HeightfieldCollisionShape + * Method: createShape + * Signature: (IILjava/nio/ByteBuffer;FFFIZ)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_HeightfieldCollisionShape_createShape + (JNIEnv *, jobject, jint, jint, jobject, jfloat, jfloat, jfloat, jint, jboolean); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp new file mode 100644 index 000000000..e3878d01e --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_shapes_HullCollisionShape.h" +#include "jmeBulletUtil.h" +#include "BulletCollision/CollisionShapes/btConvexHullShape.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_shapes_HullCollisionShape + * Method: createShape + * Signature: ([F)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_HullCollisionShape_createShape + (JNIEnv *env, jobject object, jobject array) { + jmeClasses::initJavaClasses(env); + float* data = (float*) env->GetDirectBufferAddress(array); + //TODO: capacity will not always be length! + int length = env->GetDirectBufferCapacity(array)/4; + btConvexHullShape* shape = new btConvexHullShape(); + for (int i = 0; i < length; i+=3) { + btVector3 vect = btVector3(data[i], + data[i + 1], + data[i + 2]); + + shape->addPoint(vect); + } + + return reinterpret_cast(shape); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.h new file mode 100644 index 000000000..42a267216 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_HullCollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_HullCollisionShape +#define _Included_com_jme3_bullet_collision_shapes_HullCollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_HullCollisionShape + * Method: createShape + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_HullCollisionShape_createShape + (JNIEnv *, jobject, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_MeshCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_MeshCollisionShape.cpp new file mode 100644 index 000000000..a5eabcb3c --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_MeshCollisionShape.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** +* Author: Normen Hansen +*/ +#include "com_jme3_bullet_collision_shapes_MeshCollisionShape.h" +#include "jmeBulletUtil.h" +#include "BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h" +#include "btBulletDynamicsCommon.h" +#include "BulletCollision/Gimpact/btGImpactShape.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_shapes_MeshCollisionShape + * Method: createShape + * Signature: (J)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_createShape + (JNIEnv* env, jobject object,jboolean isMemoryEfficient,jboolean buildBVH, jlong arrayId) { + jmeClasses::initJavaClasses(env); + btTriangleIndexVertexArray* array = reinterpret_cast(arrayId); + btBvhTriangleMeshShape* shape = new btBvhTriangleMeshShape(array, isMemoryEfficient, buildBVH); + return reinterpret_cast(shape); + } + + JNIEXPORT jbyteArray JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_saveBVH(JNIEnv* env, jobject, jlong meshobj){ + btBvhTriangleMeshShape* mesh = reinterpret_cast(meshobj); + btOptimizedBvh* bvh = mesh->getOptimizedBvh(); + unsigned int ssize = bvh->calculateSerializeBufferSize(); + char* buffer = (char*)btAlignedAlloc(ssize, 16); + bool success = bvh->serialize(buffer, ssize, true); + if(!success){ + jclass newExc = env->FindClass("java/lang/RuntimeException"); + env->ThrowNew(newExc, "Unableto Serialize, native error reported"); + } + + jbyteArray byteArray = env->NewByteArray(ssize); + env->SetByteArrayRegion(byteArray, 0, ssize , (jbyte*) buffer); + btAlignedFree(buffer); + return byteArray; + }; + + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_setBVH(JNIEnv* env, jobject,jbyteArray bytearray,jlong meshobj){ + int len = env->GetArrayLength (bytearray); + void* buffer = btAlignedAlloc(len, 16); + env->GetByteArrayRegion (bytearray, 0, len, reinterpret_cast(buffer)); + + btOptimizedBvh* bhv = btOptimizedBvh::deSerializeInPlace(buffer, len, true); + btBvhTriangleMeshShape* mesh = reinterpret_cast(meshobj); + mesh->setOptimizedBvh(bhv); + return reinterpret_cast(buffer); + }; + + /* + * Class: com_jme3_bullet_collision_shapes_MeshCollisionShape + * Method: finalizeNative + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_finalizeNative + (JNIEnv* env, jobject object, jlong arrayId,jlong nativeBVHBuffer){ + btTriangleIndexVertexArray* array = reinterpret_cast(arrayId); + delete(array); + if (nativeBVHBuffer > 0) { + void* buffer = reinterpret_cast(nativeBVHBuffer); + btAlignedFree(buffer); + } + } + + +#ifdef __cplusplus +} +#endif + diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_MeshCollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_MeshCollisionShape.h new file mode 100644 index 000000000..11b2aa32a --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_MeshCollisionShape.h @@ -0,0 +1,45 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_MeshCollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_MeshCollisionShape +#define _Included_com_jme3_bullet_collision_shapes_MeshCollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_MeshCollisionShape + * Method: setBVH + * Signature: ([BJ)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_setBVH + (JNIEnv *, jobject, jbyteArray, jlong); + +/* + * Class: com_jme3_bullet_collision_shapes_MeshCollisionShape + * Method: saveBVH + * Signature: (J)[B + */ +JNIEXPORT jbyteArray JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_saveBVH + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_collision_shapes_MeshCollisionShape + * Method: createShape + * Signature: (ZZJ)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_createShape + (JNIEnv *, jobject, jboolean, jboolean, jlong); + +/* + * Class: com_jme3_bullet_collision_shapes_MeshCollisionShape + * Method: finalizeNative + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_finalizeNative + (JNIEnv *, jobject, jlong, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_PlaneCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_PlaneCollisionShape.cpp new file mode 100644 index 000000000..6362516ad --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_PlaneCollisionShape.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_shapes_PlaneCollisionShape.h" +#include "jmeBulletUtil.h" +#include "BulletCollision/CollisionShapes/btStaticPlaneShape.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_shapes_PlaneCollisionShape + * Method: createShape + * Signature: (Lcom/jme3/math/Vector3f;F)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_PlaneCollisionShape_createShape + (JNIEnv * env, jobject object, jobject normal, jfloat constant) { + jmeClasses::initJavaClasses(env); + btVector3 norm = btVector3(); + jmeBulletUtil::convert(env, normal, &norm); + btStaticPlaneShape* shape = new btStaticPlaneShape(norm, constant); + return reinterpret_cast(shape); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_PlaneCollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_PlaneCollisionShape.h new file mode 100644 index 000000000..7e7a22bdf --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_PlaneCollisionShape.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_PlaneCollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_PlaneCollisionShape +#define _Included_com_jme3_bullet_collision_shapes_PlaneCollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_PlaneCollisionShape + * Method: createShape + * Signature: (Lcom/jme3/math/Vector3f;F)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_PlaneCollisionShape_createShape + (JNIEnv *, jobject, jobject, jfloat); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SimplexCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SimplexCollisionShape.cpp new file mode 100644 index 000000000..43a4ddf8a --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SimplexCollisionShape.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_shapes_SimplexCollisionShape.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_shapes_SimplexCollisionShape + * Method: createShape + * Signature: (Lcom/jme3/math/Vector3f;)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2 + (JNIEnv *env, jobject object, jobject vector1) { + jmeClasses::initJavaClasses(env); + btVector3 vec1 = btVector3(); + jmeBulletUtil::convert(env, vector1, &vec1); + btBU_Simplex1to4* simplexShape = new btBU_Simplex1to4(vec1); + return reinterpret_cast(simplexShape); + } + + /* + * Class: com_jme3_bullet_collision_shapes_SimplexCollisionShape + * Method: createShape + * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2 + (JNIEnv *env, jobject object, jobject vector1, jobject vector2) { + jmeClasses::initJavaClasses(env); + btVector3 vec1 = btVector3(); + jmeBulletUtil::convert(env, vector1, &vec1); + btVector3 vec2 = btVector3(); + jmeBulletUtil::convert(env, vector2, &vec2); + btBU_Simplex1to4* simplexShape = new btBU_Simplex1to4(vec1, vec2); + return reinterpret_cast(simplexShape); + } + /* + * Class: com_jme3_bullet_collision_shapes_SimplexCollisionShape + * Method: createShape + * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2 + (JNIEnv * env, jobject object, jobject vector1, jobject vector2, jobject vector3) { + jmeClasses::initJavaClasses(env); + btVector3 vec1 = btVector3(); + jmeBulletUtil::convert(env, vector1, &vec1); + btVector3 vec2 = btVector3(); + jmeBulletUtil::convert(env, vector2, &vec2); + btVector3 vec3 = btVector3(); + jmeBulletUtil::convert(env, vector3, &vec3); + btBU_Simplex1to4* simplexShape = new btBU_Simplex1to4(vec1, vec2, vec3); + return reinterpret_cast(simplexShape); + } + /* + * Class: com_jme3_bullet_collision_shapes_SimplexCollisionShape + * Method: createShape + * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2 + (JNIEnv * env, jobject object, jobject vector1, jobject vector2, jobject vector3, jobject vector4) { + jmeClasses::initJavaClasses(env); + btVector3 vec1 = btVector3(); + jmeBulletUtil::convert(env, vector1, &vec1); + btVector3 vec2 = btVector3(); + jmeBulletUtil::convert(env, vector2, &vec2); + btVector3 vec3 = btVector3(); + jmeBulletUtil::convert(env, vector3, &vec3); + btVector3 vec4 = btVector3(); + jmeBulletUtil::convert(env, vector4, &vec4); + btBU_Simplex1to4* simplexShape = new btBU_Simplex1to4(vec1, vec2, vec3, vec4); + return reinterpret_cast(simplexShape); + } +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SimplexCollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SimplexCollisionShape.h new file mode 100644 index 000000000..e50d06226 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SimplexCollisionShape.h @@ -0,0 +1,45 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_SimplexCollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_SimplexCollisionShape +#define _Included_com_jme3_bullet_collision_shapes_SimplexCollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_SimplexCollisionShape + * Method: createShape + * Signature: (Lcom/jme3/math/Vector3f;)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2 + (JNIEnv *, jobject, jobject); + +/* + * Class: com_jme3_bullet_collision_shapes_SimplexCollisionShape + * Method: createShape + * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2 + (JNIEnv *, jobject, jobject, jobject); + +/* + * Class: com_jme3_bullet_collision_shapes_SimplexCollisionShape + * Method: createShape + * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2 + (JNIEnv *, jobject, jobject, jobject, jobject); + +/* + * Class: com_jme3_bullet_collision_shapes_SimplexCollisionShape + * Method: createShape + * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2 + (JNIEnv *, jobject, jobject, jobject, jobject, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SphereCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SphereCollisionShape.cpp new file mode 100644 index 000000000..e161ed9bf --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SphereCollisionShape.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_collision_shapes_SphereCollisionShape.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_collision_shapes_SphereCollisionShape + * Method: createShape + * Signature: (F)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SphereCollisionShape_createShape + (JNIEnv *env, jobject object, jfloat radius) { + jmeClasses::initJavaClasses(env); + btSphereShape* shape=new btSphereShape(radius); + return reinterpret_cast(shape); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SphereCollisionShape.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SphereCollisionShape.h new file mode 100644 index 000000000..ef1b2cbed --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SphereCollisionShape.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_collision_shapes_SphereCollisionShape */ + +#ifndef _Included_com_jme3_bullet_collision_shapes_SphereCollisionShape +#define _Included_com_jme3_bullet_collision_shapes_SphereCollisionShape +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_collision_shapes_SphereCollisionShape + * Method: createShape + * Signature: (F)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SphereCollisionShape_createShape + (JNIEnv *, jobject, jfloat); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_ConeJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_ConeJoint.cpp new file mode 100644 index 000000000..27c90e1d9 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_ConeJoint.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_joints_ConeJoint.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_joints_ConeJoint + * Method: setLimit + * Signature: (JFFF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_ConeJoint_setLimit + (JNIEnv * env, jobject object, jlong jointId, jfloat swingSpan1, jfloat swingSpan2, jfloat twistSpan) { + btConeTwistConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + //TODO: extended setLimit! + joint->setLimit(swingSpan1, swingSpan2, twistSpan); + } + + /* + * Class: com_jme3_bullet_joints_ConeJoint + * Method: setAngularOnly + * Signature: (JZ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_ConeJoint_setAngularOnly + (JNIEnv * env, jobject object, jlong jointId, jboolean angularOnly) { + btConeTwistConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setAngularOnly(angularOnly); + } + + /* + * Class: com_jme3_bullet_joints_ConeJoint + * Method: createJoint + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_ConeJoint_createJoint + (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject rotA, jobject pivotB, jobject rotB) { + jmeClasses::initJavaClasses(env); + btRigidBody* bodyA = reinterpret_cast(bodyIdA); + btRigidBody* bodyB = reinterpret_cast(bodyIdB); + btMatrix3x3 mtx1 = btMatrix3x3(); + btMatrix3x3 mtx2 = btMatrix3x3(); + btTransform transA = btTransform(mtx1); + jmeBulletUtil::convert(env, pivotA, &transA.getOrigin()); + jmeBulletUtil::convert(env, rotA, &transA.getBasis()); + btTransform transB = btTransform(mtx2); + jmeBulletUtil::convert(env, pivotB, &transB.getOrigin()); + jmeBulletUtil::convert(env, rotB, &transB.getBasis()); + btConeTwistConstraint* joint = new btConeTwistConstraint(*bodyA, *bodyB, transA, transB); + return reinterpret_cast(joint); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_ConeJoint.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_ConeJoint.h new file mode 100644 index 000000000..327b47f25 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_ConeJoint.h @@ -0,0 +1,37 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_joints_ConeJoint */ + +#ifndef _Included_com_jme3_bullet_joints_ConeJoint +#define _Included_com_jme3_bullet_joints_ConeJoint +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_joints_ConeJoint + * Method: setLimit + * Signature: (JFFF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_ConeJoint_setLimit + (JNIEnv *, jobject, jlong, jfloat, jfloat, jfloat); + +/* + * Class: com_jme3_bullet_joints_ConeJoint + * Method: setAngularOnly + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_ConeJoint_setAngularOnly + (JNIEnv *, jobject, jlong, jboolean); + +/* + * Class: com_jme3_bullet_joints_ConeJoint + * Method: createJoint + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_ConeJoint_createJoint + (JNIEnv *, jobject, jlong, jlong, jobject, jobject, jobject, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.cpp new file mode 100644 index 000000000..48ba90f37 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_joints_HingeJoint.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: enableMotor + * Signature: (JZFF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_enableMotor + (JNIEnv * env, jobject object, jlong jointId, jboolean enable, jfloat targetVelocity, jfloat maxMotorImpulse) { + btHingeConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->enableAngularMotor(enable, targetVelocity, maxMotorImpulse); + } + + /* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: getEnableAngularMotor + * Signature: (J)Z + */ + JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_HingeJoint_getEnableAngularMotor + (JNIEnv * env, jobject object, jlong jointId) { + btHingeConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return false; + } + return joint->getEnableAngularMotor(); + } + + /* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: getMotorTargetVelocity + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getMotorTargetVelocity + (JNIEnv * env, jobject object, jlong jointId) { + btHingeConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getMotorTargetVelosity(); + } + + /* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: getMaxMotorImpulse + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getMaxMotorImpulse + (JNIEnv * env, jobject object, jlong jointId) { + btHingeConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getMaxMotorImpulse(); + } + + /* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: setLimit + * Signature: (JFF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setLimit__JFF + (JNIEnv * env, jobject object, jlong jointId, jfloat low, jfloat high) { + btHingeConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + return joint->setLimit(low, high); + } + + /* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: setLimit + * Signature: (JFFFFF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setLimit__JFFFFF + (JNIEnv * env, jobject object, jlong jointId, jfloat low, jfloat high, jfloat softness, jfloat biasFactor, jfloat relaxationFactor) { + btHingeConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + return joint->setLimit(low, high, softness, biasFactor, relaxationFactor); + } + + /* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: getUpperLimit + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getUpperLimit + (JNIEnv * env, jobject object, jlong jointId) { + btHingeConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getUpperLimit(); + } + + /* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: getLowerLimit + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getLowerLimit + (JNIEnv * env, jobject object, jlong jointId) { + btHingeConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getLowerLimit(); + } + + /* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: setAngularOnly + * Signature: (JZ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setAngularOnly + (JNIEnv * env, jobject object, jlong jointId, jboolean angular) { + btHingeConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setAngularOnly(angular); + } + + /* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: getHingeAngle + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getHingeAngle + (JNIEnv * env, jobject object, jlong jointId) { + btHingeConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getHingeAngle(); + } + + /* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: createJoint + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_HingeJoint_createJoint + (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject axisA, jobject pivotB, jobject axisB) { + jmeClasses::initJavaClasses(env); + btRigidBody* bodyA = reinterpret_cast(bodyIdA); + btRigidBody* bodyB = reinterpret_cast(bodyIdB); + btVector3 vec1 = btVector3(); + btVector3 vec2 = btVector3(); + btVector3 vec3 = btVector3(); + btVector3 vec4 = btVector3(); + jmeBulletUtil::convert(env, pivotA, &vec1); + jmeBulletUtil::convert(env, pivotB, &vec2); + jmeBulletUtil::convert(env, axisA, &vec3); + jmeBulletUtil::convert(env, axisB, &vec4); + btHingeConstraint* joint = new btHingeConstraint(*bodyA, *bodyB, vec1, vec2, vec3, vec4); + return reinterpret_cast(joint); + } +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.h new file mode 100644 index 000000000..ab6e0034f --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.h @@ -0,0 +1,101 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_joints_HingeJoint */ + +#ifndef _Included_com_jme3_bullet_joints_HingeJoint +#define _Included_com_jme3_bullet_joints_HingeJoint +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: enableMotor + * Signature: (JZFF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_enableMotor + (JNIEnv *, jobject, jlong, jboolean, jfloat, jfloat); + +/* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: getEnableAngularMotor + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_HingeJoint_getEnableAngularMotor + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: getMotorTargetVelocity + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getMotorTargetVelocity + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: getMaxMotorImpulse + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getMaxMotorImpulse + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: setLimit + * Signature: (JFF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setLimit__JFF + (JNIEnv *, jobject, jlong, jfloat, jfloat); + +/* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: setLimit + * Signature: (JFFFFF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setLimit__JFFFFF + (JNIEnv *, jobject, jlong, jfloat, jfloat, jfloat, jfloat, jfloat); + +/* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: getUpperLimit + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getUpperLimit + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: getLowerLimit + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getLowerLimit + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: setAngularOnly + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setAngularOnly + (JNIEnv *, jobject, jlong, jboolean); + +/* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: getHingeAngle + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getHingeAngle + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_HingeJoint + * Method: createJoint + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_HingeJoint_createJoint + (JNIEnv *, jobject, jlong, jlong, jobject, jobject, jobject, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_PhysicsJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_PhysicsJoint.cpp new file mode 100644 index 000000000..129865034 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_PhysicsJoint.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_joints_PhysicsJoint.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_joints_PhysicsJoint + * Method: getAppliedImpulse + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_PhysicsJoint_getAppliedImpulse + (JNIEnv * env, jobject object, jlong jointId) { + btTypedConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getAppliedImpulse(); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_PhysicsJoint.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_PhysicsJoint.h new file mode 100644 index 000000000..63e21a7d3 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_PhysicsJoint.h @@ -0,0 +1,29 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_joints_PhysicsJoint */ + +#ifndef _Included_com_jme3_bullet_joints_PhysicsJoint +#define _Included_com_jme3_bullet_joints_PhysicsJoint +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_joints_PhysicsJoint + * Method: getAppliedImpulse + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_PhysicsJoint_getAppliedImpulse + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_PhysicsJoint + * Method: finalizeNative + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_PhysicsJoint_finalizeNative + (JNIEnv *, jobject, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_Point2PointJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_Point2PointJoint.cpp new file mode 100644 index 000000000..c1bffb00e --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_Point2PointJoint.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_joints_Point2PointJoint.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: setDamping + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setDamping + (JNIEnv * env, jobject object, jlong jointId, jfloat damping) { + btPoint2PointConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->m_setting.m_damping = damping; + } + + /* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: setImpulseClamp + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setImpulseClamp + (JNIEnv * env, jobject object, jlong jointId, jfloat clamp) { + btPoint2PointConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->m_setting.m_impulseClamp = clamp; + } + + /* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: setTau + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setTau + (JNIEnv * env, jobject object, jlong jointId, jfloat tau) { + btPoint2PointConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->m_setting.m_tau = tau; + } + + /* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: getDamping + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getDamping + (JNIEnv * env, jobject object, jlong jointId) { + btPoint2PointConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->m_setting.m_damping; + } + + /* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: getImpulseClamp + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getImpulseClamp + (JNIEnv * env, jobject object, jlong jointId) { + btPoint2PointConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->m_setting.m_damping; + } + + /* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: getTau + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getTau + (JNIEnv * env, jobject object, jlong jointId) { + btPoint2PointConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->m_setting.m_damping; + } + + /* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: createJoint + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_createJoint + (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject pivotB) { + jmeClasses::initJavaClasses(env); + btRigidBody* bodyA = reinterpret_cast(bodyIdA); + btRigidBody* bodyB = reinterpret_cast(bodyIdB); + //TODO: matrix not needed? + btMatrix3x3 mtx1 = btMatrix3x3(); + btMatrix3x3 mtx2 = btMatrix3x3(); + btTransform transA = btTransform(mtx1); + jmeBulletUtil::convert(env, pivotA, &transA.getOrigin()); + btTransform transB = btTransform(mtx2); + jmeBulletUtil::convert(env, pivotB, &transB.getOrigin()); + btHingeConstraint* joint = new btHingeConstraint(*bodyA, *bodyB, transA, transB); + return reinterpret_cast(joint); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_Point2PointJoint.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_Point2PointJoint.h new file mode 100644 index 000000000..5cb8188a8 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_Point2PointJoint.h @@ -0,0 +1,69 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_joints_Point2PointJoint */ + +#ifndef _Included_com_jme3_bullet_joints_Point2PointJoint +#define _Included_com_jme3_bullet_joints_Point2PointJoint +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: setDamping + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setDamping + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: setImpulseClamp + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setImpulseClamp + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: setTau + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setTau + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: getDamping + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getDamping + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: getImpulseClamp + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getImpulseClamp + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: getTau + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getTau + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_Point2PointJoint + * Method: createJoint + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_createJoint + (JNIEnv *, jobject, jlong, jlong, jobject, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofJoint.cpp new file mode 100644 index 000000000..ea55820e0 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofJoint.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_joints_SixDofJoint.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: getRotationalLimitMotor + * Signature: (JI)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_getRotationalLimitMotor + (JNIEnv * env, jobject object, jlong jointId, jint index) { + btGeneric6DofConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return reinterpret_cast(joint->getRotationalLimitMotor(index)); + } + + /* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: getTranslationalLimitMotor + * Signature: (J)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_getTranslationalLimitMotor + (JNIEnv * env, jobject object, jlong jointId) { + btGeneric6DofConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return reinterpret_cast(joint->getTranslationalLimitMotor()); + } + + /* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: setLinearUpperLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setLinearUpperLimit + (JNIEnv * env, jobject object, jlong jointId, jobject vector) { + btGeneric6DofConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec = btVector3(); + jmeBulletUtil::convert(env, vector, &vec); + joint->setLinearUpperLimit(vec); + } + + /* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: setLinearLowerLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setLinearLowerLimit + (JNIEnv * env, jobject object, jlong jointId, jobject vector) { + btGeneric6DofConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec = btVector3(); + jmeBulletUtil::convert(env, vector, &vec); + joint->setLinearLowerLimit(vec); + } + + /* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: setAngularUpperLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setAngularUpperLimit + (JNIEnv * env, jobject object, jlong jointId, jobject vector) { + btGeneric6DofConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec = btVector3(); + jmeBulletUtil::convert(env, vector, &vec); + joint->setAngularUpperLimit(vec); + } + + /* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: setAngularLowerLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setAngularLowerLimit + (JNIEnv * env, jobject object, jlong jointId, jobject vector) { + btGeneric6DofConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec = btVector3(); + jmeBulletUtil::convert(env, vector, &vec); + joint->setAngularLowerLimit(vec); + } + + /* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: createJoint + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_createJoint + (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject rotA, jobject pivotB, jobject rotB, jboolean useLinearReferenceFrameA) { + jmeClasses::initJavaClasses(env); + btRigidBody* bodyA = reinterpret_cast(bodyIdA); + btRigidBody* bodyB = reinterpret_cast(bodyIdB); + btMatrix3x3 mtx1 = btMatrix3x3(); + btMatrix3x3 mtx2 = btMatrix3x3(); + btTransform transA = btTransform(mtx1); + jmeBulletUtil::convert(env, pivotA, &transA.getOrigin()); + jmeBulletUtil::convert(env, rotA, &transA.getBasis()); + btTransform transB = btTransform(mtx2); + jmeBulletUtil::convert(env, pivotB, &transB.getOrigin()); + jmeBulletUtil::convert(env, rotB, &transB.getBasis()); + btGeneric6DofConstraint* joint = new btGeneric6DofConstraint(*bodyA, *bodyB, transA, transB, useLinearReferenceFrameA); + return reinterpret_cast(joint); + } +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofJoint.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofJoint.h new file mode 100644 index 000000000..86e610234 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofJoint.h @@ -0,0 +1,69 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_joints_SixDofJoint */ + +#ifndef _Included_com_jme3_bullet_joints_SixDofJoint +#define _Included_com_jme3_bullet_joints_SixDofJoint +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: getRotationalLimitMotor + * Signature: (JI)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_getRotationalLimitMotor + (JNIEnv *, jobject, jlong, jint); + +/* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: getTranslationalLimitMotor + * Signature: (J)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_getTranslationalLimitMotor + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: setLinearUpperLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setLinearUpperLimit + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: setLinearLowerLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setLinearLowerLimit + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: setAngularUpperLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setAngularUpperLimit + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: setAngularLowerLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setAngularLowerLimit + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_joints_SixDofJoint + * Method: createJoint + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_createJoint + (JNIEnv *, jobject, jlong, jlong, jobject, jobject, jobject, jobject, jboolean); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofSpringJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofSpringJoint.cpp new file mode 100644 index 000000000..da225ee2f --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofSpringJoint.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_joints_SixDofSpringJoint.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Class: com_jme3_bullet_joints_SixDofSpringJoint + * Method: enableString + * Signature: (JIZ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_enableSpring + (JNIEnv *env, jobject object, jlong jointId, jint index, jboolean onOff) { + btGeneric6DofSpringConstraint* joint = reinterpret_cast(jointId); + joint -> enableSpring(index, onOff); +} + + +/* + * Class: com_jme3_bullet_joints_SixDofSpringJoint + * Method: setStiffness + * Signature: (JIF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setStiffness + (JNIEnv *env, jobject object, jlong jointId, jint index, jfloat stiffness) { + btGeneric6DofSpringConstraint* joint = reinterpret_cast(jointId); + joint -> setStiffness(index, stiffness); +} + +/* + * Class: com_jme3_bullet_joints_SixDofSpringJoint + * Method: setDamping + * Signature: (JIF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setDamping + (JNIEnv *env, jobject object, jlong jointId, jint index, jfloat damping) { + btGeneric6DofSpringConstraint* joint = reinterpret_cast(jointId); + joint -> setDamping(index, damping); +} + +/* + * Class: com_jme3_bullet_joints_SixDofSpringJoint + * Method: setEquilibriumPoint + * Signature: (JIF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setEquilibriumPoint__J + (JNIEnv *env, jobject object, jlong jointId) { + btGeneric6DofSpringConstraint* joint = reinterpret_cast(jointId); + joint -> setEquilibriumPoint(); +} + +/* + * Class: com_jme3_bullet_joints_SixDofSpringJoint + * Method: setEquilibriumPoint + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setEquilibriumPoint__JI + (JNIEnv *env, jobject object, jlong jointId, jint index) { + btGeneric6DofSpringConstraint* joint = reinterpret_cast(jointId); + joint -> setEquilibriumPoint(index); +} + + + + +/* + * Class: com_jme3_bullet_joints_SixDofSpringJoint + * Method: createJoint + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_createJoint + (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject rotA, jobject pivotB, jobject rotB, jboolean useLinearReferenceFrameA) { + jmeClasses::initJavaClasses(env); + btRigidBody* bodyA = reinterpret_cast(bodyIdA); + btRigidBody* bodyB = reinterpret_cast(bodyIdB); + btTransform transA; + jmeBulletUtil::convert(env, pivotA, &transA.getOrigin()); + jmeBulletUtil::convert(env, rotA, &transA.getBasis()); + btTransform transB; + jmeBulletUtil::convert(env, pivotB, &transB.getOrigin()); + jmeBulletUtil::convert(env, rotB, &transB.getBasis()); + + btGeneric6DofSpringConstraint* joint = new btGeneric6DofSpringConstraint(*bodyA, *bodyB, transA, transB, useLinearReferenceFrameA); + return reinterpret_cast(joint); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofSpringJoint.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofSpringJoint.h new file mode 100644 index 000000000..b4fced0c9 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofSpringJoint.h @@ -0,0 +1,61 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_joints_SixDofSpringJoint */ + +#ifndef _Included_com_jme3_bullet_joints_SixDofSpringJoint +#define _Included_com_jme3_bullet_joints_SixDofSpringJoint +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_joints_SixDofSpringJoint + * Method: enableSpring + * Signature: (JIZ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_enableSpring + (JNIEnv *, jobject, jlong, jint, jboolean); + +/* + * Class: com_jme3_bullet_joints_SixDofSpringJoint + * Method: setStiffness + * Signature: (JIF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setStiffness + (JNIEnv *, jobject, jlong, jint, jfloat); + +/* + * Class: com_jme3_bullet_joints_SixDofSpringJoint + * Method: setDamping + * Signature: (JIF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setDamping + (JNIEnv *, jobject, jlong, jint, jfloat); + +/* + * Class: com_jme3_bullet_joints_SixDofSpringJoint + * Method: setEquilibriumPoint + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setEquilibriumPoint__J + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SixDofSpringJoint + * Method: setEquilibriumPoint + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setEquilibriumPoint__JI + (JNIEnv *, jobject, jlong, jint); + +/* + * Class: com_jme3_bullet_joints_SixDofSpringJoint + * Method: createJoint + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_createJoint + (JNIEnv *, jobject, jlong, jlong, jobject, jobject, jobject, jobject, jboolean); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SliderJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SliderJoint.cpp new file mode 100644 index 000000000..0817d7975 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SliderJoint.cpp @@ -0,0 +1,963 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_joints_SliderJoint.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getLowerLinLimit + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getLowerLinLimit + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getLowerLinLimit(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setLowerLinLimit + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setLowerLinLimit + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setLowerLinLimit(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getUpperLinLimit + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getUpperLinLimit + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getUpperLinLimit(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setUpperLinLimit + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setUpperLinLimit + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setUpperLinLimit(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getLowerAngLimit + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getLowerAngLimit + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getLowerAngLimit(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setLowerAngLimit + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setLowerAngLimit + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setLowerAngLimit(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getUpperAngLimit + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getUpperAngLimit + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getUpperAngLimit(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setUpperAngLimit + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setUpperAngLimit + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setUpperAngLimit(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getSoftnessDirLin + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessDirLin + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getSoftnessDirLin(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setSoftnessDirLin + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessDirLin + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setSoftnessDirLin(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getRestitutionDirLin + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionDirLin + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getRestitutionDirLin(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setRestitutionDirLin + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionDirLin + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setRestitutionDirLin(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getDampingDirLin + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingDirLin + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getDampingDirLin(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setDampingDirLin + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingDirLin + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setDampingDirLin(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getSoftnessDirAng + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessDirAng + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getSoftnessDirAng(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setSoftnessDirAng + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessDirAng + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setSoftnessDirAng(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getRestitutionDirAng + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionDirAng + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getRestitutionDirAng(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setRestitutionDirAng + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionDirAng + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setRestitutionDirAng(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getDampingDirAng + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingDirAng + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getDampingDirAng(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setDampingDirAng + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingDirAng + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setDampingDirAng(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getSoftnessLimLin + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessLimLin + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getSoftnessLimLin(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setSoftnessLimLin + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessLimLin + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setSoftnessLimLin(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getRestitutionLimLin + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionLimLin + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getRestitutionLimLin(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setRestitutionLimLin + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionLimLin + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setRestitutionLimLin(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getDampingLimLin + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingLimLin + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getDampingLimLin(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setDampingLimLin + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingLimLin + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setDampingLimLin(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getSoftnessLimAng + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessLimAng + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getSoftnessLimAng(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setSoftnessLimAng + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessLimAng + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setSoftnessLimAng(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getRestitutionLimAng + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionLimAng + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getRestitutionLimAng(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setRestitutionLimAng + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionLimAng + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setRestitutionLimAng(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getDampingLimAng + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingLimAng + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getDampingLimAng(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setDampingLimAng + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingLimAng + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setDampingLimAng(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getSoftnessOrthoLin + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessOrthoLin + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getSoftnessOrthoLin(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setSoftnessOrthoLin + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessOrthoLin + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setSoftnessOrthoLin(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getRestitutionOrthoLin + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionOrthoLin + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getRestitutionOrthoLin(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setRestitutionOrthoLin + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionOrthoLin + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setRestitutionOrthoLin(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getDampingOrthoLin + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingOrthoLin + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getDampingOrthoLin(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setDampingOrthoLin + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingOrthoLin + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setDampingOrthoLin(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getSoftnessOrthoAng + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessOrthoAng + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getSoftnessOrthoAng(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setSoftnessOrthoAng + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessOrthoAng + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setSoftnessOrthoAng(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getRestitutionOrthoAng + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionOrthoAng + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getRestitutionOrthoAng(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setRestitutionOrthoAng + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionOrthoAng + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setRestitutionOrthoAng(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getDampingOrthoAng + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingOrthoAng + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getDampingOrthoAng(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setDampingOrthoAng + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingOrthoAng + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setDampingOrthoAng(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: isPoweredLinMotor + * Signature: (J)Z + */ + JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_SliderJoint_isPoweredLinMotor + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return false; + } + return joint->getPoweredLinMotor(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setPoweredLinMotor + * Signature: (JZ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setPoweredLinMotor + (JNIEnv * env, jobject object, jlong jointId, jboolean value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setPoweredLinMotor(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getTargetLinMotorVelocity + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getTargetLinMotorVelocity + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getTargetLinMotorVelocity(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setTargetLinMotorVelocity + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setTargetLinMotorVelocity + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setTargetLinMotorVelocity(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getMaxLinMotorForce + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getMaxLinMotorForce + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getMaxLinMotorForce(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setMaxLinMotorForce + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setMaxLinMotorForce + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setMaxLinMotorForce(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: isPoweredAngMotor + * Signature: (J)Z + */ + JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_SliderJoint_isPoweredAngMotor + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return false; + } + return joint->getPoweredAngMotor(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setPoweredAngMotor + * Signature: (JZ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setPoweredAngMotor + (JNIEnv * env, jobject object, jlong jointId, jboolean value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setPoweredAngMotor(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getTargetAngMotorVelocity + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getTargetAngMotorVelocity + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getTargetAngMotorVelocity(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setTargetAngMotorVelocity + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setTargetAngMotorVelocity + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setTargetAngMotorVelocity(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getMaxAngMotorForce + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getMaxAngMotorForce + (JNIEnv * env, jobject object, jlong jointId) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return joint->getMaxAngMotorForce(); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setMaxAngMotorForce + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setMaxAngMotorForce + (JNIEnv * env, jobject object, jlong jointId, jfloat value) { + btSliderConstraint* joint = reinterpret_cast(jointId); + if (joint == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + joint->setMaxAngMotorForce(value); + } + + /* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: createJoint + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SliderJoint_createJoint + (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject rotA, jobject pivotB, jobject rotB, jboolean useLinearReferenceFrameA) { + jmeClasses::initJavaClasses(env); + btRigidBody* bodyA = reinterpret_cast(bodyIdA); + btRigidBody* bodyB = reinterpret_cast(bodyIdB); + btMatrix3x3 mtx1 = btMatrix3x3(); + btMatrix3x3 mtx2 = btMatrix3x3(); + btTransform transA = btTransform(mtx1); + jmeBulletUtil::convert(env, pivotA, &transA.getOrigin()); + jmeBulletUtil::convert(env, rotA, &transA.getBasis()); + btTransform transB = btTransform(mtx2); + jmeBulletUtil::convert(env, pivotB, &transB.getOrigin()); + jmeBulletUtil::convert(env, rotB, &transB.getBasis()); + btSliderConstraint* joint = new btSliderConstraint(*bodyA, *bodyB, transA, transB, useLinearReferenceFrameA); + return reinterpret_cast(joint); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SliderJoint.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SliderJoint.h new file mode 100644 index 000000000..7afd66a56 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SliderJoint.h @@ -0,0 +1,469 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_joints_SliderJoint */ + +#ifndef _Included_com_jme3_bullet_joints_SliderJoint +#define _Included_com_jme3_bullet_joints_SliderJoint +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getLowerLinLimit + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getLowerLinLimit + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setLowerLinLimit + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setLowerLinLimit + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getUpperLinLimit + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getUpperLinLimit + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setUpperLinLimit + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setUpperLinLimit + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getLowerAngLimit + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getLowerAngLimit + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setLowerAngLimit + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setLowerAngLimit + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getUpperAngLimit + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getUpperAngLimit + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setUpperAngLimit + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setUpperAngLimit + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getSoftnessDirLin + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessDirLin + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setSoftnessDirLin + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessDirLin + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getRestitutionDirLin + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionDirLin + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setRestitutionDirLin + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionDirLin + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getDampingDirLin + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingDirLin + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setDampingDirLin + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingDirLin + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getSoftnessDirAng + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessDirAng + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setSoftnessDirAng + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessDirAng + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getRestitutionDirAng + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionDirAng + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setRestitutionDirAng + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionDirAng + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getDampingDirAng + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingDirAng + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setDampingDirAng + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingDirAng + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getSoftnessLimLin + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessLimLin + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setSoftnessLimLin + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessLimLin + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getRestitutionLimLin + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionLimLin + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setRestitutionLimLin + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionLimLin + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getDampingLimLin + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingLimLin + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setDampingLimLin + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingLimLin + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getSoftnessLimAng + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessLimAng + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setSoftnessLimAng + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessLimAng + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getRestitutionLimAng + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionLimAng + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setRestitutionLimAng + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionLimAng + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getDampingLimAng + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingLimAng + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setDampingLimAng + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingLimAng + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getSoftnessOrthoLin + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessOrthoLin + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setSoftnessOrthoLin + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessOrthoLin + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getRestitutionOrthoLin + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionOrthoLin + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setRestitutionOrthoLin + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionOrthoLin + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getDampingOrthoLin + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingOrthoLin + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setDampingOrthoLin + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingOrthoLin + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getSoftnessOrthoAng + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessOrthoAng + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setSoftnessOrthoAng + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessOrthoAng + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getRestitutionOrthoAng + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionOrthoAng + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setRestitutionOrthoAng + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionOrthoAng + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getDampingOrthoAng + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingOrthoAng + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setDampingOrthoAng + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingOrthoAng + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: isPoweredLinMotor + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_SliderJoint_isPoweredLinMotor + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setPoweredLinMotor + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setPoweredLinMotor + (JNIEnv *, jobject, jlong, jboolean); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getTargetLinMotorVelocity + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getTargetLinMotorVelocity + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setTargetLinMotorVelocity + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setTargetLinMotorVelocity + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getMaxLinMotorForce + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getMaxLinMotorForce + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setMaxLinMotorForce + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setMaxLinMotorForce + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: isPoweredAngMotor + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_SliderJoint_isPoweredAngMotor + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setPoweredAngMotor + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setPoweredAngMotor + (JNIEnv *, jobject, jlong, jboolean); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getTargetAngMotorVelocity + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getTargetAngMotorVelocity + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setTargetAngMotorVelocity + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setTargetAngMotorVelocity + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: getMaxAngMotorForce + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getMaxAngMotorForce + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: setMaxAngMotorForce + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setMaxAngMotorForce + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_SliderJoint + * Method: createJoint + * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SliderJoint_createJoint + (JNIEnv *, jobject, jlong, jlong, jobject, jobject, jobject, jobject, jboolean); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_RotationalLimitMotor.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_RotationalLimitMotor.cpp new file mode 100644 index 000000000..396d47ae3 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_RotationalLimitMotor.cpp @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_joints_motors_RotationalLimitMotor.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getLoLimit + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getLoLimit + (JNIEnv *env, jobject object, jlong motorId) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return motor->m_loLimit; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setLoLimit + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setLoLimit + (JNIEnv *env, jobject object, jlong motorId, jfloat value) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_loLimit = value; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getHiLimit + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getHiLimit + (JNIEnv *env, jobject object, jlong motorId) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return motor->m_hiLimit; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setHiLimit + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setHiLimit + (JNIEnv *env, jobject object, jlong motorId, jfloat value) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_hiLimit = value; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getTargetVelocity + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getTargetVelocity + (JNIEnv *env, jobject object, jlong motorId) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return motor->m_targetVelocity; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setTargetVelocity + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setTargetVelocity + (JNIEnv *env, jobject object, jlong motorId, jfloat value) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_targetVelocity = value; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getMaxMotorForce + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getMaxMotorForce + (JNIEnv *env, jobject object, jlong motorId) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return motor->m_maxMotorForce; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setMaxMotorForce + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setMaxMotorForce + (JNIEnv *env, jobject object, jlong motorId, jfloat value) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_maxMotorForce = value; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getMaxLimitForce + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getMaxLimitForce + (JNIEnv *env, jobject object, jlong motorId) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return motor->m_maxLimitForce; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setMaxLimitForce + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setMaxLimitForce + (JNIEnv *env, jobject object, jlong motorId, jfloat value) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_maxLimitForce = value; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getDamping + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getDamping + (JNIEnv *env, jobject object, jlong motorId) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return motor->m_damping; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setDamping + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setDamping + (JNIEnv *env, jobject object, jlong motorId, jfloat value) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_damping = value; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getLimitSoftness + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getLimitSoftness + (JNIEnv *env, jobject object, jlong motorId) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return motor->m_limitSoftness; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setLimitSoftness + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setLimitSoftness + (JNIEnv *env, jobject object, jlong motorId, jfloat value) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_limitSoftness = value; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getERP + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getERP + (JNIEnv *env, jobject object, jlong motorId) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return motor->m_stopERP; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setERP + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setERP + (JNIEnv *env, jobject object, jlong motorId, jfloat value) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_stopERP = value; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getBounce + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getBounce + (JNIEnv *env, jobject object, jlong motorId) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return motor->m_bounce; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setBounce + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setBounce + (JNIEnv *env, jobject object, jlong motorId, jfloat value) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_bounce = value; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: isEnableMotor + * Signature: (J)Z + */ + JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_isEnableMotor + (JNIEnv *env, jobject object, jlong motorId) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return false; + } + return motor->m_enableMotor; + } + + /* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setEnableMotor + * Signature: (JZ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setEnableMotor + (JNIEnv *env, jobject object, jlong motorId, jboolean value) { + btRotationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_enableMotor = value; + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_RotationalLimitMotor.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_RotationalLimitMotor.h new file mode 100644 index 000000000..b14bf1d73 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_RotationalLimitMotor.h @@ -0,0 +1,173 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_joints_motors_RotationalLimitMotor */ + +#ifndef _Included_com_jme3_bullet_joints_motors_RotationalLimitMotor +#define _Included_com_jme3_bullet_joints_motors_RotationalLimitMotor +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getLoLimit + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getLoLimit + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setLoLimit + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setLoLimit + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getHiLimit + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getHiLimit + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setHiLimit + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setHiLimit + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getTargetVelocity + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getTargetVelocity + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setTargetVelocity + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setTargetVelocity + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getMaxMotorForce + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getMaxMotorForce + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setMaxMotorForce + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setMaxMotorForce + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getMaxLimitForce + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getMaxLimitForce + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setMaxLimitForce + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setMaxLimitForce + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getDamping + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getDamping + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setDamping + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setDamping + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getLimitSoftness + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getLimitSoftness + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setLimitSoftness + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setLimitSoftness + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getERP + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getERP + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setERP + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setERP + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: getBounce + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getBounce + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setBounce + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setBounce + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: isEnableMotor + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_isEnableMotor + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor + * Method: setEnableMotor + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setEnableMotor + (JNIEnv *, jobject, jlong, jboolean); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_TranslationalLimitMotor.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_TranslationalLimitMotor.cpp new file mode 100644 index 000000000..ecc6c429b --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_TranslationalLimitMotor.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_joints_motors_TranslationalLimitMotor.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: getLowerLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getLowerLimit + (JNIEnv *env, jobject object, jlong motorId, jobject vector) { + btTranslationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &motor->m_lowerLimit, vector); + } + + /* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: setLowerLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setLowerLimit + (JNIEnv *env, jobject object, jlong motorId, jobject vector) { + btTranslationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, vector, &motor->m_lowerLimit); + } + + /* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: getUpperLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getUpperLimit + (JNIEnv *env, jobject object, jlong motorId, jobject vector) { + btTranslationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &motor->m_upperLimit, vector); + } + + /* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: setUpperLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setUpperLimit + (JNIEnv *env, jobject object, jlong motorId, jobject vector) { + btTranslationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, vector, &motor->m_upperLimit); + } + + /* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: getAccumulatedImpulse + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getAccumulatedImpulse + (JNIEnv *env, jobject object, jlong motorId, jobject vector) { + btTranslationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &motor->m_accumulatedImpulse, vector); + } + + /* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: setAccumulatedImpulse + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setAccumulatedImpulse + (JNIEnv *env, jobject object, jlong motorId, jobject vector) { + btTranslationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, vector, &motor->m_accumulatedImpulse); + } + + /* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: getLimitSoftness + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getLetLimitSoftness + (JNIEnv *env, jobject object, jlong motorId) { + btTranslationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return motor->m_limitSoftness; + } + + /* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: setLimitSoftness + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setLimitSoftness + (JNIEnv *env, jobject object, jlong motorId, jfloat value) { + btTranslationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_limitSoftness = value; + } + + /* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: getDamping + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getDamping + (JNIEnv *env, jobject object, jlong motorId) { + btTranslationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return motor->m_damping; + } + + /* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: setDamping + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setDamping + (JNIEnv *env, jobject object, jlong motorId, jfloat value) { + btTranslationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_damping = value; + } + + /* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: getRestitution + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getRestitution + (JNIEnv *env, jobject object, jlong motorId) { + btTranslationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return motor->m_restitution; + } + + /* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: setRestitution + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setRestitution + (JNIEnv *env, jobject object, jlong motorId, jfloat value) { + btTranslationalLimitMotor* motor = reinterpret_cast(motorId); + if (motor == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + motor->m_restitution = value; + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_TranslationalLimitMotor.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_TranslationalLimitMotor.h new file mode 100644 index 000000000..0ea93e2b1 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_TranslationalLimitMotor.h @@ -0,0 +1,109 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_joints_motors_TranslationalLimitMotor */ + +#ifndef _Included_com_jme3_bullet_joints_motors_TranslationalLimitMotor +#define _Included_com_jme3_bullet_joints_motors_TranslationalLimitMotor +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: getLowerLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getLowerLimit + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: setLowerLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setLowerLimit + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: getUpperLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getUpperLimit + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: setUpperLimit + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setUpperLimit + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: getAccumulatedImpulse + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getAccumulatedImpulse + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: setAccumulatedImpulse + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setAccumulatedImpulse + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: getLimitSoftness + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getLimitSoftness + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: setLimitSoftness + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setLimitSoftness + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: getDamping + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getDamping + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: setDamping + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setDamping + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: getRestitution + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getRestitution + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor + * Method: setRestitution + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setRestitution + (JNIEnv *, jobject, jlong, jfloat); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsCharacter.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsCharacter.cpp new file mode 100644 index 000000000..b5c481782 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsCharacter.cpp @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ + +#include "com_jme3_bullet_objects_PhysicsCharacter.h" +#include "jmeBulletUtil.h" +#include "BulletCollision/CollisionDispatch/btGhostObject.h" +#include "BulletDynamics/Character/btKinematicCharacterController.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: createGhostObject + * Signature: ()J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_createGhostObject + (JNIEnv * env, jobject object) { + jmeClasses::initJavaClasses(env); + btPairCachingGhostObject* ghost = new btPairCachingGhostObject(); + return reinterpret_cast(ghost); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setCharacterFlags + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCharacterFlags + (JNIEnv *env, jobject object, jlong ghostId) { + btPairCachingGhostObject* ghost = reinterpret_cast(ghostId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + ghost->setCollisionFlags(/*ghost->getCollisionFlags() |*/ btCollisionObject::CF_CHARACTER_OBJECT); + ghost->setCollisionFlags(ghost->getCollisionFlags() & ~btCollisionObject::CF_NO_CONTACT_RESPONSE); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: createCharacterObject + * Signature: (JJF)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_createCharacterObject + (JNIEnv *env, jobject object, jlong objectId, jlong shapeId, jfloat stepHeight) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + //TODO: check convexshape! + btConvexShape* shape = reinterpret_cast(shapeId); + btKinematicCharacterController* character = new btKinematicCharacterController(ghost, shape, stepHeight); + return reinterpret_cast(character); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: warp + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_warp + (JNIEnv *env, jobject object, jlong objectId, jobject vector) { + btKinematicCharacterController* character = reinterpret_cast(objectId); + if (character == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec = btVector3(); + jmeBulletUtil::convert(env, vector, &vec); + character->warp(vec); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setWalkDirection + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setWalkDirection + (JNIEnv *env, jobject object, jlong objectId, jobject vector) { + btKinematicCharacterController* character = reinterpret_cast(objectId); + if (character == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec = btVector3(); + jmeBulletUtil::convert(env, vector, &vec); + character->setWalkDirection(vec); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setUpAxis + * Signature: (JI)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setUpAxis + (JNIEnv *env, jobject object, jlong objectId, jint value) { + btKinematicCharacterController* character = reinterpret_cast(objectId); + if (character == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + character->setUpAxis(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setFallSpeed + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setFallSpeed + (JNIEnv *env, jobject object, jlong objectId, jfloat value) { + btKinematicCharacterController* character = reinterpret_cast(objectId); + if (character == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + character->setFallSpeed(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setJumpSpeed + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setJumpSpeed + (JNIEnv *env, jobject object, jlong objectId, jfloat value) { + btKinematicCharacterController* character = reinterpret_cast(objectId); + if (character == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + character->setJumpSpeed(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setGravity + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setGravity + (JNIEnv *env, jobject object, jlong objectId, jfloat value) { + btKinematicCharacterController* character = reinterpret_cast(objectId); + if (character == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + character->setGravity(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: getGravity + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getGravity + (JNIEnv *env, jobject object, jlong objectId) { + btKinematicCharacterController* character = reinterpret_cast(objectId); + if (character == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return character->getGravity(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setMaxSlope + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setMaxSlope + (JNIEnv *env, jobject object, jlong objectId, jfloat value) { + btKinematicCharacterController* character = reinterpret_cast(objectId); + if (character == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + character->setMaxSlope(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: getMaxSlope + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getMaxSlope + (JNIEnv *env, jobject object, jlong objectId) { + btKinematicCharacterController* character = reinterpret_cast(objectId); + if (character == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return character->getMaxSlope(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: onGround + * Signature: (J)Z + */ + JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_onGround + (JNIEnv *env, jobject object, jlong objectId) { + btKinematicCharacterController* character = reinterpret_cast(objectId); + if (character == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return false; + } + return character->onGround(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: jump + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_jump + (JNIEnv *env, jobject object, jlong objectId) { + btKinematicCharacterController* character = reinterpret_cast(objectId); + if (character == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + character->jump(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: getPhysicsLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getPhysicsLocation + (JNIEnv *env, jobject object, jlong objectId, jobject value) { + btGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &ghost->getWorldTransform().getOrigin(), value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setCcdSweptSphereRadius + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCcdSweptSphereRadius + (JNIEnv *env, jobject object, jlong objectId, jfloat value) { + btGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + ghost->setCcdSweptSphereRadius(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setCcdMotionThreshold + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCcdMotionThreshold + (JNIEnv *env, jobject object, jlong objectId, jfloat value) { + btGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + ghost->setCcdMotionThreshold(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: getCcdSweptSphereRadius + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdSweptSphereRadius + (JNIEnv *env, jobject object, jlong objectId) { + btGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return ghost->getCcdSweptSphereRadius(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: getCcdMotionThreshold + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdMotionThreshold + (JNIEnv *env, jobject object, jlong objectId) { + btGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return ghost->getCcdMotionThreshold(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: getCcdSquareMotionThreshold + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdSquareMotionThreshold + (JNIEnv *env, jobject object, jlong objectId) { + btGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return ghost->getCcdSquareMotionThreshold(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: finalizeNativeCharacter + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_finalizeNativeCharacter + (JNIEnv *env, jobject object, jlong objectId) { + btKinematicCharacterController* character = reinterpret_cast(objectId); + if (character == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + delete(character); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsCharacter.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsCharacter.h new file mode 100644 index 000000000..210198c01 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsCharacter.h @@ -0,0 +1,215 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_objects_PhysicsCharacter */ + +#ifndef _Included_com_jme3_bullet_objects_PhysicsCharacter +#define _Included_com_jme3_bullet_objects_PhysicsCharacter +#ifdef __cplusplus +extern "C" { +#endif +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_NONE +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_NONE 0L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_01 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_01 1L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_02 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_02 2L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_03 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_03 4L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_04 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_04 8L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_05 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_05 16L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_06 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_06 32L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_07 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_07 64L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_08 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_08 128L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_09 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_09 256L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_10 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_10 512L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_11 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_11 1024L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_12 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_12 2048L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_13 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_13 4096L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_14 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_14 8192L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_15 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_15 16384L +#undef com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_16 +#define com_jme3_bullet_objects_PhysicsCharacter_COLLISION_GROUP_16 32768L +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: createGhostObject + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_createGhostObject + (JNIEnv *, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setCharacterFlags + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCharacterFlags + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: createCharacterObject + * Signature: (JJF)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_createCharacterObject + (JNIEnv *, jobject, jlong, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: warp + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_warp + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setWalkDirection + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setWalkDirection + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setUpAxis + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setUpAxis + (JNIEnv *, jobject, jlong, jint); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setFallSpeed + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setFallSpeed + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setJumpSpeed + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setJumpSpeed + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setGravity + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setGravity + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: getGravity + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getGravity + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setMaxSlope + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setMaxSlope + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: getMaxSlope + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getMaxSlope + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: onGround + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_onGround + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: jump + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_jump + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: getPhysicsLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getPhysicsLocation + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setCcdSweptSphereRadius + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCcdSweptSphereRadius + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: setCcdMotionThreshold + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCcdMotionThreshold + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: getCcdSweptSphereRadius + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdSweptSphereRadius + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: getCcdMotionThreshold + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdMotionThreshold + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: getCcdSquareMotionThreshold + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdSquareMotionThreshold + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsCharacter + * Method: finalizeNativeCharacter + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_finalizeNativeCharacter + (JNIEnv *, jobject, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsGhostObject.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsGhostObject.cpp new file mode 100644 index 000000000..fa38c1b63 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsGhostObject.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ + +#include + +#include "com_jme3_bullet_objects_PhysicsGhostObject.h" +#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h" +#include "jmeBulletUtil.h" +#include "jmePhysicsSpace.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: createGhostObject + * Signature: ()J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_createGhostObject + (JNIEnv * env, jobject object) { + jmeClasses::initJavaClasses(env); + btPairCachingGhostObject* ghost = new btPairCachingGhostObject(); + return reinterpret_cast(ghost); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: setGhostFlags + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setGhostFlags + (JNIEnv *env, jobject object, jlong objectId) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + ghost->setCollisionFlags(ghost->getCollisionFlags() | btCollisionObject::CF_NO_CONTACT_RESPONSE); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: setPhysicsLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsLocation + (JNIEnv *env, jobject object, jlong objectId, jobject value) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, value, &ghost->getWorldTransform().getOrigin()); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: setPhysicsRotation + * Signature: (JLcom/jme3/math/Matrix3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsRotation__JLcom_jme3_math_Matrix3f_2 + (JNIEnv *env, jobject object, jlong objectId, jobject value) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, value, &ghost->getWorldTransform().getBasis()); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: setPhysicsRotation + * Signature: (JLcom/jme3/math/Quaternion;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsRotation__JLcom_jme3_math_Quaternion_2 + (JNIEnv *env, jobject object, jlong objectId, jobject value) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convertQuat(env, value, &ghost->getWorldTransform().getBasis()); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getPhysicsLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsLocation + (JNIEnv *env, jobject object, jlong objectId, jobject value) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &ghost->getWorldTransform().getOrigin(), value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getPhysicsRotation + * Signature: (JLcom/jme3/math/Quaternion;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsRotation + (JNIEnv *env, jobject object, jlong objectId, jobject value) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convertQuat(env, &ghost->getWorldTransform().getBasis(), value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getPhysicsRotationMatrix + * Signature: (JLcom/jme3/math/Matrix3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsRotationMatrix + (JNIEnv *env, jobject object, jlong objectId, jobject value) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &ghost->getWorldTransform().getBasis(), value); + } + + class jmeGhostOverlapCallback : public btOverlapCallback { + JNIEnv* m_env; + jobject m_object; + btCollisionObject *m_ghost; + public: + jmeGhostOverlapCallback(JNIEnv *env, jobject object, btCollisionObject *ghost) + :m_env(env), + m_object(object), + m_ghost(ghost) + { + } + virtual ~jmeGhostOverlapCallback() {} + virtual bool processOverlap(btBroadphasePair& pair) + { + btCollisionObject *other; + if(pair.m_pProxy1->m_clientObject == m_ghost){ + other = (btCollisionObject *)pair.m_pProxy0->m_clientObject; + }else{ + other = (btCollisionObject *)pair.m_pProxy1->m_clientObject; + } + jmeUserPointer *up1 = (jmeUserPointer*)other -> getUserPointer(); + jobject javaCollisionObject1 = m_env->NewLocalRef(up1->javaCollisionObject); + m_env->CallVoidMethod(m_object, jmeClasses::PhysicsGhostObject_addOverlappingObject, javaCollisionObject1); + m_env->DeleteLocalRef(javaCollisionObject1); + if (m_env->ExceptionCheck()) { + m_env->Throw(m_env->ExceptionOccurred()); + return false; + } + + return false; + } + }; + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getOverlappingObjects + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getOverlappingObjects + (JNIEnv *env, jobject object, jlong objectId) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btHashedOverlappingPairCache * pc = ghost->getOverlappingPairCache(); + jmeGhostOverlapCallback cb(env, object, ghost); + pc -> processAllOverlappingPairs(&cb, NULL); + } + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getOverlappingCount + * Signature: (J)I + */ + JNIEXPORT jint JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getOverlappingCount + (JNIEnv *env, jobject object, jlong objectId) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return ghost->getNumOverlappingObjects(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: setCcdSweptSphereRadius + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setCcdSweptSphereRadius + (JNIEnv *env, jobject object, jlong objectId, jfloat value) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + ghost->setCcdSweptSphereRadius(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: setCcdMotionThreshold + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setCcdMotionThreshold + (JNIEnv *env, jobject object, jlong objectId, jfloat value) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + ghost->setCcdMotionThreshold(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getCcdSweptSphereRadius + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdSweptSphereRadius + (JNIEnv *env, jobject object, jlong objectId) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return ghost->getCcdSweptSphereRadius(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getCcdMotionThreshold + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdMotionThreshold + (JNIEnv *env, jobject object, jlong objectId) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return ghost->getCcdMotionThreshold(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getCcdSquareMotionThreshold + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdSquareMotionThreshold + (JNIEnv *env, jobject object, jlong objectId) { + btPairCachingGhostObject* ghost = reinterpret_cast(objectId); + if (ghost == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return ghost->getCcdSquareMotionThreshold(); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsGhostObject.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsGhostObject.h new file mode 100644 index 000000000..cf98e5970 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsGhostObject.h @@ -0,0 +1,167 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_objects_PhysicsGhostObject */ + +#ifndef _Included_com_jme3_bullet_objects_PhysicsGhostObject +#define _Included_com_jme3_bullet_objects_PhysicsGhostObject +#ifdef __cplusplus +extern "C" { +#endif +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_NONE +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_NONE 0L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_01 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_01 1L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_02 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_02 2L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_03 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_03 4L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_04 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_04 8L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_05 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_05 16L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_06 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_06 32L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_07 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_07 64L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_08 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_08 128L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_09 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_09 256L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_10 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_10 512L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_11 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_11 1024L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_12 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_12 2048L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_13 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_13 4096L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_14 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_14 8192L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_15 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_15 16384L +#undef com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_16 +#define com_jme3_bullet_objects_PhysicsGhostObject_COLLISION_GROUP_16 32768L +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: createGhostObject + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_createGhostObject + (JNIEnv *, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: setGhostFlags + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setGhostFlags + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: setPhysicsLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsLocation + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: setPhysicsRotation + * Signature: (JLcom/jme3/math/Matrix3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsRotation__JLcom_jme3_math_Matrix3f_2 + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: setPhysicsRotation + * Signature: (JLcom/jme3/math/Quaternion;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsRotation__JLcom_jme3_math_Quaternion_2 + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getPhysicsLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsLocation + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getPhysicsRotation + * Signature: (JLcom/jme3/math/Quaternion;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsRotation + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getPhysicsRotationMatrix + * Signature: (JLcom/jme3/math/Matrix3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsRotationMatrix + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getOverlappingObjects + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getOverlappingObjects + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getOverlappingCount + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getOverlappingCount + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: setCcdSweptSphereRadius + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setCcdSweptSphereRadius + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: setCcdMotionThreshold + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setCcdMotionThreshold + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getCcdSweptSphereRadius + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdSweptSphereRadius + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getCcdMotionThreshold + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdMotionThreshold + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsGhostObject + * Method: getCcdSquareMotionThreshold + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdSquareMotionThreshold + (JNIEnv *, jobject, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsRigidBody.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsRigidBody.cpp new file mode 100644 index 000000000..0d9621bb8 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsRigidBody.cpp @@ -0,0 +1,849 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_objects_PhysicsRigidBody.h" +#include "jmeBulletUtil.h" +#include "jmeMotionState.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: createRigidBody + * Signature: (FJJ)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_createRigidBody + (JNIEnv *env, jobject object, jfloat mass, jlong motionstatId, jlong shapeId) { + jmeClasses::initJavaClasses(env); + btMotionState* motionState = reinterpret_cast(motionstatId); + btCollisionShape* shape = reinterpret_cast(shapeId); + btVector3 localInertia = btVector3(); + shape->calculateLocalInertia(mass, localInertia); + btRigidBody* body = new btRigidBody(mass, motionState, shape, localInertia); + body->setUserPointer(NULL); + return reinterpret_cast(body); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: isInWorld + * Signature: (J)Z + */ + JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_isInWorld + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return false; + } + return body->isInWorld(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setPhysicsLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsLocation + (JNIEnv *env, jobject object, jlong bodyId, jobject value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + // if (body->isStaticOrKinematicObject() || !body->isInWorld()) + ((jmeMotionState*) body->getMotionState())->setKinematicLocation(env, value); + body->setCenterOfMassTransform(((jmeMotionState*) body->getMotionState())->worldTransform); + // else{ + // btMatrix3x3* mtx = &btMatrix3x3(); + // btTransform* trans = &btTransform(*mtx); + // trans->setBasis(body->getCenterOfMassTransform().getBasis()); + // jmeBulletUtil::convert(env, value, &trans->getOrigin()); + // body->setCenterOfMassTransform(*trans); + // } + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setPhysicsRotation + * Signature: (JLcom/jme3/math/Matrix3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsRotation__JLcom_jme3_math_Matrix3f_2 + (JNIEnv *env, jobject object, jlong bodyId, jobject value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + // if (body->isStaticOrKinematicObject() || !body->isInWorld()) + ((jmeMotionState*) body->getMotionState())->setKinematicRotation(env, value); + body->setCenterOfMassTransform(((jmeMotionState*) body->getMotionState())->worldTransform); + // else{ + // btMatrix3x3* mtx = &btMatrix3x3(); + // btTransform* trans = &btTransform(*mtx); + // trans->setOrigin(body->getCenterOfMassTransform().getOrigin()); + // jmeBulletUtil::convert(env, value, &trans->getBasis()); + // body->setCenterOfMassTransform(*trans); + // } + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setPhysicsRotation + * Signature: (JLcom/jme3/math/Quaternion;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsRotation__JLcom_jme3_math_Quaternion_2 + (JNIEnv *env, jobject object, jlong bodyId, jobject value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + // if (body->isStaticOrKinematicObject() || !body->isInWorld()) + ((jmeMotionState*) body->getMotionState())->setKinematicRotationQuat(env, value); + body->setCenterOfMassTransform(((jmeMotionState*) body->getMotionState())->worldTransform); + // else{ + // btMatrix3x3* mtx = &btMatrix3x3(); + // btTransform* trans = &btTransform(*mtx); + // trans->setOrigin(body->getCenterOfMassTransform().getOrigin()); + // jmeBulletUtil::convertQuat(env, value, &trans->getBasis()); + // body->setCenterOfMassTransform(*trans); + // } + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getPhysicsLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsLocation + (JNIEnv *env, jobject object, jlong bodyId, jobject value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &body->getWorldTransform().getOrigin(), value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getPhysicsRotation + * Signature: (JLcom/jme3/math/Quaternion;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsRotation + (JNIEnv *env, jobject object, jlong bodyId, jobject value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convertQuat(env, &body->getWorldTransform().getBasis(), value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getPhysicsRotationMatrix + * Signature: (JLcom/jme3/math/Matrix3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsRotationMatrix + (JNIEnv *env, jobject object, jlong bodyId, jobject value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &body->getWorldTransform().getBasis(), value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setKinematic + * Signature: (JZ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setKinematic + (JNIEnv *env, jobject object, jlong bodyId, jboolean value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + if (value) { + body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT); + body->setActivationState(DISABLE_DEACTIVATION); + } else { + body->setCollisionFlags(body->getCollisionFlags() & ~btCollisionObject::CF_KINEMATIC_OBJECT); + body->setActivationState(ACTIVE_TAG); + } + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setCcdSweptSphereRadius + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCcdSweptSphereRadius + (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + body->setCcdSweptSphereRadius(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setCcdMotionThreshold + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCcdMotionThreshold + (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + body->setCcdMotionThreshold(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getCcdSweptSphereRadius + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdSweptSphereRadius + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return body->getCcdSweptSphereRadius(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getCcdMotionThreshold + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdMotionThreshold + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return body->getCcdMotionThreshold(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getCcdSquareMotionThreshold + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdSquareMotionThreshold + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return body->getCcdSquareMotionThreshold(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setStatic + * Signature: (JZ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setStatic + (JNIEnv *env, jobject object, jlong bodyId, jboolean value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + if (value) { + body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_STATIC_OBJECT); + } else { + body->setCollisionFlags(body->getCollisionFlags() & ~btCollisionObject::CF_STATIC_OBJECT); + } + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: updateMassProps + * Signature: (JJF)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_updateMassProps + (JNIEnv *env, jobject object, jlong bodyId, jlong shapeId, jfloat mass) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + btCollisionShape* shape = reinterpret_cast(shapeId); + btVector3 localInertia = btVector3(); + shape->calculateLocalInertia(mass, localInertia); + body->setMassProps(mass, localInertia); + return reinterpret_cast(body); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getGravity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getGravity + (JNIEnv *env, jobject object, jlong bodyId, jobject value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &body->getGravity(), value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setGravity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setGravity + (JNIEnv *env, jobject object, jlong bodyId, jobject value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec = btVector3(); + jmeBulletUtil::convert(env, value, &vec); + body->setGravity(vec); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getFriction + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getFriction + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return body->getFriction(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setFriction + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setFriction + (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + body->setFriction(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setDamping + * Signature: (JFF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setDamping + (JNIEnv *env, jobject object, jlong bodyId, jfloat value1, jfloat value2) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + body->setDamping(value1, value2); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setAngularDamping + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularDamping + (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + body->setDamping(body->getAngularDamping(), value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getLinearDamping + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearDamping + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return body->getLinearDamping(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getAngularDamping + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularDamping + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return body->getAngularDamping(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getRestitution + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getRestitution + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return body->getRestitution(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setRestitution + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setRestitution + (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + body->setRestitution(value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getAngularVelocity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularVelocity + (JNIEnv *env, jobject object, jlong bodyId, jobject value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &body->getAngularVelocity(), value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setAngularVelocity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularVelocity + (JNIEnv *env, jobject object, jlong bodyId, jobject value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec = btVector3(); + jmeBulletUtil::convert(env, value, &vec); + body->setAngularVelocity(vec); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getLinearVelocity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearVelocity + (JNIEnv *env, jobject object, jlong bodyId, jobject value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &body->getLinearVelocity(), value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setLinearVelocity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setLinearVelocity + (JNIEnv *env, jobject object, jlong bodyId, jobject value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec = btVector3(); + jmeBulletUtil::convert(env, value, &vec); + body->setLinearVelocity(vec); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: applyForce + * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyForce + (JNIEnv *env, jobject object, jlong bodyId, jobject force, jobject location) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec1 = btVector3(); + btVector3 vec2 = btVector3(); + jmeBulletUtil::convert(env, force, &vec1); + jmeBulletUtil::convert(env, location, &vec2); + body->applyForce(vec1, vec2); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: applyCentralForce + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyCentralForce + (JNIEnv *env, jobject object, jlong bodyId, jobject force) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec1 = btVector3(); + jmeBulletUtil::convert(env, force, &vec1); + body->applyCentralForce(vec1); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: applyTorque + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyTorque + (JNIEnv *env, jobject object, jlong bodyId, jobject force) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec1 = btVector3(); + jmeBulletUtil::convert(env, force, &vec1); + body->applyTorque(vec1); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: applyImpulse + * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyImpulse + (JNIEnv *env, jobject object, jlong bodyId, jobject force, jobject location) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec1 = btVector3(); + btVector3 vec2 = btVector3(); + jmeBulletUtil::convert(env, force, &vec1); + jmeBulletUtil::convert(env, location, &vec2); + body->applyImpulse(vec1, vec2); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: applyTorqueImpulse + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyTorqueImpulse + (JNIEnv *env, jobject object, jlong bodyId, jobject force) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec1 = btVector3(); + jmeBulletUtil::convert(env, force, &vec1); + body->applyTorqueImpulse(vec1); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: clearForces + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_clearForces + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + body->clearForces(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setCollisionShape + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCollisionShape + (JNIEnv *env, jobject object, jlong bodyId, jlong shapeId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btCollisionShape* shape = reinterpret_cast(shapeId); + body->setCollisionShape(shape); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: activate + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_activate + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + body->activate(false); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: isActive + * Signature: (J)Z + */ + JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_isActive + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return false; + } + return body->isActive(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setSleepingThresholds + * Signature: (JFF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setSleepingThresholds + (JNIEnv *env, jobject object, jlong bodyId, jfloat linear, jfloat angular) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + body->setSleepingThresholds(linear, angular); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setLinearSleepingThreshold + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setLinearSleepingThreshold + (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + body->setSleepingThresholds(value, body->getLinearSleepingThreshold()); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setAngularSleepingThreshold + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularSleepingThreshold + (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + body->setSleepingThresholds(body->getAngularSleepingThreshold(), value); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getLinearSleepingThreshold + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearSleepingThreshold + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return body->getLinearSleepingThreshold(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getAngularSleepingThreshold + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularSleepingThreshold + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return body->getAngularSleepingThreshold(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getAngularFactor + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularFactor + (JNIEnv *env, jobject object, jlong bodyId) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return body->getAngularFactor().getX(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setAngularFactor + * Signature: (JF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularFactor + (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { + btRigidBody* body = reinterpret_cast(bodyId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 vec1 = btVector3(); + vec1.setX(value); + vec1.setY(value); + vec1.setZ(value); + body->setAngularFactor(vec1); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsRigidBody.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsRigidBody.h new file mode 100644 index 000000000..aa09a620a --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsRigidBody.h @@ -0,0 +1,415 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_objects_PhysicsRigidBody */ + +#ifndef _Included_com_jme3_bullet_objects_PhysicsRigidBody +#define _Included_com_jme3_bullet_objects_PhysicsRigidBody +#ifdef __cplusplus +extern "C" { +#endif +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_NONE +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_NONE 0L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_01 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_01 1L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_02 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_02 2L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_03 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_03 4L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_04 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_04 8L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_05 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_05 16L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_06 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_06 32L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_07 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_07 64L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_08 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_08 128L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_09 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_09 256L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_10 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_10 512L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_11 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_11 1024L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_12 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_12 2048L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_13 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_13 4096L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_14 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_14 8192L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_15 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_15 16384L +#undef com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_16 +#define com_jme3_bullet_objects_PhysicsRigidBody_COLLISION_GROUP_16 32768L +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: createRigidBody + * Signature: (FJJ)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_createRigidBody + (JNIEnv *, jobject, jfloat, jlong, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: isInWorld + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_isInWorld + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setPhysicsLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsLocation + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setPhysicsRotation + * Signature: (JLcom/jme3/math/Matrix3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsRotation__JLcom_jme3_math_Matrix3f_2 + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setPhysicsRotation + * Signature: (JLcom/jme3/math/Quaternion;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsRotation__JLcom_jme3_math_Quaternion_2 + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getPhysicsLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsLocation + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getPhysicsRotation + * Signature: (JLcom/jme3/math/Quaternion;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsRotation + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getPhysicsRotationMatrix + * Signature: (JLcom/jme3/math/Matrix3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsRotationMatrix + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setKinematic + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setKinematic + (JNIEnv *, jobject, jlong, jboolean); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setCcdSweptSphereRadius + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCcdSweptSphereRadius + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setCcdMotionThreshold + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCcdMotionThreshold + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getCcdSweptSphereRadius + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdSweptSphereRadius + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getCcdMotionThreshold + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdMotionThreshold + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getCcdSquareMotionThreshold + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdSquareMotionThreshold + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setStatic + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setStatic + (JNIEnv *, jobject, jlong, jboolean); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: updateMassProps + * Signature: (JJF)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_updateMassProps + (JNIEnv *, jobject, jlong, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getGravity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getGravity + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setGravity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setGravity + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getFriction + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getFriction + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setFriction + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setFriction + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setDamping + * Signature: (JFF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setDamping + (JNIEnv *, jobject, jlong, jfloat, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setAngularDamping + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularDamping + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getLinearDamping + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearDamping + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getAngularDamping + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularDamping + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getRestitution + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getRestitution + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setRestitution + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setRestitution + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getAngularVelocity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularVelocity + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setAngularVelocity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularVelocity + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getLinearVelocity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearVelocity + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setLinearVelocity + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setLinearVelocity + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: applyForce + * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyForce + (JNIEnv *, jobject, jlong, jobject, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: applyCentralForce + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyCentralForce + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: applyTorque + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyTorque + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: applyImpulse + * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyImpulse + (JNIEnv *, jobject, jlong, jobject, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: applyTorqueImpulse + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyTorqueImpulse + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: clearForces + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_clearForces + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setCollisionShape + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCollisionShape + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: activate + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_activate + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: isActive + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_isActive + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setSleepingThresholds + * Signature: (JFF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setSleepingThresholds + (JNIEnv *, jobject, jlong, jfloat, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setLinearSleepingThreshold + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setLinearSleepingThreshold + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setAngularSleepingThreshold + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularSleepingThreshold + (JNIEnv *, jobject, jlong, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getLinearSleepingThreshold + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearSleepingThreshold + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getAngularSleepingThreshold + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularSleepingThreshold + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: getAngularFactor + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularFactor + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsRigidBody + * Method: setAngularFactor + * Signature: (JF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularFactor + (JNIEnv *, jobject, jlong, jfloat); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsVehicle.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsVehicle.cpp new file mode 100644 index 000000000..58ceaf9da --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsVehicle.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ + +#include "com_jme3_bullet_objects_PhysicsVehicle.h" +#include "jmeBulletUtil.h" +#include "jmePhysicsSpace.h" +#include "BulletDynamics/Vehicle/btRaycastVehicle.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: updateWheelTransform + * Signature: (JIZ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_updateWheelTransform + (JNIEnv *env, jobject object, jlong vehicleId, jint wheel, jboolean interpolated) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + vehicle->updateWheelTransform(wheel, interpolated); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: createVehicleRaycaster + * Signature: (JJ)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_createVehicleRaycaster + (JNIEnv *env, jobject object, jlong bodyId, jlong spaceId) { + //btRigidBody* body = reinterpret_cast bodyId; + jmeClasses::initJavaClasses(env); + jmePhysicsSpace *space = reinterpret_cast(spaceId); + if (space == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + btDefaultVehicleRaycaster* caster = new btDefaultVehicleRaycaster(space->getDynamicsWorld()); + return reinterpret_cast(caster); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: createRaycastVehicle + * Signature: (JJ)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_createRaycastVehicle + (JNIEnv *env, jobject object, jlong objectId, jlong casterId) { + jmeClasses::initJavaClasses(env); + btRigidBody* body = reinterpret_cast(objectId); + if (body == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + body->setActivationState(DISABLE_DEACTIVATION); + btVehicleRaycaster* caster = reinterpret_cast(casterId); + if (caster == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + btRaycastVehicle::btVehicleTuning tuning; + btRaycastVehicle* vehicle = new btRaycastVehicle(tuning, body, caster); + return reinterpret_cast(vehicle); + + } + + /* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: setCoordinateSystem + * Signature: (JIII)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_setCoordinateSystem + (JNIEnv *env, jobject object, jlong vehicleId, jint right, jint up, jint forward) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + vehicle->setCoordinateSystem(right, up, forward); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: addWheel + * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;FFLcom/jme3/bullet/objects/infos/VehicleTuning;Z)J + */ + JNIEXPORT jint JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_addWheel + (JNIEnv *env, jobject object, jlong vehicleId, jobject location, jobject direction, jobject axle, jfloat restLength, jfloat radius, jobject tuning, jboolean frontWheel) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + btVector3 vec1 = btVector3(); + btVector3 vec2 = btVector3(); + btVector3 vec3 = btVector3(); + jmeBulletUtil::convert(env, location, &vec1); + jmeBulletUtil::convert(env, direction, &vec2); + jmeBulletUtil::convert(env, axle, &vec3); + btRaycastVehicle::btVehicleTuning tune; + btWheelInfo* info = &vehicle->addWheel(vec1, vec2, vec3, restLength, radius, tune, frontWheel); + int idx = vehicle->getNumWheels(); + return idx-1; + } + + /* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: resetSuspension + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_resetSuspension + (JNIEnv *env, jobject object, jlong vehicleId) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + vehicle->resetSuspension(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: applyEngineForce + * Signature: (JIF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_applyEngineForce + (JNIEnv *env, jobject object, jlong vehicleId, jint wheel, jfloat force) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + vehicle->applyEngineForce(force, wheel); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: steer + * Signature: (JIF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_steer + (JNIEnv *env, jobject object, jlong vehicleId, jint wheel, jfloat value) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + vehicle->setSteeringValue(value, wheel); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: brake + * Signature: (JIF)F + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_brake + (JNIEnv *env, jobject object, jlong vehicleId, jint wheel, jfloat value) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + vehicle->setBrake(value, wheel); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: getCurrentVehicleSpeedKmHour + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_getCurrentVehicleSpeedKmHour + (JNIEnv *env, jobject object, jlong vehicleId) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return vehicle->getCurrentSpeedKmHour(); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: getForwardVector + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_getForwardVector + (JNIEnv *env, jobject object, jlong vehicleId, jobject out) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + btVector3 forwardVector = vehicle->getForwardVector(); + jmeBulletUtil::convert(env, &forwardVector, out); + } + + /* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: finalizeNative + * Signature: (JJ)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_finalizeNative + (JNIEnv *env, jobject object, jlong casterId, jlong vehicleId) { + btVehicleRaycaster* rayCaster = reinterpret_cast(casterId); + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + delete(vehicle); + if (rayCaster == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + delete(rayCaster); + } + +#ifdef __cplusplus +} +#endif + diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsVehicle.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsVehicle.h new file mode 100644 index 000000000..821e384cc --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsVehicle.h @@ -0,0 +1,143 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_objects_PhysicsVehicle */ + +#ifndef _Included_com_jme3_bullet_objects_PhysicsVehicle +#define _Included_com_jme3_bullet_objects_PhysicsVehicle +#ifdef __cplusplus +extern "C" { +#endif +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_NONE +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_NONE 0L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_01 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_01 1L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_02 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_02 2L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_03 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_03 4L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_04 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_04 8L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_05 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_05 16L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_06 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_06 32L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_07 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_07 64L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_08 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_08 128L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_09 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_09 256L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_10 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_10 512L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_11 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_11 1024L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_12 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_12 2048L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_13 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_13 4096L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_14 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_14 8192L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_15 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_15 16384L +#undef com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_16 +#define com_jme3_bullet_objects_PhysicsVehicle_COLLISION_GROUP_16 32768L +/* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: updateWheelTransform + * Signature: (JIZ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_updateWheelTransform + (JNIEnv *, jobject, jlong, jint, jboolean); + +/* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: createVehicleRaycaster + * Signature: (JJ)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_createVehicleRaycaster + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: createRaycastVehicle + * Signature: (JJ)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_createRaycastVehicle + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: setCoordinateSystem + * Signature: (JIII)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_setCoordinateSystem + (JNIEnv *, jobject, jlong, jint, jint, jint); + +/* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: addWheel + * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;FFLcom/jme3/bullet/objects/infos/VehicleTuning;Z)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_addWheel + (JNIEnv *, jobject, jlong, jobject, jobject, jobject, jfloat, jfloat, jobject, jboolean); + +/* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: resetSuspension + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_resetSuspension + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: applyEngineForce + * Signature: (JIF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_applyEngineForce + (JNIEnv *, jobject, jlong, jint, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: steer + * Signature: (JIF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_steer + (JNIEnv *, jobject, jlong, jint, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: brake + * Signature: (JIF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_brake + (JNIEnv *, jobject, jlong, jint, jfloat); + +/* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: getCurrentVehicleSpeedKmHour + * Signature: (J)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_getCurrentVehicleSpeedKmHour + (JNIEnv *, jobject, jlong); + +/* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: getForwardVector + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_getForwardVector + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_PhysicsVehicle + * Method: finalizeNative + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_finalizeNative + (JNIEnv *, jobject, jlong, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_VehicleWheel.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_VehicleWheel.cpp new file mode 100644 index 000000000..b3d38a673 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_VehicleWheel.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ + +#include "com_jme3_bullet_objects_VehicleWheel.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: getWheelLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getWheelLocation + (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jobject out) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &vehicle->getWheelInfo(wheelIndex).m_worldTransform.getOrigin(), out); + } + + /* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: getWheelRotation + * Signature: (JLcom/jme3/math/Matrix3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getWheelRotation + (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jobject out) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &vehicle->getWheelInfo(wheelIndex).m_worldTransform.getBasis(), out); + } + + /* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: applyInfo + * Signature: (JFFFFFFFFZF)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_applyInfo + (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jfloat suspensionStiffness, jfloat wheelsDampingRelaxation, jfloat wheelsDampingCompression, jfloat frictionSlip, jfloat rollInfluence, jfloat maxSuspensionTravelCm, jfloat maxSuspensionForce, jfloat radius, jboolean frontWheel, jfloat restLength) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + vehicle->getWheelInfo(wheelIndex).m_suspensionStiffness = suspensionStiffness; + vehicle->getWheelInfo(wheelIndex).m_wheelsDampingRelaxation = wheelsDampingRelaxation; + vehicle->getWheelInfo(wheelIndex).m_wheelsDampingCompression = wheelsDampingCompression; + vehicle->getWheelInfo(wheelIndex).m_frictionSlip = frictionSlip; + vehicle->getWheelInfo(wheelIndex).m_rollInfluence = rollInfluence; + vehicle->getWheelInfo(wheelIndex).m_maxSuspensionTravelCm = maxSuspensionTravelCm; + vehicle->getWheelInfo(wheelIndex).m_maxSuspensionForce = maxSuspensionForce; + vehicle->getWheelInfo(wheelIndex).m_wheelsRadius = radius; + vehicle->getWheelInfo(wheelIndex).m_bIsFrontWheel = frontWheel; + vehicle->getWheelInfo(wheelIndex).m_suspensionRestLength1 = restLength; + + } + + /* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: getCollisionLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getCollisionLocation + (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jobject out) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &vehicle->getWheelInfo(wheelIndex).m_raycastInfo.m_contactPointWS, out); + } + + /* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: getCollisionNormal + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getCollisionNormal + (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jobject out) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &vehicle->getWheelInfo(wheelIndex).m_raycastInfo.m_contactNormalWS, out); + } + + /* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: getSkidInfo + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getSkidInfo + (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return vehicle->getWheelInfo(wheelIndex).m_skidInfo; + } + + /* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: getDeltaRotation + * Signature: (J)F + */ + JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getDeltaRotation + (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex) { + btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); + if (vehicle == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return 0; + } + return vehicle->getWheelInfo(wheelIndex).m_deltaRotation; + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_VehicleWheel.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_VehicleWheel.h new file mode 100644 index 000000000..f4ab208ae --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_VehicleWheel.h @@ -0,0 +1,69 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_objects_VehicleWheel */ + +#ifndef _Included_com_jme3_bullet_objects_VehicleWheel +#define _Included_com_jme3_bullet_objects_VehicleWheel +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: getWheelLocation + * Signature: (JILcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getWheelLocation + (JNIEnv *, jobject, jlong, jint, jobject); + +/* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: getWheelRotation + * Signature: (JILcom/jme3/math/Matrix3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getWheelRotation + (JNIEnv *, jobject, jlong, jint, jobject); + +/* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: applyInfo + * Signature: (JIFFFFFFFFZF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_applyInfo + (JNIEnv *, jobject, jlong, jint, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jboolean, jfloat); + +/* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: getCollisionLocation + * Signature: (JILcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getCollisionLocation + (JNIEnv *, jobject, jlong, jint, jobject); + +/* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: getCollisionNormal + * Signature: (JILcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getCollisionNormal + (JNIEnv *, jobject, jlong, jint, jobject); + +/* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: getSkidInfo + * Signature: (JI)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getSkidInfo + (JNIEnv *, jobject, jlong, jint); + +/* + * Class: com_jme3_bullet_objects_VehicleWheel + * Method: getDeltaRotation + * Signature: (JI)F + */ +JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getDeltaRotation + (JNIEnv *, jobject, jlong, jint); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_infos_RigidBodyMotionState.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_infos_RigidBodyMotionState.cpp new file mode 100644 index 000000000..5f614f052 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_infos_RigidBodyMotionState.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_objects_infos_RigidBodyMotionState.h" +#include "jmeBulletUtil.h" +#include "jmeMotionState.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState + * Method: createMotionState + * Signature: ()J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_createMotionState + (JNIEnv *env, jobject object) { + jmeClasses::initJavaClasses(env); + jmeMotionState* motionState = new jmeMotionState(); + return reinterpret_cast(motionState); + } + + /* + * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState + * Method: applyTransform + * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Quaternion;)Z + */ + JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_applyTransform + (JNIEnv *env, jobject object, jlong stateId, jobject location, jobject rotation) { + jmeMotionState* motionState = reinterpret_cast(stateId); + if (motionState == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return false; + } + return motionState->applyTransform(env, location, rotation); + } + + /* + * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState + * Method: getWorldLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldLocation + (JNIEnv *env, jobject object, jlong stateId, jobject value) { + jmeMotionState* motionState = reinterpret_cast(stateId); + if (motionState == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &motionState->worldTransform.getOrigin(), value); + } + + /* + * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState + * Method: getWorldRotation + * Signature: (JLcom/jme3/math/Matrix3f;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldRotation + (JNIEnv *env, jobject object, jlong stateId, jobject value) { + jmeMotionState* motionState = reinterpret_cast(stateId); + if (motionState == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convert(env, &motionState->worldTransform.getBasis(), value); + } + + /* + * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState + * Method: getWorldRotationQuat + * Signature: (JLcom/jme3/math/Quaternion;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldRotationQuat + (JNIEnv *env, jobject object, jlong stateId, jobject value) { + jmeMotionState* motionState = reinterpret_cast(stateId); + if (motionState == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + jmeBulletUtil::convertQuat(env, &motionState->worldTransform.getBasis(), value); + } + + /* + * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState + * Method: finalizeNative + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_finalizeNative + (JNIEnv *env, jobject object, jlong stateId) { + jmeMotionState* motionState = reinterpret_cast(stateId); + if (motionState == NULL) { + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, "The native object does not exist."); + return; + } + delete(motionState); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_infos_RigidBodyMotionState.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_infos_RigidBodyMotionState.h new file mode 100644 index 000000000..7939038d2 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_infos_RigidBodyMotionState.h @@ -0,0 +1,61 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_objects_infos_RigidBodyMotionState */ + +#ifndef _Included_com_jme3_bullet_objects_infos_RigidBodyMotionState +#define _Included_com_jme3_bullet_objects_infos_RigidBodyMotionState +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState + * Method: createMotionState + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_createMotionState + (JNIEnv *, jobject); + +/* + * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState + * Method: applyTransform + * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Quaternion;)Z + */ +JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_applyTransform + (JNIEnv *, jobject, jlong, jobject, jobject); + +/* + * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState + * Method: getWorldLocation + * Signature: (JLcom/jme3/math/Vector3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldLocation + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState + * Method: getWorldRotation + * Signature: (JLcom/jme3/math/Matrix3f;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldRotation + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState + * Method: getWorldRotationQuat + * Signature: (JLcom/jme3/math/Quaternion;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldRotationQuat + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState + * Method: finalizeNative + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_finalizeNative + (JNIEnv *, jobject, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_DebugShapeFactory.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_DebugShapeFactory.cpp new file mode 100644 index 000000000..0496cb07d --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_DebugShapeFactory.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen, CJ Hare + */ +#include "com_jme3_bullet_util_DebugShapeFactory.h" +#include "jmeBulletUtil.h" +#include "BulletCollision/CollisionShapes/btShapeHull.h" + +class DebugCallback : public btTriangleCallback, public btInternalTriangleIndexCallback { +public: + JNIEnv* env; + jobject callback; + + DebugCallback(JNIEnv* env, jobject object) { + this->env = env; + this->callback = object; + } + + virtual void internalProcessTriangleIndex(btVector3* triangle, int partId, int triangleIndex) { + processTriangle(triangle, partId, triangleIndex); + } + + virtual void processTriangle(btVector3* triangle, int partId, int triangleIndex) { + btVector3 vertexA, vertexB, vertexC; + vertexA = triangle[0]; + vertexB = triangle[1]; + vertexC = triangle[2]; + env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexA.getX(), vertexA.getY(), vertexA.getZ(), partId, triangleIndex); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } +// triangle = + env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexB.getX(), vertexB.getY(), vertexB.getZ(), partId, triangleIndex); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexC.getX(), vertexC.getY(), vertexC.getZ(), partId, triangleIndex); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + } +}; + +#ifdef __cplusplus +extern "C" { +#endif + + /* Inaccessible static: _00024assertionsDisabled */ + + /* + * Class: com_jme3_bullet_util_DebugShapeFactory + * Method: getVertices + * Signature: (JLcom/jme3/bullet/util/DebugMeshCallback;)V + */ + JNIEXPORT void JNICALL Java_com_jme3_bullet_util_DebugShapeFactory_getVertices + (JNIEnv *env, jclass clazz, jlong shapeId, jobject callback) { + btCollisionShape* shape = reinterpret_cast(shapeId); + if (shape->isConcave()) { + btConcaveShape* concave = reinterpret_cast(shape); + DebugCallback* clb = new DebugCallback(env, callback); + btVector3 min = btVector3(-1e30, -1e30, -1e30); + btVector3 max = btVector3(1e30, 1e30, 1e30); + concave->processAllTriangles(clb, min, max); + delete(clb); + } else if (shape->isConvex()) { + btConvexShape* convexShape = reinterpret_cast(shape); + // Check there is a hull shape to render + if (convexShape->getUserPointer() == NULL) { + // create a hull approximation + btShapeHull* hull = new btShapeHull(convexShape); + float margin = convexShape->getMargin(); + hull->buildHull(margin); + convexShape->setUserPointer(hull); + } + + btShapeHull* hull = (btShapeHull*) convexShape->getUserPointer(); + + int numberOfTriangles = hull->numTriangles(); + int numberOfFloats = 3 * 3 * numberOfTriangles; + int byteBufferSize = numberOfFloats * 4; + + // Loop variables + const unsigned int* hullIndices = hull->getIndexPointer(); + const btVector3* hullVertices = hull->getVertexPointer(); + btVector3 vertexA, vertexB, vertexC; + int index = 0; + + for (int i = 0; i < numberOfTriangles; i++) { + // Grab the data for this triangle from the hull + vertexA = hullVertices[hullIndices[index++]]; + vertexB = hullVertices[hullIndices[index++]]; + vertexC = hullVertices[hullIndices[index++]]; + + // Put the verticies into the vertex buffer + env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexA.getX(), vertexA.getY(), vertexA.getZ()); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexB.getX(), vertexB.getY(), vertexB.getZ()); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexC.getX(), vertexC.getY(), vertexC.getZ()); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + } + delete hull; + convexShape->setUserPointer(NULL); + } + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_DebugShapeFactory.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_DebugShapeFactory.h new file mode 100644 index 000000000..757696fcc --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_DebugShapeFactory.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_util_DebugShapeFactory */ + +#ifndef _Included_com_jme3_bullet_util_DebugShapeFactory +#define _Included_com_jme3_bullet_util_DebugShapeFactory +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_util_DebugShapeFactory + * Method: getVertices + * Signature: (JLcom/jme3/bullet/util/DebugMeshCallback;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_bullet_util_DebugShapeFactory_getVertices + (JNIEnv *, jclass, jlong, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_NativeMeshUtil.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_NativeMeshUtil.cpp new file mode 100644 index 000000000..a85d2d84d --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_NativeMeshUtil.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Author: Normen Hansen + */ +#include "com_jme3_bullet_util_NativeMeshUtil.h" +#include "jmeBulletUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: com_jme3_bullet_util_NativeMeshUtil + * Method: createTriangleIndexVertexArray + * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;IIII)J + */ + JNIEXPORT jlong JNICALL Java_com_jme3_bullet_util_NativeMeshUtil_createTriangleIndexVertexArray + (JNIEnv * env, jclass cls, jobject triangleIndexBase, jobject vertexIndexBase, jint numTriangles, jint numVertices, jint vertexStride, jint triangleIndexStride) { + jmeClasses::initJavaClasses(env); + int* triangles = (int*) env->GetDirectBufferAddress(triangleIndexBase); + float* vertices = (float*) env->GetDirectBufferAddress(vertexIndexBase); + btTriangleIndexVertexArray* array = new btTriangleIndexVertexArray(numTriangles, triangles, triangleIndexStride, numVertices, vertices, vertexStride); + return reinterpret_cast(array); + } + +#ifdef __cplusplus +} +#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_NativeMeshUtil.h b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_NativeMeshUtil.h new file mode 100644 index 000000000..0be9f4d5a --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_NativeMeshUtil.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_bullet_util_NativeMeshUtil */ + +#ifndef _Included_com_jme3_bullet_util_NativeMeshUtil +#define _Included_com_jme3_bullet_util_NativeMeshUtil +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_bullet_util_NativeMeshUtil + * Method: createTriangleIndexVertexArray + * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;IIII)J + */ +JNIEXPORT jlong JNICALL Java_com_jme3_bullet_util_NativeMeshUtil_createTriangleIndexVertexArray + (JNIEnv *, jclass, jobject, jobject, jint, jint, jint, jint); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-bullet-native/src/native/cpp/jmeBulletUtil.cpp b/jme3-bullet-native/src/native/cpp/jmeBulletUtil.cpp new file mode 100644 index 000000000..b584b83c0 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/jmeBulletUtil.cpp @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include "jmeBulletUtil.h" + +/** + * Author: Normen Hansen,Empire Phoenix, Lutherion + */ +void jmeBulletUtil::convert(JNIEnv* env, jobject in, btVector3* out) { + if (in == NULL || out == NULL) { + jmeClasses::throwNPE(env); + } + float x = env->GetFloatField(in, jmeClasses::Vector3f_x); //env->CallFloatMethod(in, jmeClasses::Vector3f_getX); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float y = env->GetFloatField(in, jmeClasses::Vector3f_y); //env->CallFloatMethod(in, jmeClasses::Vector3f_getY); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float z = env->GetFloatField(in, jmeClasses::Vector3f_z); //env->CallFloatMethod(in, jmeClasses::Vector3f_getZ); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + out->setX(x); + out->setY(y); + out->setZ(z); +} + +void jmeBulletUtil::convert(JNIEnv* env, const btVector3* in, jobject out) { + if (in == NULL || out == NULL) { + jmeClasses::throwNPE(env); + } + float x = in->getX(); + float y = in->getY(); + float z = in->getZ(); + env->SetFloatField(out, jmeClasses::Vector3f_x, x); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Vector3f_y, y); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Vector3f_z, z); + // env->CallObjectMethod(out, jmeClasses::Vector3f_set, x, y, z); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } +} + +void jmeBulletUtil::convert(JNIEnv* env, jobject in, btMatrix3x3* out) { + if (in == NULL || out == NULL) { + jmeClasses::throwNPE(env); + } + float m00 = env->GetFloatField(in, jmeClasses::Matrix3f_m00); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float m01 = env->GetFloatField(in, jmeClasses::Matrix3f_m01); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float m02 = env->GetFloatField(in, jmeClasses::Matrix3f_m02); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float m10 = env->GetFloatField(in, jmeClasses::Matrix3f_m10); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float m11 = env->GetFloatField(in, jmeClasses::Matrix3f_m11); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float m12 = env->GetFloatField(in, jmeClasses::Matrix3f_m12); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float m20 = env->GetFloatField(in, jmeClasses::Matrix3f_m20); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float m21 = env->GetFloatField(in, jmeClasses::Matrix3f_m21); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float m22 = env->GetFloatField(in, jmeClasses::Matrix3f_m22); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + out->setValue(m00, m01, m02, m10, m11, m12, m20, m21, m22); +} + +void jmeBulletUtil::convert(JNIEnv* env, const btMatrix3x3* in, jobject out) { + if (in == NULL || out == NULL) { + jmeClasses::throwNPE(env); + } + float m00 = in->getRow(0).m_floats[0]; + float m01 = in->getRow(0).m_floats[1]; + float m02 = in->getRow(0).m_floats[2]; + float m10 = in->getRow(1).m_floats[0]; + float m11 = in->getRow(1).m_floats[1]; + float m12 = in->getRow(1).m_floats[2]; + float m20 = in->getRow(2).m_floats[0]; + float m21 = in->getRow(2).m_floats[1]; + float m22 = in->getRow(2).m_floats[2]; + env->SetFloatField(out, jmeClasses::Matrix3f_m00, m00); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Matrix3f_m01, m01); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Matrix3f_m02, m02); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Matrix3f_m10, m10); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Matrix3f_m11, m11); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Matrix3f_m12, m12); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Matrix3f_m20, m20); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Matrix3f_m21, m21); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Matrix3f_m22, m22); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } +} + +void jmeBulletUtil::convertQuat(JNIEnv* env, jobject in, btMatrix3x3* out) { + if (in == NULL || out == NULL) { + jmeClasses::throwNPE(env); + } + float x = env->GetFloatField(in, jmeClasses::Quaternion_x); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float y = env->GetFloatField(in, jmeClasses::Quaternion_y); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float z = env->GetFloatField(in, jmeClasses::Quaternion_z); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + float w = env->GetFloatField(in, jmeClasses::Quaternion_w); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + float norm = w * w + x * x + y * y + z * z; + float s = (norm == 1.0) ? 2.0 : (norm > 0.1) ? 2.0 / norm : 0.0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + float xs = x * s; + float ys = y * s; + float zs = z * s; + float xx = x * xs; + float xy = x * ys; + float xz = x * zs; + float xw = w * xs; + float yy = y * ys; + float yz = y * zs; + float yw = w * ys; + float zz = z * zs; + float zw = w * zs; + + // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here + out->setValue(1.0 - (yy + zz), (xy - zw), (xz + yw), + (xy + zw), 1 - (xx + zz), (yz - xw), + (xz - yw), (yz + xw), 1.0 - (xx + yy)); +} + +void jmeBulletUtil::convertQuat(JNIEnv* env, const btMatrix3x3* in, jobject out) { + if (in == NULL || out == NULL) { + jmeClasses::throwNPE(env); + } + // the trace is the sum of the diagonal elements; see + // http://mathworld.wolfram.com/MatrixTrace.html + float t = in->getRow(0).m_floats[0] + in->getRow(1).m_floats[1] + in->getRow(2).m_floats[2]; + float w, x, y, z; + // we protect the division by s by ensuring that s>=1 + if (t >= 0) { // |w| >= .5 + float s = sqrt(t + 1); // |s|>=1 ... + w = 0.5f * s; + s = 0.5f / s; // so this division isn't bad + x = (in->getRow(2).m_floats[1] - in->getRow(1).m_floats[2]) * s; + y = (in->getRow(0).m_floats[2] - in->getRow(2).m_floats[0]) * s; + z = (in->getRow(1).m_floats[0] - in->getRow(0).m_floats[1]) * s; + } else if ((in->getRow(0).m_floats[0] > in->getRow(1).m_floats[1]) && (in->getRow(0).m_floats[0] > in->getRow(2).m_floats[2])) { + float s = sqrt(1.0f + in->getRow(0).m_floats[0] - in->getRow(1).m_floats[1] - in->getRow(2).m_floats[2]); // |s|>=1 + x = s * 0.5f; // |x| >= .5 + s = 0.5f / s; + y = (in->getRow(1).m_floats[0] + in->getRow(0).m_floats[1]) * s; + z = (in->getRow(0).m_floats[2] + in->getRow(2).m_floats[0]) * s; + w = (in->getRow(2).m_floats[1] - in->getRow(1).m_floats[2]) * s; + } else if (in->getRow(1).m_floats[1] > in->getRow(2).m_floats[2]) { + float s = sqrt(1.0f + in->getRow(1).m_floats[1] - in->getRow(0).m_floats[0] - in->getRow(2).m_floats[2]); // |s|>=1 + y = s * 0.5f; // |y| >= .5 + s = 0.5f / s; + x = (in->getRow(1).m_floats[0] + in->getRow(0).m_floats[1]) * s; + z = (in->getRow(2).m_floats[1] + in->getRow(1).m_floats[2]) * s; + w = (in->getRow(0).m_floats[2] - in->getRow(2).m_floats[0]) * s; + } else { + float s = sqrt(1.0f + in->getRow(2).m_floats[2] - in->getRow(0).m_floats[0] - in->getRow(1).m_floats[1]); // |s|>=1 + z = s * 0.5f; // |z| >= .5 + s = 0.5f / s; + x = (in->getRow(0).m_floats[2] + in->getRow(2).m_floats[0]) * s; + y = (in->getRow(2).m_floats[1] + in->getRow(1).m_floats[2]) * s; + w = (in->getRow(1).m_floats[0] - in->getRow(0).m_floats[1]) * s; + } + + env->SetFloatField(out, jmeClasses::Quaternion_x, x); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Quaternion_y, y); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Quaternion_z, z); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + env->SetFloatField(out, jmeClasses::Quaternion_w, w); + // env->CallObjectMethod(out, jmeClasses::Quaternion_set, x, y, z, w); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } +} + +void jmeBulletUtil::addResult(JNIEnv* env, jobject resultlist, btVector3 hitnormal, btVector3 m_hitPointWorld, btScalar m_hitFraction, const btCollisionObject* hitobject) { + + jobject singleresult = env->AllocObject(jmeClasses::PhysicsRay_Class); + jobject hitnormalvec = env->AllocObject(jmeClasses::Vector3f); + + convert(env, const_cast (&hitnormal), hitnormalvec); + jmeUserPointer *up1 = (jmeUserPointer*) hitobject -> getUserPointer(); + + env->SetObjectField(singleresult, jmeClasses::PhysicsRay_normalInWorldSpace, hitnormalvec); + env->SetFloatField(singleresult, jmeClasses::PhysicsRay_hitfraction, m_hitFraction); + + env->SetObjectField(singleresult, jmeClasses::PhysicsRay_collisionObject, up1->javaCollisionObject); + env->CallVoidMethod(resultlist, jmeClasses::PhysicsRay_addmethod, singleresult); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } +} diff --git a/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h b/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h new file mode 100644 index 000000000..9ec758201 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "jmeClasses.h" +#include "btBulletDynamicsCommon.h" +#include "btBulletCollisionCommon.h" +#include "LinearMath/btVector3.h" + +/** + * Author: Normen Hansen + */ +class jmeBulletUtil{ +public: + static void convert(JNIEnv* env, jobject in, btVector3* out); + static void convert(JNIEnv* env, const btVector3* in, jobject out); + static void convert(JNIEnv* env, jobject in, btMatrix3x3* out); + static void convert(JNIEnv* env, const btMatrix3x3* in, jobject out); + static void convertQuat(JNIEnv* env, jobject in, btMatrix3x3* out); + static void convertQuat(JNIEnv* env, const btMatrix3x3* in, jobject out); + static void addResult(JNIEnv* env, jobject resultlist, const btVector3 hitnormal,const btVector3 m_hitPointWorld,const btScalar m_hitFraction,const btCollisionObject* hitobject); +private: + jmeBulletUtil(){}; + ~jmeBulletUtil(){}; + +}; + +class jmeUserPointer { +public: + jobject javaCollisionObject; + jint group; + jint groups; + void *space; +}; \ No newline at end of file diff --git a/jme3-bullet-native/src/native/cpp/jmeClasses.cpp b/jme3-bullet-native/src/native/cpp/jmeClasses.cpp new file mode 100644 index 000000000..e0b5515b4 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/jmeClasses.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "jmeClasses.h" +#include + +/** + * Author: Normen Hansen,Empire Phoenix, Lutherion + */ +//public fields +jclass jmeClasses::PhysicsSpace; +jmethodID jmeClasses::PhysicsSpace_preTick; +jmethodID jmeClasses::PhysicsSpace_postTick; +jmethodID jmeClasses::PhysicsSpace_addCollisionEvent; + +jclass jmeClasses::PhysicsGhostObject; +jmethodID jmeClasses::PhysicsGhostObject_addOverlappingObject; + +jclass jmeClasses::Vector3f; +jmethodID jmeClasses::Vector3f_set; +jmethodID jmeClasses::Vector3f_toArray; +jmethodID jmeClasses::Vector3f_getX; +jmethodID jmeClasses::Vector3f_getY; +jmethodID jmeClasses::Vector3f_getZ; +jfieldID jmeClasses::Vector3f_x; +jfieldID jmeClasses::Vector3f_y; +jfieldID jmeClasses::Vector3f_z; + +jclass jmeClasses::Quaternion; +jmethodID jmeClasses::Quaternion_set; +jmethodID jmeClasses::Quaternion_getX; +jmethodID jmeClasses::Quaternion_getY; +jmethodID jmeClasses::Quaternion_getZ; +jmethodID jmeClasses::Quaternion_getW; +jfieldID jmeClasses::Quaternion_x; +jfieldID jmeClasses::Quaternion_y; +jfieldID jmeClasses::Quaternion_z; +jfieldID jmeClasses::Quaternion_w; + +jclass jmeClasses::Matrix3f; +jmethodID jmeClasses::Matrix3f_set; +jmethodID jmeClasses::Matrix3f_get; +jfieldID jmeClasses::Matrix3f_m00; +jfieldID jmeClasses::Matrix3f_m01; +jfieldID jmeClasses::Matrix3f_m02; +jfieldID jmeClasses::Matrix3f_m10; +jfieldID jmeClasses::Matrix3f_m11; +jfieldID jmeClasses::Matrix3f_m12; +jfieldID jmeClasses::Matrix3f_m20; +jfieldID jmeClasses::Matrix3f_m21; +jfieldID jmeClasses::Matrix3f_m22; + +jclass jmeClasses::DebugMeshCallback; +jmethodID jmeClasses::DebugMeshCallback_addVector; + +jclass jmeClasses::PhysicsRay_Class; +jmethodID jmeClasses::PhysicsRay_newSingleResult; + +jfieldID jmeClasses::PhysicsRay_normalInWorldSpace; +jfieldID jmeClasses::PhysicsRay_hitfraction; +jfieldID jmeClasses::PhysicsRay_collisionObject; + +jclass jmeClasses::PhysicsRay_listresult; +jmethodID jmeClasses::PhysicsRay_addmethod; + +//private fields +//JNIEnv* jmeClasses::env; +JavaVM* jmeClasses::vm; + +void jmeClasses::initJavaClasses(JNIEnv* env) { +// if (env != NULL) { +// fprintf(stdout, "Check Java VM state\n"); +// fflush(stdout); +// int res = vm->AttachCurrentThread((void**) &jmeClasses::env, NULL); +// if (res < 0) { +// fprintf(stdout, "** ERROR: getting Java env!\n"); +// if (res == JNI_EVERSION) fprintf(stdout, "GetEnv Error because of different JNI Version!\n"); +// fflush(stdout); +// } +// return; +// } + if(PhysicsSpace!=NULL) return; + fprintf(stdout, "Bullet-Native: Initializing java classes\n"); + fflush(stdout); +// jmeClasses::env = env; + env->GetJavaVM(&vm); + + PhysicsSpace = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/PhysicsSpace")); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + PhysicsSpace_preTick = env->GetMethodID(PhysicsSpace, "preTick_native", "(F)V"); + PhysicsSpace_postTick = env->GetMethodID(PhysicsSpace, "postTick_native", "(F)V"); + PhysicsSpace_addCollisionEvent = env->GetMethodID(PhysicsSpace, "addCollisionEvent_native","(Lcom/jme3/bullet/collision/PhysicsCollisionObject;Lcom/jme3/bullet/collision/PhysicsCollisionObject;J)V"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + PhysicsGhostObject = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/objects/PhysicsGhostObject")); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + PhysicsGhostObject_addOverlappingObject = env->GetMethodID(PhysicsGhostObject, "addOverlappingObject_native","(Lcom/jme3/bullet/collision/PhysicsCollisionObject;)V"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + Vector3f = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/math/Vector3f")); + Vector3f_set = env->GetMethodID(Vector3f, "set", "(FFF)Lcom/jme3/math/Vector3f;"); + Vector3f_toArray = env->GetMethodID(Vector3f, "toArray", "([F)[F"); + Vector3f_getX = env->GetMethodID(Vector3f, "getX", "()F"); + Vector3f_getY = env->GetMethodID(Vector3f, "getY", "()F"); + Vector3f_getZ = env->GetMethodID(Vector3f, "getZ", "()F"); + Vector3f_x = env->GetFieldID(Vector3f, "x", "F"); + Vector3f_y = env->GetFieldID(Vector3f, "y", "F"); + Vector3f_z = env->GetFieldID(Vector3f, "z", "F"); + + Quaternion = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/math/Quaternion")); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + Quaternion_set = env->GetMethodID(Quaternion, "set", "(FFFF)Lcom/jme3/math/Quaternion;"); + Quaternion_getW = env->GetMethodID(Quaternion, "getW", "()F"); + Quaternion_getX = env->GetMethodID(Quaternion, "getX", "()F"); + Quaternion_getY = env->GetMethodID(Quaternion, "getY", "()F"); + Quaternion_getZ = env->GetMethodID(Quaternion, "getZ", "()F"); + Quaternion_x = env->GetFieldID(Quaternion, "x", "F"); + Quaternion_y = env->GetFieldID(Quaternion, "y", "F"); + Quaternion_z = env->GetFieldID(Quaternion, "z", "F"); + Quaternion_w = env->GetFieldID(Quaternion, "w", "F"); + + Matrix3f = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/math/Matrix3f")); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + Matrix3f_set = env->GetMethodID(Matrix3f, "set", "(IIF)Lcom/jme3/math/Matrix3f;"); + Matrix3f_get = env->GetMethodID(Matrix3f, "get", "(II)F"); + Matrix3f_m00 = env->GetFieldID(Matrix3f, "m00", "F"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + Matrix3f_m01 = env->GetFieldID(Matrix3f, "m01", "F"); + Matrix3f_m02 = env->GetFieldID(Matrix3f, "m02", "F"); + Matrix3f_m10 = env->GetFieldID(Matrix3f, "m10", "F"); + Matrix3f_m11 = env->GetFieldID(Matrix3f, "m11", "F"); + Matrix3f_m12 = env->GetFieldID(Matrix3f, "m12", "F"); + Matrix3f_m20 = env->GetFieldID(Matrix3f, "m20", "F"); + Matrix3f_m21 = env->GetFieldID(Matrix3f, "m21", "F"); + Matrix3f_m22 = env->GetFieldID(Matrix3f, "m22", "F"); + + DebugMeshCallback = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/util/DebugMeshCallback")); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + DebugMeshCallback_addVector = env->GetMethodID(DebugMeshCallback, "addVector", "(FFFII)V"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + PhysicsRay_Class = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/collision/PhysicsRayTestResult")); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + PhysicsRay_newSingleResult = env->GetMethodID(PhysicsRay_Class,"","()V"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + PhysicsRay_normalInWorldSpace = env->GetFieldID(PhysicsRay_Class,"hitNormalLocal","Lcom/jme3/math/Vector3f;"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + + PhysicsRay_hitfraction = env->GetFieldID(PhysicsRay_Class,"hitFraction","F"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + + PhysicsRay_collisionObject = env->GetFieldID(PhysicsRay_Class,"collisionObject","Lcom/jme3/bullet/collision/PhysicsCollisionObject;"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + PhysicsRay_listresult = env->FindClass("java/util/List"); + PhysicsRay_listresult = (jclass)env->NewGlobalRef(PhysicsRay_listresult); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + + PhysicsRay_addmethod = env->GetMethodID(PhysicsRay_listresult,"add","(Ljava/lang/Object;)Z"); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } +} + +void jmeClasses::throwNPE(JNIEnv* env) { + if (env == NULL) return; + jclass newExc = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(newExc, ""); + return; +} diff --git a/jme3-bullet-native/src/native/cpp/jmeClasses.h b/jme3-bullet-native/src/native/cpp/jmeClasses.h new file mode 100644 index 000000000..24684dae9 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/jmeClasses.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include + +/** + * Author: Normen Hansen + */ + +class jmeClasses { +public: + static void initJavaClasses(JNIEnv* env); +// static JNIEnv* env; + static JavaVM* vm; + static jclass PhysicsSpace; + static jmethodID PhysicsSpace_preTick; + static jmethodID PhysicsSpace_postTick; + static jmethodID PhysicsSpace_addCollisionEvent; + static jclass PhysicsGhostObject; + static jmethodID PhysicsGhostObject_addOverlappingObject; + + static jclass Vector3f; + static jmethodID Vector3f_set; + static jmethodID Vector3f_getX; + static jmethodID Vector3f_getY; + static jmethodID Vector3f_getZ; + static jmethodID Vector3f_toArray; + static jfieldID Vector3f_x; + static jfieldID Vector3f_y; + static jfieldID Vector3f_z; + + static jclass Quaternion; + static jmethodID Quaternion_set; + static jmethodID Quaternion_getX; + static jmethodID Quaternion_getY; + static jmethodID Quaternion_getZ; + static jmethodID Quaternion_getW; + static jfieldID Quaternion_x; + static jfieldID Quaternion_y; + static jfieldID Quaternion_z; + static jfieldID Quaternion_w; + + static jclass Matrix3f; + static jmethodID Matrix3f_get; + static jmethodID Matrix3f_set; + static jfieldID Matrix3f_m00; + static jfieldID Matrix3f_m01; + static jfieldID Matrix3f_m02; + static jfieldID Matrix3f_m10; + static jfieldID Matrix3f_m11; + static jfieldID Matrix3f_m12; + static jfieldID Matrix3f_m20; + static jfieldID Matrix3f_m21; + static jfieldID Matrix3f_m22; + + static jclass PhysicsRay_Class; + static jmethodID PhysicsRay_newSingleResult; + static jfieldID PhysicsRay_normalInWorldSpace; + static jfieldID PhysicsRay_hitfraction; + static jfieldID PhysicsRay_collisionObject; + static jclass PhysicsRay_listresult; + static jmethodID PhysicsRay_addmethod; + + static jclass DebugMeshCallback; + static jmethodID DebugMeshCallback_addVector; + + static void throwNPE(JNIEnv* env); +private: + jmeClasses(){}; + ~jmeClasses(){}; +}; \ No newline at end of file diff --git a/jme3-bullet-native/src/native/cpp/jmeMotionState.cpp b/jme3-bullet-native/src/native/cpp/jmeMotionState.cpp new file mode 100644 index 000000000..7c14207fe --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/jmeMotionState.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "jmeMotionState.h" +#include "jmeBulletUtil.h" + +/** + * Author: Normen Hansen + */ + +jmeMotionState::jmeMotionState() { + trans = new btTransform(); + trans -> setIdentity(); + worldTransform = *trans; + dirty = true; +} + +void jmeMotionState::getWorldTransform(btTransform& worldTrans) const { + worldTrans = worldTransform; +} + +void jmeMotionState::setWorldTransform(const btTransform& worldTrans) { + worldTransform = worldTrans; + dirty = true; +} + +void jmeMotionState::setKinematicTransform(const btTransform& worldTrans) { + worldTransform = worldTrans; + dirty = true; +} + +void jmeMotionState::setKinematicLocation(JNIEnv* env, jobject location) { + jmeBulletUtil::convert(env, location, &worldTransform.getOrigin()); + dirty = true; +} + +void jmeMotionState::setKinematicRotation(JNIEnv* env, jobject rotation) { + jmeBulletUtil::convert(env, rotation, &worldTransform.getBasis()); + dirty = true; +} + +void jmeMotionState::setKinematicRotationQuat(JNIEnv* env, jobject rotation) { + jmeBulletUtil::convertQuat(env, rotation, &worldTransform.getBasis()); + dirty = true; +} + +bool jmeMotionState::applyTransform(JNIEnv* env, jobject location, jobject rotation) { + if (dirty) { + // fprintf(stdout, "Apply world translation\n"); + // fflush(stdout); + jmeBulletUtil::convert(env, &worldTransform.getOrigin(), location); + jmeBulletUtil::convertQuat(env, &worldTransform.getBasis(), rotation); + dirty = false; + return true; + } + return false; +} + +jmeMotionState::~jmeMotionState() { + free(trans); +} diff --git a/jme3-bullet-native/src/native/cpp/jmeMotionState.h b/jme3-bullet-native/src/native/cpp/jmeMotionState.h new file mode 100644 index 000000000..d4e4d195f --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/jmeMotionState.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include + +/** + * Author: Normen Hansen + */ + +#include "btBulletDynamicsCommon.h" +//#include "btBulletCollisionCommon.h" + +class jmeMotionState : public btMotionState { +private: + bool dirty; + btTransform* trans; +public: + jmeMotionState(); + virtual ~jmeMotionState(); + + btTransform worldTransform; + virtual void getWorldTransform(btTransform& worldTrans) const; + virtual void setWorldTransform(const btTransform& worldTrans); + void setKinematicTransform(const btTransform& worldTrans); + void setKinematicLocation(JNIEnv*, jobject); + void setKinematicRotation(JNIEnv*, jobject); + void setKinematicRotationQuat(JNIEnv*, jobject); + bool applyTransform(JNIEnv* env, jobject location, jobject rotation); +}; diff --git a/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp b/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp new file mode 100644 index 000000000..34c77c407 --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "jmePhysicsSpace.h" +#include "jmeBulletUtil.h" +#include + +/** + * Author: Normen Hansen + */ +jmePhysicsSpace::jmePhysicsSpace(JNIEnv* env, jobject javaSpace) { + //TODO: global ref? maybe not -> cleaning, rather callback class? + this->javaPhysicsSpace = env->NewWeakGlobalRef(javaSpace); + this->env = env; + env->GetJavaVM(&vm); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } +} + +void jmePhysicsSpace::attachThread() { +#ifdef ANDROID + vm->AttachCurrentThread((JNIEnv**) &env, NULL); +#elif defined (JNI_VERSION_1_2) + vm->AttachCurrentThread((void**) &env, NULL); +#else + vm->AttachCurrentThread(&env, NULL); +#endif +} + +JNIEnv* jmePhysicsSpace::getEnv() { + attachThread(); + return this->env; +} + +void jmePhysicsSpace::stepSimulation(jfloat tpf, jint maxSteps, jfloat accuracy) { + dynamicsWorld->stepSimulation(tpf, maxSteps, accuracy); +} + +btThreadSupportInterface* jmePhysicsSpace::createSolverThreadSupport(int maxNumThreads) { +#ifdef _WIN32 + Win32ThreadSupport::Win32ThreadConstructionInfo threadConstructionInfo("solverThreads", SolverThreadFunc, SolverlsMemoryFunc, maxNumThreads); + Win32ThreadSupport* threadSupport = new Win32ThreadSupport(threadConstructionInfo); + threadSupport->startSPU(); +#elif defined (USE_PTHREADS) + PosixThreadSupport::ThreadConstructionInfo constructionInfo("collision", SolverThreadFunc, + SolverlsMemoryFunc, maxNumThreads); + PosixThreadSupport* threadSupport = new PosixThreadSupport(constructionInfo); + threadSupport->startSPU(); +#else + SequentialThreadSupport::SequentialThreadConstructionInfo tci("solverThreads", SolverThreadFunc, SolverlsMemoryFunc); + SequentialThreadSupport* threadSupport = new SequentialThreadSupport(tci); + threadSupport->startSPU(); +#endif + return threadSupport; +} + +btThreadSupportInterface* jmePhysicsSpace::createDispatchThreadSupport(int maxNumThreads) { +#ifdef _WIN32 + Win32ThreadSupport::Win32ThreadConstructionInfo threadConstructionInfo("solverThreads", processCollisionTask, createCollisionLocalStoreMemory, maxNumThreads); + Win32ThreadSupport* threadSupport = new Win32ThreadSupport(threadConstructionInfo); + threadSupport->startSPU(); +#elif defined (USE_PTHREADS) + PosixThreadSupport::ThreadConstructionInfo solverConstructionInfo("solver", processCollisionTask, + createCollisionLocalStoreMemory, maxNumThreads); + PosixThreadSupport* threadSupport = new PosixThreadSupport(solverConstructionInfo); + threadSupport->startSPU(); +#else + SequentialThreadSupport::SequentialThreadConstructionInfo tci("solverThreads", processCollisionTask, createCollisionLocalStoreMemory); + SequentialThreadSupport* threadSupport = new SequentialThreadSupport(tci); + threadSupport->startSPU(); +#endif + return threadSupport; +} + +void jmePhysicsSpace::createPhysicsSpace(jfloat minX, jfloat minY, jfloat minZ, jfloat maxX, jfloat maxY, jfloat maxZ, jint broadphaseId, jboolean threading) { + // collision configuration contains default setup for memory, collision setup + btDefaultCollisionConstructionInfo cci; + // if(threading){ + // cci.m_defaultMaxPersistentManifoldPoolSize = 32768; + // } + btCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration(cci); + + btVector3 min = btVector3(minX, minY, minZ); + btVector3 max = btVector3(maxX, maxY, maxZ); + + btBroadphaseInterface* broadphase; + + switch (broadphaseId) { + case 0: + broadphase = new btSimpleBroadphase(); + break; + case 1: + broadphase = new btAxisSweep3(min, max); + break; + case 2: + //TODO: 32bit! + broadphase = new btAxisSweep3(min, max); + break; + case 3: + broadphase = new btDbvtBroadphase(); + break; + case 4: + // broadphase = new btGpu3DGridBroadphase( + // min, max, + // 20, 20, 20, + // 10000, 1000, 25); + break; + } + + btCollisionDispatcher* dispatcher; + btConstraintSolver* solver; + // use the default collision dispatcher. For parallel processing you can use a diffent dispatcher (see Extras/BulletMultiThreaded) + if (threading) { + btThreadSupportInterface* dispatchThreads = createDispatchThreadSupport(4); + dispatcher = new SpuGatheringCollisionDispatcher(dispatchThreads, 4, collisionConfiguration); + dispatcher->setDispatcherFlags(btCollisionDispatcher::CD_DISABLE_CONTACTPOOL_DYNAMIC_ALLOCATION); + } else { + dispatcher = new btCollisionDispatcher(collisionConfiguration); + } + + // the default constraint solver. For parallel processing you can use a different solver (see Extras/BulletMultiThreaded) + if (threading) { + btThreadSupportInterface* solverThreads = createSolverThreadSupport(4); + solver = new btParallelConstraintSolver(solverThreads); + } else { + solver = new btSequentialImpulseConstraintSolver; + } + + //create dynamics world + btDiscreteDynamicsWorld* world = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration); + dynamicsWorld = world; + dynamicsWorld->setWorldUserInfo(this); + + //parallel solver requires the contacts to be in a contiguous pool, so avoid dynamic allocation + if (threading) { + world->getSimulationIslandManager()->setSplitIslands(false); + world->getSolverInfo().m_numIterations = 4; + world->getSolverInfo().m_solverMode = SOLVER_SIMD + SOLVER_USE_WARMSTARTING; //+SOLVER_RANDMIZE_ORDER; + world->getDispatchInfo().m_enableSPU = true; + } + + broadphase->getOverlappingPairCache()->setInternalGhostPairCallback(new btGhostPairCallback()); + + dynamicsWorld->setGravity(btVector3(0, -9.81f, 0)); + + struct jmeFilterCallback : public btOverlapFilterCallback { + // return true when pairs need collision + + virtual bool needBroadphaseCollision(btBroadphaseProxy* proxy0, btBroadphaseProxy * proxy1) const { + // bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0; + // collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask); + bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0; + collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask); + if (collides) { + btCollisionObject* co0 = (btCollisionObject*) proxy0->m_clientObject; + btCollisionObject* co1 = (btCollisionObject*) proxy1->m_clientObject; + jmeUserPointer *up0 = (jmeUserPointer*) co0 -> getUserPointer(); + jmeUserPointer *up1 = (jmeUserPointer*) co1 -> getUserPointer(); + if (up0 != NULL && up1 != NULL) { + collides = (up0->group & up1->groups) != 0; + collides = collides && (up1->group & up0->groups); + + //add some additional logic here that modified 'collides' + return collides; + } + return false; + } + return collides; + } + }; + dynamicsWorld->getPairCache()->setOverlapFilterCallback(new jmeFilterCallback()); + dynamicsWorld->setInternalTickCallback(&jmePhysicsSpace::preTickCallback, static_cast (this), true); + dynamicsWorld->setInternalTickCallback(&jmePhysicsSpace::postTickCallback, static_cast (this)); + if (gContactProcessedCallback == NULL) { + gContactProcessedCallback = &jmePhysicsSpace::contactProcessedCallback; + } +} + +void jmePhysicsSpace::preTickCallback(btDynamicsWorld *world, btScalar timeStep) { + jmePhysicsSpace* dynamicsWorld = (jmePhysicsSpace*) world->getWorldUserInfo(); + JNIEnv* env = dynamicsWorld->getEnv(); + jobject javaPhysicsSpace = env->NewLocalRef(dynamicsWorld->getJavaPhysicsSpace()); + if (javaPhysicsSpace != NULL) { + env->CallVoidMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_preTick, timeStep); + env->DeleteLocalRef(javaPhysicsSpace); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + } +} + +void jmePhysicsSpace::postTickCallback(btDynamicsWorld *world, btScalar timeStep) { + jmePhysicsSpace* dynamicsWorld = (jmePhysicsSpace*) world->getWorldUserInfo(); + JNIEnv* env = dynamicsWorld->getEnv(); + jobject javaPhysicsSpace = env->NewLocalRef(dynamicsWorld->getJavaPhysicsSpace()); + if (javaPhysicsSpace != NULL) { + env->CallVoidMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_postTick, timeStep); + env->DeleteLocalRef(javaPhysicsSpace); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return; + } + } +} + +bool jmePhysicsSpace::contactProcessedCallback(btManifoldPoint &cp, void *body0, void *body1) { + // printf("contactProcessedCallback %d %dn", body0, body1); + btCollisionObject* co0 = (btCollisionObject*) body0; + jmeUserPointer *up0 = (jmeUserPointer*) co0 -> getUserPointer(); + btCollisionObject* co1 = (btCollisionObject*) body1; + jmeUserPointer *up1 = (jmeUserPointer*) co1 -> getUserPointer(); + if (up0 != NULL) { + jmePhysicsSpace *dynamicsWorld = (jmePhysicsSpace *)up0->space; + if (dynamicsWorld != NULL) { + JNIEnv* env = dynamicsWorld->getEnv(); + jobject javaPhysicsSpace = env->NewLocalRef(dynamicsWorld->getJavaPhysicsSpace()); + if (javaPhysicsSpace != NULL) { + jobject javaCollisionObject0 = env->NewLocalRef(up0->javaCollisionObject); + jobject javaCollisionObject1 = env->NewLocalRef(up1->javaCollisionObject); + env->CallVoidMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_addCollisionEvent, javaCollisionObject0, javaCollisionObject1, (jlong) & cp); + env->DeleteLocalRef(javaPhysicsSpace); + env->DeleteLocalRef(javaCollisionObject0); + env->DeleteLocalRef(javaCollisionObject1); + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return true; + } + } + } + } + return true; +} + +btDynamicsWorld* jmePhysicsSpace::getDynamicsWorld() { + return dynamicsWorld; +} + +jobject jmePhysicsSpace::getJavaPhysicsSpace() { + return javaPhysicsSpace; +} + +jmePhysicsSpace::~jmePhysicsSpace() { + delete(dynamicsWorld); +} \ No newline at end of file diff --git a/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.h b/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.h new file mode 100644 index 000000000..2e2b3865c --- /dev/null +++ b/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include "btBulletDynamicsCommon.h" +#include "btBulletCollisionCommon.h" +#include "BulletCollision/CollisionDispatch/btCollisionDispatcher.h" +#include "BulletCollision/CollisionDispatch/btCollisionObject.h" +#include "BulletCollision/CollisionDispatch/btGhostObject.h" +#include "BulletDynamics/Character/btKinematicCharacterController.h" +#ifdef _WIN32 +#include "BulletMultiThreaded/Win32ThreadSupport.h" +#else +#include "BulletMultiThreaded/PosixThreadSupport.h" +#endif +#include "BulletMultiThreaded/btParallelConstraintSolver.h" +#include "BulletMultiThreaded/SpuGatheringCollisionDispatcher.h" +#include "BulletMultiThreaded/SpuCollisionTaskProcess.h" +#include "BulletMultiThreaded/SequentialThreadSupport.h" +#include "BulletCollision/CollisionDispatch/btSimulationIslandManager.h" +#include "BulletCollision/NarrowPhaseCollision/btManifoldPoint.h" +#include "BulletCollision/NarrowPhaseCollision/btPersistentManifold.h" + +/** + * Author: Normen Hansen + */ +class jmePhysicsSpace { +private: + JNIEnv* env; + JavaVM* vm; + btDynamicsWorld* dynamicsWorld; + jobject javaPhysicsSpace; + btThreadSupportInterface* createSolverThreadSupport(int); + btThreadSupportInterface* createDispatchThreadSupport(int); + void attachThread(); +public: + jmePhysicsSpace(){}; + ~jmePhysicsSpace(); + jmePhysicsSpace(JNIEnv*, jobject); + void stepSimulation(jfloat, jint, jfloat); + void createPhysicsSpace(jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jint, jboolean); + btDynamicsWorld* getDynamicsWorld(); + jobject getJavaPhysicsSpace(); + JNIEnv* getEnv(); + static void preTickCallback(btDynamicsWorld*, btScalar); + static void postTickCallback(btDynamicsWorld*, btScalar); + static bool contactProcessedCallback(btManifoldPoint &, void *, void *); +}; \ No newline at end of file diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java b/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java new file mode 100644 index 000000000..05aa9a926 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet; + +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.bullet.debug.BulletDebugAppState; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import java.util.concurrent.*; +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); + protected float speed = 1; + protected boolean active = true; + protected boolean debugEnabled = false; + protected BulletDebugAppState debugAppState; + 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() { + if (initialized) { + return; + } + //start physics thread(pool) + if (threadingType == ThreadingType.PARALLEL) { + startPhysicsOnExecutor(); + } else { + pSpace = new PhysicsSpace(worldMin, worldMax, broadphaseType); + } + pSpace.addTickListener(this); + initialized = true; + } + + public void stopPhysics() { + if(!initialized){ + return; + } + if (executor != null) { + executor.shutdown(); + executor = null; + } + pSpace.removeTickListener(this); + pSpace.destroy(); + initialized = false; + } + + public void initialize(AppStateManager stateManager, Application app) { + this.app = app; + this.stateManager = stateManager; + startPhysics(); + } + + public boolean isInitialized() { + return initialized; + } + + public void setEnabled(boolean enabled) { + this.active = enabled; + } + + public boolean isEnabled() { + return active; + } + + public void setDebugEnabled(boolean debugEnabled) { + this.debugEnabled = debugEnabled; + } + + public boolean isDebugEnabled() { + return debugEnabled; + } + + public void stateAttached(AppStateManager stateManager) { + if (!initialized) { + startPhysics(); + } + if (threadingType == ThreadingType.PARALLEL) { + PhysicsSpace.setLocalThreadPhysicsSpace(pSpace); + } + if (debugEnabled) { + debugAppState = new BulletDebugAppState(pSpace); + stateManager.attach(debugAppState); + } + } + + public void stateDetached(AppStateManager stateManager) { + } + + public void update(float tpf) { + if (debugEnabled && debugAppState == null && pSpace != null) { + debugAppState = new BulletDebugAppState(pSpace); + stateManager.attach(debugAppState); + pSpace.enableDebug(app.getAssetManager()); + } else if (!debugEnabled && debugAppState != null) { + stateManager.detach(debugAppState); + debugAppState = null; + if (pSpace != null) { + pSpace.enableDebug(null); + } + } + //TODO: remove when deprecation of PhysicsSpace.enableDebug is through + if (pSpace.getDebugManager() != null && !debugEnabled) { + debugEnabled = true; + } else if (pSpace.getDebugManager() == null && debugEnabled) { + debugEnabled = false; + } + if (!active) { + return; + } + 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 (debugAppState != null) { + stateManager.detach(debugAppState); + debugAppState = null; + } + stopPhysics(); + } + + /** + * @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, + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/PhysicsTickListener.java b/jme3-bullet/src/common/java/com/jme3/bullet/PhysicsTickListener.java new file mode 100644 index 000000000..0a52a5119 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/PhysicsTickListener.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet; + +/** + * 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 the physics space + * @param tpf the time per frame in seconds + */ + public void prePhysicsTick(PhysicsSpace space, float tpf); + + /** + * Called after the physics has been stepped, use to check for forces etc. + * @param space the physics space + * @param tpf the time per frame in seconds + */ + public void physicsTick(PhysicsSpace space, float tpf); + +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/collision/PhysicsCollisionGroupListener.java b/jme3-bullet/src/common/java/com/jme3/bullet/collision/PhysicsCollisionGroupListener.java new file mode 100644 index 000000000..e50324116 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/collision/PhysicsCollisionGroupListener.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +/** + * + * @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/jme3-bullet/src/common/java/com/jme3/bullet/collision/PhysicsCollisionListener.java b/jme3-bullet/src/common/java/com/jme3/bullet/collision/PhysicsCollisionListener.java new file mode 100644 index 000000000..86ba943ab --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/collision/PhysicsCollisionListener.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +/** + * 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/jme3-bullet/src/common/java/com/jme3/bullet/collision/RagdollCollisionListener.java b/jme3-bullet/src/common/java/com/jme3/bullet/collision/RagdollCollisionListener.java new file mode 100644 index 000000000..cb00aeff3 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/collision/RagdollCollisionListener.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +import com.jme3.animation.Bone; + +/** + * + * @author Nehon + */ +public interface RagdollCollisionListener { + + public void collide(Bone bone, PhysicsCollisionObject object, PhysicsCollisionEvent event); + +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java b/jme3-bullet/src/common/java/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java new file mode 100644 index 000000000..ac7196258 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes.infos; + +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.export.*; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * + * @author normenhansen + */ +public class ChildCollisionShape implements Savable { + + public Vector3f location; + public Matrix3f rotation; + public CollisionShape shape; + + public ChildCollisionShape() { + } + + public ChildCollisionShape(Vector3f location, Matrix3f rotation, CollisionShape shape) { + this.location = location; + this.rotation = rotation; + this.shape = shape; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(location, "location", new Vector3f()); + capsule.write(rotation, "rotation", new Matrix3f()); + capsule.write(shape, "shape", new BoxCollisionShape(new Vector3f(1, 1, 1))); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule capsule = im.getCapsule(this); + location = (Vector3f) capsule.readSavable("location", new Vector3f()); + rotation = (Matrix3f) capsule.readSavable("rotation", new Matrix3f()); + shape = (CollisionShape) capsule.readSavable("shape", new BoxCollisionShape(new Vector3f(1, 1, 1))); + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java new file mode 100644 index 000000000..7c69fdf51 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.control; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import 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 java.io.IOException; + +/** + * AbstractPhysicsControl manages the lifecycle of a physics object that is + * attached to a spatial in the SceneGraph. + * + * @author normenhansen + */ +public abstract class AbstractPhysicsControl implements PhysicsControl { + + private final Quaternion tmp_inverseWorldRotation = new Quaternion(); + protected Spatial spatial; + protected boolean enabled = true; + protected boolean added = false; + protected PhysicsSpace space = null; + protected boolean applyLocal = false; + + /** + * Called when the control is added to a new spatial, create any + * spatial-dependent data here. + * + * @param spat The new spatial, guaranteed not to be null + */ + protected abstract void createSpatialData(Spatial spat); + + /** + * Called when the control is removed from a spatial, remove any + * spatial-dependent data here. + * + * @param spat The old spatial, guaranteed not to be null + */ + protected abstract void removeSpatialData(Spatial spat); + + /** + * Called when the physics object is supposed to move to the spatial + * position. + * + * @param vec + */ + protected abstract void setPhysicsLocation(Vector3f vec); + + /** + * Called when the physics object is supposed to move to the spatial + * rotation. + * + * @param quat + */ + protected abstract void setPhysicsRotation(Quaternion quat); + + /** + * Called when the physics object is supposed to add all objects it needs to + * manage to the physics space. + * + * @param space + */ + protected abstract void addPhysics(PhysicsSpace space); + + /** + * Called when the physics object is supposed to remove all objects added to + * the physics space. + * + * @param space + */ + protected abstract void removePhysics(PhysicsSpace space); + + 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; + } + + protected Vector3f getSpatialTranslation() { + if (applyLocal) { + return spatial.getLocalTranslation(); + } + return spatial.getWorldTranslation(); + } + + protected Quaternion getSpatialRotation() { + if (applyLocal) { + return spatial.getLocalRotation(); + } + return spatial.getWorldRotation(); + } + + /** + * Applies a physics transform to the spatial + * + * @param worldLocation + * @param worldRotation + */ + protected void applyPhysicsTransform(Vector3f worldLocation, Quaternion worldRotation) { + if (enabled && spatial != null) { + Vector3f localLocation = spatial.getLocalTranslation(); + Quaternion localRotationQuat = spatial.getLocalRotation(); + if (!applyLocal && 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(worldRotation); + tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().mult(localRotationQuat, localRotationQuat); + + spatial.setLocalTranslation(localLocation); + spatial.setLocalRotation(localRotationQuat); + } else { + spatial.setLocalTranslation(worldLocation); + spatial.setLocalRotation(worldRotation); + } + } + + } + + public void setSpatial(Spatial spatial) { + if (this.spatial != null && this.spatial != spatial) { + removeSpatialData(this.spatial); + } else if (this.spatial == spatial) { + return; + } + this.spatial = spatial; + if (spatial == null) { + return; + } + createSpatialData(this.spatial); + 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()); + } + addPhysics(space); + added = true; + } else if (!enabled && added) { + removePhysics(space); + added = false; + } + } + } + + public boolean isEnabled() { + return enabled; + } + + public void update(float tpf) { + } + + public void render(RenderManager rm, ViewPort vp) { + } + + public void setPhysicsSpace(PhysicsSpace space) { + if (space == null) { + if (this.space != null) { + removePhysics(this.space); + added = false; + } + } else { + if (this.space == space) { + return; + } else if (this.space != null) { + removePhysics(this.space); + } + addPhysics(space); + added = true; + } + this.space = space; + } + + public PhysicsSpace getPhysicsSpace() { + return space; + } + + @Override + public void write(JmeExporter ex) throws IOException { + 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 { + InputCapsule ic = im.getCapsule(this); + enabled = ic.readBoolean("enabled", true); + spatial = (Spatial) ic.readSavable("spatial", null); + applyLocal = ic.readBoolean("applyLocalPhysics", false); + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java new file mode 100644 index 000000000..065a645eb --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java @@ -0,0 +1,694 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.control; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.PhysicsRayTestResult; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.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.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This is intended to be a replacement for the internal bullet character class. + * A RigidBody with cylinder collision shape is used and its velocity is set + * continuously, a ray test is used to check if the character is on the ground. + * + * The character keeps his own local coordinate system which adapts based on the + * gravity working on the character so the character will always stand upright. + * + * Forces in the local x/z plane are dampened while those in the local y + * direction are applied fully (e.g. jumping, falling). + * + * @author normenhansen + */ +public class BetterCharacterControl extends AbstractPhysicsControl implements PhysicsTickListener { + + protected static final Logger logger = Logger.getLogger(BetterCharacterControl.class.getName()); + protected PhysicsRigidBody rigidBody; + protected float radius; + protected float height; + protected float mass; + protected float duckedFactor = 0.6f; + /** + * Local up direction, derived from gravity. + */ + protected final Vector3f localUp = new Vector3f(0, 1, 0); + /** + * Local absolute z-forward direction, derived from gravity and UNIT_Z, + * updated continuously when gravity changes. + */ + protected final Vector3f localForward = new Vector3f(0, 0, 1); + /** + * Local left direction, derived from up and forward. + */ + protected final Vector3f localLeft = new Vector3f(1, 0, 0); + /** + * Local z-forward quaternion for the "local absolute" z-forward direction. + */ + protected final Quaternion localForwardRotation = new Quaternion(Quaternion.DIRECTION_Z); + /** + * Is a z-forward vector based on the view direction and the current local + * x/z plane. + */ + protected final Vector3f viewDirection = new Vector3f(0, 0, 1); + /** + * Stores final spatial location, corresponds to RigidBody location. + */ + protected final Vector3f location = new Vector3f(); + /** + * Stores final spatial rotation, is a z-forward rotation based on the view + * direction and the current local x/z plane. See also rotatedViewDirection. + */ + protected final Quaternion rotation = new Quaternion(Quaternion.DIRECTION_Z); + protected final Vector3f rotatedViewDirection = new Vector3f(0, 0, 1); + protected final Vector3f walkDirection = new Vector3f(); + protected final Vector3f jumpForce; + protected float physicsDamping = 0.9f; + protected final Vector3f scale = new Vector3f(1, 1, 1); + protected final Vector3f velocity = new Vector3f(); + protected boolean jump = false; + protected boolean onGround = false; + protected boolean ducked = false; + protected boolean wantToUnDuck = false; + + /** + * Only used for serialization, do not use this constructor. + */ + public BetterCharacterControl() { + jumpForce = new Vector3f(); + } + + /** + * Creates a new character with the given properties. Note that to avoid + * issues the final height when ducking should be larger than 2x radius. The + * jumpForce will be set to an upwards force of 5x mass. + * + * @param radius + * @param height + * @param mass + */ + public BetterCharacterControl(float radius, float height, float mass) { + this.radius = radius; + this.height = height; + this.mass = mass; + rigidBody = new PhysicsRigidBody(getShape(), mass); + jumpForce = new Vector3f(0, mass * 5, 0); + rigidBody.setAngularFactor(0); + } + + @Override + public void update(float tpf) { + super.update(tpf); + rigidBody.getPhysicsLocation(location); + //rotation has been set through viewDirection + applyPhysicsTransform(location, rotation); + } + + @Override + public void render(RenderManager rm, ViewPort vp) { + super.render(rm, vp); + } + + /** + * Used internally, don't call manually + * + * @param space + * @param tpf + */ + public void prePhysicsTick(PhysicsSpace space, float tpf) { + checkOnGround(); + if (wantToUnDuck && checkCanUnDuck()) { + setHeightPercent(1); + wantToUnDuck = false; + ducked = false; + } + TempVars vars = TempVars.get(); + + // dampen existing x/z forces + float existingLeftVelocity = velocity.dot(localLeft); + float existingForwardVelocity = velocity.dot(localForward); + Vector3f counter = vars.vect1; + existingLeftVelocity = existingLeftVelocity * physicsDamping; + existingForwardVelocity = existingForwardVelocity * physicsDamping; + counter.set(-existingLeftVelocity, 0, -existingForwardVelocity); + localForwardRotation.multLocal(counter); + velocity.addLocal(counter); + + float designatedVelocity = walkDirection.length(); + if (designatedVelocity > 0) { + Vector3f localWalkDirection = vars.vect1; + //normalize walkdirection + localWalkDirection.set(walkDirection).normalizeLocal(); + //check for the existing velocity in the desired direction + float existingVelocity = velocity.dot(localWalkDirection); + //calculate the final velocity in the desired direction + float finalVelocity = designatedVelocity - existingVelocity; + localWalkDirection.multLocal(finalVelocity); + //add resulting vector to existing velocity + velocity.addLocal(localWalkDirection); + } + rigidBody.setLinearVelocity(velocity); + if (jump) { + //TODO: precalculate jump force + Vector3f rotatedJumpForce = vars.vect1; + rotatedJumpForce.set(jumpForce); + rigidBody.applyImpulse(localForwardRotation.multLocal(rotatedJumpForce), Vector3f.ZERO); + jump = false; + } + vars.release(); + } + + /** + * Used internally, don't call manually + * + * @param space + * @param tpf + */ + public void physicsTick(PhysicsSpace space, float tpf) { + rigidBody.getLinearVelocity(velocity); + } + + /** + * Move the character somewhere. Note the character also takes the location + * of any spatial its being attached to in the moment it is attached. + * + * @param vec The new character location. + */ + public void warp(Vector3f vec) { + setPhysicsLocation(vec); + } + + /** + * Makes the character jump with the set jump force. + */ + public void jump() { + //TODO: debounce over some frames + if (!onGround) { + return; + } + jump = true; + } + + /** + * Set the jump force as a Vector3f. The jump force is local to the + * characters coordinate system, which normally is always z-forward (in + * world coordinates, parent coordinates when set to applyLocalPhysics) + * + * @param jumpForce The new jump force + */ + public void setJumpForce(Vector3f jumpForce) { + this.jumpForce.set(jumpForce); + } + + /** + * Gets the current jump force. The default is 5 * character mass in y + * direction. + * + * @return + */ + public Vector3f getJumpForce() { + return jumpForce; + } + + /** + * Check if the character is on the ground. This is determined by a ray test + * in the center of the character and might return false even if the + * character is not falling yet. + * + * @return + */ + public boolean isOnGround() { + return onGround; + } + + /** + * Toggle character ducking. When ducked the characters capsule collision + * shape height will be multiplied by duckedFactor to make the capsule + * smaller. When unducking, the character will check with a ray test if it + * can in fact unduck and only do so when its possible. You can check the + * state of the unducking by checking isDucked(). + * + * @param enabled + */ + public void setDucked(boolean enabled) { + if (enabled) { + setHeightPercent(duckedFactor); + ducked = true; + wantToUnDuck = false; + } else { + if (checkCanUnDuck()) { + setHeightPercent(1); + ducked = false; + } else { + wantToUnDuck = true; + } + } + } + + /** + * Check if the character is ducking, either due to user input or due to + * unducking being impossible at the moment (obstacle above). + * + * @return + */ + public boolean isDucked() { + return ducked; + } + + /** + * Sets the height multiplication factor for ducking. + * + * @param factor The factor by which the height should be multiplied when + * ducking + */ + public void setDuckedFactor(float factor) { + duckedFactor = factor; + } + + /** + * Gets the height multiplication factor for ducking. + * + * @return + */ + public float getDuckedFactor() { + return duckedFactor; + } + + /** + * Sets the walk direction of the character. This parameter is framerate + * independent and the character will move continuously in the direction + * given by the vector with the speed given by the vector length in m/s. + * + * @param vec The movement direction and speed in m/s + */ + public void setWalkDirection(Vector3f vec) { + walkDirection.set(vec); + } + + /** + * Gets the current walk direction and speed of the character. The length of + * the vector defines the speed. + * + * @return + */ + public Vector3f getWalkDirection() { + return walkDirection; + } + + /** + * Sets the view direction for the character. Note this only defines the + * rotation of the spatial in the local x/z plane of the character. + * + * @param vec + */ + public void setViewDirection(Vector3f vec) { + viewDirection.set(vec); + updateLocalViewDirection(); + } + + /** + * Gets the current view direction, note this doesn't need to correspond + * with the spatials forward direction. + * + * @return + */ + public Vector3f getViewDirection() { + return viewDirection; + } + + /** + * Realign the local forward vector to given direction vector, if null is + * supplied Vector3f.UNIT_Z is used. Input vector has to be perpendicular to + * current gravity vector. This normally only needs to be called when the + * gravity direction changed continuously and the local forward vector is + * off due to drift. E.g. after walking around on a sphere "planet" for a + * while and then going back to a y-up coordinate system the local z-forward + * might not be 100% alinged with Z axis. + * + * @param vec The new forward vector, has to be perpendicular to the current + * gravity vector! + */ + public void resetForward(Vector3f vec) { + if (vec == null) { + vec = Vector3f.UNIT_Z; + } + localForward.set(vec); + updateLocalCoordinateSystem(); + } + + /** + * Get the current linear velocity along the three axes of the character. + * This is prepresented in world coordinates, parent coordinates when the + * control is set to applyLocalPhysics. + * + * @return The current linear velocity of the character + */ + public Vector3f getVelocity() { + return velocity; + } + + /** + * Set the gravity for this character. Note that this also realigns the + * local coordinate system of the character so that continuous changes in + * gravity direction are possible while maintaining a sensible control over + * the character. + * + * @param gravity + */ + public void setGravity(Vector3f gravity) { + rigidBody.setGravity(gravity); + localUp.set(gravity).normalizeLocal().negateLocal(); + updateLocalCoordinateSystem(); + } + + /** + * Get the current gravity of the character. + * + * @return + */ + public Vector3f getGravity() { + return rigidBody.getGravity(); + } + + /** + * Get the current gravity of the character. + * + * @param store The vector to store the result in + * @return + */ + public Vector3f getGravity(Vector3f store) { + return rigidBody.getGravity(store); + } + + /** + * Sets how much the physics forces in the local x/z plane should be + * dampened. + * @param physicsDamping The dampening value, 0 = no dampening, 1 = no external force, default = 0.9 + */ + public void setPhysicsDamping(float physicsDamping) { + this.physicsDamping = physicsDamping; + } + + /** + * Gets how much the physics forces in the local x/z plane should be + * dampened. + */ + public float getPhysicsDamping() { + return physicsDamping; + } + + /** + * This actually sets a new collision shape to the character to change the + * height of the capsule. + * + * @param percent + */ + protected void setHeightPercent(float percent) { + scale.setY(percent); + rigidBody.setCollisionShape(getShape()); + } + + /** + * This checks if the character is on the ground by doing a ray test. + */ + protected void checkOnGround() { + TempVars vars = TempVars.get(); + Vector3f location = vars.vect1; + Vector3f rayVector = vars.vect2; + float height = getFinalHeight(); + location.set(localUp).multLocal(height).addLocal(this.location); + rayVector.set(localUp).multLocal(-height - 0.1f).addLocal(location); + List results = space.rayTest(location, rayVector); + vars.release(); + for (PhysicsRayTestResult physicsRayTestResult : results) { + if (!physicsRayTestResult.getCollisionObject().equals(rigidBody)) { + onGround = true; + return; + } + } + onGround = false; + } + + /** + * This checks if the character can go from ducked to unducked state by + * doing a ray test. + */ + protected boolean checkCanUnDuck() { + TempVars vars = TempVars.get(); + Vector3f location = vars.vect1; + Vector3f rayVector = vars.vect2; + location.set(localUp).multLocal(FastMath.ZERO_TOLERANCE).addLocal(this.location); + rayVector.set(localUp).multLocal(height + FastMath.ZERO_TOLERANCE).addLocal(location); + List results = space.rayTest(location, rayVector); + vars.release(); + for (PhysicsRayTestResult physicsRayTestResult : results) { + if (!physicsRayTestResult.getCollisionObject().equals(rigidBody)) { + return false; + } + } + return true; + } + + /** + * Gets a new collision shape based on the current scale parameter. The + * created collisionshape is a capsule collision shape that is attached to a + * compound collision shape with an offset to set the object center at the + * bottom of the capsule. + * + * @return + */ + protected CollisionShape getShape() { + //TODO: cleanup size mess.. + CapsuleCollisionShape capsuleCollisionShape = new CapsuleCollisionShape(getFinalRadius(), (getFinalHeight() - (2 * getFinalRadius()))); + CompoundCollisionShape compoundCollisionShape = new CompoundCollisionShape(); + Vector3f addLocation = new Vector3f(0, (getFinalHeight() / 2.0f), 0); + compoundCollisionShape.addChildShape(capsuleCollisionShape, addLocation); + return compoundCollisionShape; + } + + /** + * Gets the scaled height. + * + * @return + */ + protected float getFinalHeight() { + return height * scale.getY(); + } + + /** + * Gets the scaled radius. + * + * @return + */ + protected float getFinalRadius() { + return radius * scale.getZ(); + } + + /** + * Updates the local coordinate system from the localForward and localUp + * vectors, adapts localForward, sets localForwardRotation quaternion to + * local z-forward rotation. + */ + protected void updateLocalCoordinateSystem() { + //gravity vector has possibly changed, calculate new world forward (UNIT_Z) + calculateNewForward(localForwardRotation, localForward, localUp); + localLeft.set(localUp).crossLocal(localForward); + rigidBody.setPhysicsRotation(localForwardRotation); + updateLocalViewDirection(); + } + + /** + * Updates the local x/z-flattened view direction and the corresponding + * rotation quaternion for the spatial. + */ + protected void updateLocalViewDirection() { + //update local rotation quaternion to use for view rotation + localForwardRotation.multLocal(rotatedViewDirection.set(viewDirection)); + calculateNewForward(rotation, rotatedViewDirection, localUp); + } + + /** + * This method works similar to Camera.lookAt but where lookAt sets the + * priority on the direction, this method sets the priority on the up vector + * so that the result direction vector and rotation is guaranteed to be + * perpendicular to the up vector. + * + * @param rotation The rotation to set the result on or null to create a new + * Quaternion, this will be set to the new "z-forward" rotation if not null + * @param direction The direction to base the new look direction on, will be + * set to the new direction + * @param worldUpVector The up vector to use, the result direction will be + * perpendicular to this + * @return + */ + protected final void calculateNewForward(Quaternion rotation, Vector3f direction, Vector3f worldUpVector) { + if (direction == null) { + return; + } + TempVars vars = TempVars.get(); + Vector3f newLeft = vars.vect1; + Vector3f newLeftNegate = vars.vect2; + + newLeft.set(worldUpVector).crossLocal(direction).normalizeLocal(); + if (newLeft.equals(Vector3f.ZERO)) { + if (direction.x != 0) { + newLeft.set(direction.y, -direction.x, 0f).normalizeLocal(); + } else { + newLeft.set(0f, direction.z, -direction.y).normalizeLocal(); + } + logger.log(Level.INFO, "Zero left for direction {0}, up {1}", new Object[]{direction, worldUpVector}); + } + newLeftNegate.set(newLeft).negateLocal(); + direction.set(worldUpVector).crossLocal(newLeftNegate).normalizeLocal(); + if (direction.equals(Vector3f.ZERO)) { + direction.set(Vector3f.UNIT_Z); + logger.log(Level.INFO, "Zero left for left {0}, up {1}", new Object[]{newLeft, worldUpVector}); + } + if (rotation != null) { + rotation.fromAxes(newLeft, worldUpVector, direction); + } + vars.release(); + } + + /** + * This is implemented from AbstractPhysicsControl and called when the + * spatial is attached for example. + * + * @param vec + */ + @Override + protected void setPhysicsLocation(Vector3f vec) { + rigidBody.setPhysicsLocation(vec); + location.set(vec); + } + + /** + * This is implemented from AbstractPhysicsControl and called when the + * spatial is attached for example. We don't set the actual physics rotation + * but the view rotation here. It might actually be altered by the + * calculateNewForward method. + * + * @param quat + */ + @Override + protected void setPhysicsRotation(Quaternion quat) { + rotation.set(quat); + rotation.multLocal(rotatedViewDirection.set(viewDirection)); + updateLocalViewDirection(); + } + + /** + * This is implemented from AbstractPhysicsControl and called when the + * control is supposed to add all objects to the physics space. + * + * @param space + */ + @Override + protected void addPhysics(PhysicsSpace space) { + space.getGravity(localUp).normalizeLocal().negateLocal(); + updateLocalCoordinateSystem(); + + space.addCollisionObject(rigidBody); + space.addTickListener(this); + } + + /** + * This is implemented from AbstractPhysicsControl and called when the + * control is supposed to remove all objects from the physics space. + * + * @param space + */ + @Override + protected void removePhysics(PhysicsSpace space) { + space.removeCollisionObject(rigidBody); + space.removeTickListener(this); + } + + @Override + protected void createSpatialData(Spatial spat) { + rigidBody.setUserObject(spatial); + } + + @Override + protected void removeSpatialData(Spatial spat) { + rigidBody.setUserObject(null); + } + + public Control cloneForSpatial(Spatial spatial) { + BetterCharacterControl control = new BetterCharacterControl(radius, height, mass); + control.setJumpForce(jumpForce); + return control; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(radius, "radius", 1); + oc.write(height, "height", 1); + oc.write(mass, "mass", 1); + oc.write(jumpForce, "jumpForce", new Vector3f(0, mass * 5, 0)); + oc.write(physicsDamping, "physicsDamping", 0.9f); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + this.radius = in.readFloat("radius", 1); + this.height = in.readFloat("height", 2); + this.mass = in.readFloat("mass", 80); + this.physicsDamping = in.readFloat("physicsDamping", 0.9f); + this.jumpForce.set((Vector3f) in.readSavable("jumpForce", new Vector3f(0, mass * 5, 0))); + rigidBody = new PhysicsRigidBody(getShape(), mass); + jumpForce.set(new Vector3f(0, mass * 5, 0)); + rigidBody.setAngularFactor(0); + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java new file mode 100644 index 000000000..becdc7914 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.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; + +/** + * @deprecated in favor of BetterCharacterControl + * @author normenhansen + */ +@Deprecated +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()); + return control; + } + + public void setSpatial(Spatial spatial) { + this.spatial = spatial; + setUserObject(spatial); + if (spatial == 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) { + } + + 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/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java new file mode 100644 index 000000000..9ed150556 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.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()); + return control; + } + + public void setSpatial(Spatial spatial) { + this.spatial = spatial; + setUserObject(spatial); + if (spatial == 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) { + } + + 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/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java new file mode 100644 index 000000000..13e49213c --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java @@ -0,0 +1,864 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.control; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.animation.SkeletonControl; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionEvent; +import com.jme3.bullet.collision.PhysicsCollisionListener; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.RagdollCollisionListener; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.control.ragdoll.HumanoidRagdollPreset; +import com.jme3.bullet.control.ragdoll.RagdollPreset; +import com.jme3.bullet.control.ragdoll.RagdollUtils; +import com.jme3.bullet.joints.SixDofJoint; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.export.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; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.Control; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This control is still a WIP, use it at your own risk
    To + * use this control you need a model with an AnimControl and a + * SkeletonControl.
    This should be the case if you imported an animated + * model from Ogre or blender.
    Note enabling/disabling the control + * add/removes it from the physic space

    This control creates collision + * shapes for each bones of the skeleton when you call + * spatial.addControl(ragdollControl).

    • The shape is HullCollision shape + * based on the vertices associated with each bone and based on a tweakable + * weight threshold (see setWeightThreshold)
    • If you don't want each + * bone to be a collision shape, you can specify what bone to use by using the + * addBoneName method
      By using this method, bone that are not used to create + * a shape, are "merged" to their parent to create the collision shape.
    • + *

    There are 2 modes for this control :

    • The + * kinematic modes :
      this is the default behavior, this means that + * the collision shapes of the body are able to interact with physics enabled + * objects. in this mode physic shapes follow the moovements of the animated + * skeleton (for example animated by a key framed animation) this mode is + * enabled by calling setKinematicMode();
    • The ragdoll modes + * :
      To enable this behavior, you need to call setRagdollMode() + * method. In this mode the charater is entirely controled by physics, so it + * will fall under the gravity and move if any force is applied to it.
    • + *

    + * + * @author Normen Hansen and Rémy Bouquet (Nehon) + */ +public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener { + + protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName()); + protected List listeners; + protected final Set boneList = new TreeSet(); + protected final Map boneLinks = new HashMap(); + protected final Vector3f modelPosition = new Vector3f(); + protected final Quaternion modelRotation = new Quaternion(); + protected final PhysicsRigidBody baseRigidBody; + protected Spatial targetModel; + protected Skeleton skeleton; + protected RagdollPreset preset = new HumanoidRagdollPreset(); + protected Vector3f initScale; + protected Mode mode = Mode.Kinematic; + protected boolean debug = false; + protected boolean blendedControl = false; + protected float weightThreshold = -1.0f; + protected float blendStart = 0.0f; + protected float blendTime = 1.0f; + protected float eventDispatchImpulseThreshold = 10; + protected float rootMass = 15; + protected float totalMass = 0; + + public static enum Mode { + + Kinematic, + Ragdoll + } + + public class PhysicsBoneLink implements Savable { + + protected PhysicsRigidBody rigidBody; + protected Bone bone; + protected SixDofJoint joint; + protected Quaternion initalWorldRotation; + protected Quaternion startBlendingRot = new Quaternion(); + protected Vector3f startBlendingPos = new Vector3f(); + + public PhysicsBoneLink() { + } + + public Bone getBone() { + return bone; + } + + public PhysicsRigidBody getRigidBody() { + return rigidBody; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(rigidBody, "rigidBody", null); + oc.write(bone, "bone", null); + oc.write(joint, "joint", null); + oc.write(initalWorldRotation, "initalWorldRotation", null); + oc.write(startBlendingRot, "startBlendingRot", new Quaternion()); + oc.write(startBlendingPos, "startBlendingPos", new Vector3f()); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + rigidBody = (PhysicsRigidBody) ic.readSavable("rigidBody", null); + bone = (Bone) ic.readSavable("bone", null); + joint = (SixDofJoint) ic.readSavable("joint", null); + initalWorldRotation = (Quaternion) ic.readSavable("initalWorldRotation", null); + startBlendingRot = (Quaternion) ic.readSavable("startBlendingRot", null); + startBlendingPos = (Vector3f) ic.readSavable("startBlendingPos", null); + } + } + + /** + * contruct a KinematicRagdollControl + */ + public KinematicRagdollControl() { + baseRigidBody = new PhysicsRigidBody(new BoxCollisionShape(Vector3f.UNIT_XYZ.mult(0.1f)), 1); + baseRigidBody.setKinematic(mode == Mode.Kinematic); + } + + public KinematicRagdollControl(float weightThreshold) { + this(); + this.weightThreshold = weightThreshold; + } + + public KinematicRagdollControl(RagdollPreset preset, float weightThreshold) { + this(); + this.preset = preset; + this.weightThreshold = weightThreshold; + } + + public KinematicRagdollControl(RagdollPreset preset) { + this(); + this.preset = preset; + } + + public void update(float tpf) { + if (!enabled) { + return; + } + + //if the ragdoll has the control of the skeleton, we update each bone with its position in physic world space. + if (mode == mode.Ragdoll && targetModel.getLocalTranslation().equals(modelPosition)) { + ragDollUpdate(tpf); + } else { + kinematicUpdate(tpf); + } + } + + protected void ragDollUpdate(float tpf) { + TempVars vars = TempVars.get(); + Quaternion tmpRot1 = vars.quat1; + Quaternion tmpRot2 = vars.quat2; + + for (PhysicsBoneLink link : boneLinks.values()) { + + Vector3f position = vars.vect1; + + //retrieving bone position in physic world space + Vector3f p = link.rigidBody.getMotionState().getWorldLocation(); + //transforming this position with inverse transforms of the model + targetModel.getWorldTransform().transformInverseVector(p, position); + + //retrieving bone rotation in physic world space + Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat(); + + //multiplying this rotation by the initialWorld rotation of the bone, + //then transforming it with the inverse world rotation of the model + tmpRot1.set(q).multLocal(link.initalWorldRotation); + tmpRot2.set(targetModel.getWorldRotation()).inverseLocal().mult(tmpRot1, tmpRot1); + tmpRot1.normalizeLocal(); + + //if the bone is the root bone, we apply the physic's transform to the model, so its position and rotation are correctly updated + if (link.bone.getParent() == null) { + + //offsetting the physic's position/rotation by the root bone inverse model space position/rotaion + modelPosition.set(p).subtractLocal(link.bone.getWorldBindPosition()); + targetModel.getParent().getWorldTransform().transformInverseVector(modelPosition, modelPosition); + modelRotation.set(q).multLocal(tmpRot2.set(link.bone.getWorldBindRotation()).inverseLocal()); + + + //applying transforms to the model + targetModel.setLocalTranslation(modelPosition); + + targetModel.setLocalRotation(modelRotation); + + //Applying computed transforms to the bone + link.bone.setUserTransformsWorld(position, tmpRot1); + + } else { + //if boneList is empty, this means that every bone in the ragdoll has a collision shape, + //so we just update the bone position + if (boneList.isEmpty()) { + link.bone.setUserTransformsWorld(position, tmpRot1); + } else { + //boneList is not empty, this means some bones of the skeleton might not be associated with a collision shape. + //So we update them recusively + RagdollUtils.setTransform(link.bone, position, tmpRot1, false, boneList); + } + } + } + vars.release(); + } + + protected void kinematicUpdate(float tpf) { + //the ragdoll does not have the controll, so the keyframed animation updates the physic position of the physic bonces + TempVars vars = TempVars.get(); + Quaternion tmpRot1 = vars.quat1; + Quaternion tmpRot2 = vars.quat2; + Vector3f position = vars.vect1; + for (PhysicsBoneLink link : boneLinks.values()) { + //if blended control this means, keyframed animation is updating the skeleton, + //but to allow smooth transition, we blend this transformation with the saved position of the ragdoll + if (blendedControl) { + Vector3f position2 = vars.vect2; + //initializing tmp vars with the start position/rotation of the ragdoll + position.set(link.startBlendingPos); + tmpRot1.set(link.startBlendingRot); + + //interpolating between ragdoll position/rotation and keyframed position/rotation + tmpRot2.set(tmpRot1).nlerp(link.bone.getModelSpaceRotation(), blendStart / blendTime); + position2.set(position).interpolateLocal(link.bone.getModelSpacePosition(), blendStart / blendTime); + tmpRot1.set(tmpRot2); + position.set(position2); + + //updating bones transforms + if (boneList.isEmpty()) { + //we ensure we have the control to update the bone + link.bone.setUserControl(true); + link.bone.setUserTransformsWorld(position, tmpRot1); + //we give control back to the key framed animation. + link.bone.setUserControl(false); + } else { + RagdollUtils.setTransform(link.bone, position, tmpRot1, true, boneList); + } + + } + //setting skeleton transforms to the ragdoll + matchPhysicObjectToBone(link, position, tmpRot1); + modelPosition.set(targetModel.getLocalTranslation()); + } + + //time control for blending + if (blendedControl) { + blendStart += tpf; + if (blendStart > blendTime) { + blendedControl = false; + } + } + vars.release(); + } + + /** + * Set the transforms of a rigidBody to match the transforms of a bone. this + * is used to make the ragdoll follow the skeleton motion while in Kinematic + * mode + * + * @param link the link containing the bone and the rigidBody + * @param position just a temp vector for position + * @param tmpRot1 just a temp quaternion for rotation + */ + protected void matchPhysicObjectToBone(PhysicsBoneLink link, Vector3f position, Quaternion tmpRot1) { + //computing position from rotation and scale + targetModel.getWorldTransform().transformVector(link.bone.getModelSpacePosition(), position); + + //computing rotation + tmpRot1.set(link.bone.getModelSpaceRotation()).multLocal(link.bone.getWorldBindInverseRotation()); + targetModel.getWorldRotation().mult(tmpRot1, tmpRot1); + tmpRot1.normalizeLocal(); + + //updating physic location/rotation of the physic bone + link.rigidBody.setPhysicsLocation(position); + link.rigidBody.setPhysicsRotation(tmpRot1); + + } + + /** + * rebuild the ragdoll this is useful if you applied scale on the ragdoll + * after it's been initialized, same as reattaching. + */ + public void reBuild() { + if (spatial == null) { + return; + } + removeSpatialData(spatial); + createSpatialData(spatial); + } + + @Override + protected void createSpatialData(Spatial model) { + targetModel = model; + Node parent = model.getParent(); + + + Vector3f initPosition = model.getLocalTranslation().clone(); + Quaternion initRotation = model.getLocalRotation().clone(); + initScale = model.getLocalScale().clone(); + + model.removeFromParent(); + model.setLocalTranslation(Vector3f.ZERO); + model.setLocalRotation(Quaternion.IDENTITY); + model.setLocalScale(1); + //HACK ALERT change this + //I remove the skeletonControl and readd it to the spatial to make sure it's after the ragdollControl in the stack + //Find a proper way to order the controls. + SkeletonControl sc = model.getControl(SkeletonControl.class); + model.removeControl(sc); + model.addControl(sc); + + // put into bind pose and compute bone transforms in model space + // maybe dont reset to ragdoll out of animations? + scanSpatial(model); + + + if (parent != null) { + parent.attachChild(model); + + } + model.setLocalTranslation(initPosition); + model.setLocalRotation(initRotation); + model.setLocalScale(initScale); + + if (added) { + addPhysics(space); + } + logger.log(Level.FINE, "Created physics ragdoll for skeleton {0}", skeleton); + } + + @Override + protected void removeSpatialData(Spatial spat) { + if (added) { + removePhysics(space); + } + boneLinks.clear(); + } + + /** + * Add a bone name to this control Using this method you can specify which + * bones of the skeleton will be used to build the collision shapes. + * + * @param name + */ + public void addBoneName(String name) { + boneList.add(name); + } + + protected void scanSpatial(Spatial model) { + AnimControl animControl = model.getControl(AnimControl.class); + Map> pointsMap = null; + if (weightThreshold == -1.0f) { + pointsMap = RagdollUtils.buildPointMap(model); + } + + skeleton = animControl.getSkeleton(); + skeleton.resetAndUpdate(); + for (int i = 0; i < skeleton.getRoots().length; i++) { + Bone childBone = skeleton.getRoots()[i]; + if (childBone.getParent() == null) { + logger.log(Level.FINE, "Found root bone in skeleton {0}", skeleton); + boneRecursion(model, childBone, baseRigidBody, 1, pointsMap); + } + } + } + + protected void boneRecursion(Spatial model, Bone bone, PhysicsRigidBody parent, int reccount, Map> pointsMap) { + PhysicsRigidBody parentShape = parent; + if (boneList.isEmpty() || boneList.contains(bone.getName())) { + + PhysicsBoneLink link = new PhysicsBoneLink(); + link.bone = bone; + + //creating the collision shape + HullCollisionShape shape = null; + if (pointsMap != null) { + //build a shape for the bone, using the vertices that are most influenced by this bone + shape = RagdollUtils.makeShapeFromPointMap(pointsMap, RagdollUtils.getBoneIndices(link.bone, skeleton, boneList), initScale, link.bone.getModelSpacePosition()); + } else { + //build a shape for the bone, using the vertices associated with this bone with a weight above the threshold + shape = RagdollUtils.makeShapeFromVerticeWeights(model, RagdollUtils.getBoneIndices(link.bone, skeleton, boneList), initScale, link.bone.getModelSpacePosition(), weightThreshold); + } + + PhysicsRigidBody shapeNode = new PhysicsRigidBody(shape, rootMass / (float) reccount); + + shapeNode.setKinematic(mode == Mode.Kinematic); + totalMass += rootMass / (float) reccount; + + link.rigidBody = shapeNode; + link.initalWorldRotation = bone.getModelSpaceRotation().clone(); + + if (parent != null) { + //get joint position for parent + Vector3f posToParent = new Vector3f(); + if (bone.getParent() != null) { + bone.getModelSpacePosition().subtract(bone.getParent().getModelSpacePosition(), posToParent).multLocal(initScale); + } + + SixDofJoint joint = new SixDofJoint(parent, shapeNode, posToParent, new Vector3f(0, 0, 0f), true); + preset.setupJointForBone(bone.getName(), joint); + + link.joint = joint; + joint.setCollisionBetweenLinkedBodys(false); + } + boneLinks.put(bone.getName(), link); + shapeNode.setUserObject(link); + parentShape = shapeNode; + } + + for (Iterator it = bone.getChildren().iterator(); it.hasNext();) { + Bone childBone = it.next(); + boneRecursion(model, childBone, parentShape, reccount + 1, pointsMap); + } + } + + /** + * Set the joint limits for the joint between the given bone and its parent. + * This method can't work before attaching the control to a spatial + * + * @param boneName the name of the bone + * @param maxX the maximum rotation on the x axis (in radians) + * @param minX the minimum rotation on the x axis (in radians) + * @param maxY the maximum rotation on the y axis (in radians) + * @param minY the minimum rotation on the z axis (in radians) + * @param maxZ the maximum rotation on the z axis (in radians) + * @param minZ the minimum rotation on the z axis (in radians) + */ + public void setJointLimit(String boneName, float maxX, float minX, float maxY, float minY, float maxZ, float minZ) { + PhysicsBoneLink link = boneLinks.get(boneName); + if (link != null) { + RagdollUtils.setJointLimit(link.joint, maxX, minX, maxY, minY, maxZ, minZ); + } else { + logger.log(Level.WARNING, "Not joint was found for bone {0}. make sure you call spatial.addControl(ragdoll) before setting joints limit", boneName); + } + } + + /** + * Return the joint between the given bone and its parent. This return null + * if it's called before attaching the control to a spatial + * + * @param boneName the name of the bone + * @return the joint between the given bone and its parent + */ + public SixDofJoint getJoint(String boneName) { + PhysicsBoneLink link = boneLinks.get(boneName); + if (link != null) { + return link.joint; + } else { + logger.log(Level.WARNING, "Not joint was found for bone {0}. make sure you call spatial.addControl(ragdoll) before setting joints limit", boneName); + return null; + } + } + + @Override + protected void setPhysicsLocation(Vector3f vec) { + if (baseRigidBody != null) { + baseRigidBody.setPhysicsLocation(vec); + } + } + + @Override + protected void setPhysicsRotation(Quaternion quat) { + if (baseRigidBody != null) { + baseRigidBody.setPhysicsRotation(quat); + } + } + + @Override + protected void addPhysics(PhysicsSpace space) { + if (baseRigidBody != null) { + space.add(baseRigidBody); + } + for (Iterator it = boneLinks.values().iterator(); it.hasNext();) { + PhysicsBoneLink physicsBoneLink = it.next(); + if (physicsBoneLink.rigidBody != null) { + space.add(physicsBoneLink.rigidBody); + if (physicsBoneLink.joint != null) { + space.add(physicsBoneLink.joint); + + } + } + } + space.addCollisionListener(this); + } + + @Override + protected void removePhysics(PhysicsSpace space) { + if (baseRigidBody != null) { + space.remove(baseRigidBody); + } + for (Iterator it = boneLinks.values().iterator(); it.hasNext();) { + PhysicsBoneLink physicsBoneLink = it.next(); + if (physicsBoneLink.joint != null) { + space.remove(physicsBoneLink.joint); + if (physicsBoneLink.rigidBody != null) { + space.remove(physicsBoneLink.rigidBody); + } + } + } + space.removeCollisionListener(this); + } + + /** + * For internal use only callback for collisionevent + * + * @param event + */ + public void collision(PhysicsCollisionEvent event) { + PhysicsCollisionObject objA = event.getObjectA(); + PhysicsCollisionObject objB = event.getObjectB(); + + //excluding collisions that involve 2 parts of the ragdoll + if (event.getNodeA() == null && event.getNodeB() == null) { + return; + } + + //discarding low impulse collision + if (event.getAppliedImpulse() < eventDispatchImpulseThreshold) { + return; + } + + boolean hit = false; + Bone hitBone = null; + PhysicsCollisionObject hitObject = null; + + //Computing which bone has been hit + if (objA.getUserObject() instanceof PhysicsBoneLink) { + PhysicsBoneLink link = (PhysicsBoneLink) objA.getUserObject(); + if (link != null) { + hit = true; + hitBone = link.bone; + hitObject = objB; + } + } + + if (objB.getUserObject() instanceof PhysicsBoneLink) { + PhysicsBoneLink link = (PhysicsBoneLink) objB.getUserObject(); + if (link != null) { + hit = true; + hitBone = link.bone; + hitObject = objA; + + } + } + + //dispatching the event if the ragdoll has been hit + if (hit && listeners != null) { + for (RagdollCollisionListener listener : listeners) { + listener.collide(hitBone, hitObject, event); + } + } + + } + + /** + * Enable or disable the ragdoll behaviour. if ragdollEnabled is true, the + * character motion will only be powerd by physics else, the characted will + * be animated by the keyframe animation, but will be able to physically + * interact with its physic environnement + * + * @param ragdollEnabled + */ + protected void setMode(Mode mode) { + this.mode = mode; + AnimControl animControl = targetModel.getControl(AnimControl.class); + animControl.setEnabled(mode == Mode.Kinematic); + + baseRigidBody.setKinematic(mode == Mode.Kinematic); + TempVars vars = TempVars.get(); + + for (PhysicsBoneLink link : boneLinks.values()) { + link.rigidBody.setKinematic(mode == Mode.Kinematic); + if (mode == Mode.Ragdoll) { + Quaternion tmpRot1 = vars.quat1; + Vector3f position = vars.vect1; + //making sure that the ragdoll is at the correct place. + matchPhysicObjectToBone(link, position, tmpRot1); + } + + } + vars.release(); + + for (Bone bone : skeleton.getRoots()) { + RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll); + } + } + + /** + * Smoothly blend from Ragdoll mode to Kinematic mode This is useful to + * blend ragdoll actual position to a keyframe animation for example + * + * @param blendTime the blending time between ragdoll to anim. + */ + public void blendToKinematicMode(float blendTime) { + if (mode == Mode.Kinematic) { + return; + } + blendedControl = true; + this.blendTime = blendTime; + mode = Mode.Kinematic; + AnimControl animControl = targetModel.getControl(AnimControl.class); + animControl.setEnabled(true); + + + TempVars vars = TempVars.get(); + for (PhysicsBoneLink link : boneLinks.values()) { + + Vector3f p = link.rigidBody.getMotionState().getWorldLocation(); + Vector3f position = vars.vect1; + + targetModel.getWorldTransform().transformInverseVector(p, position); + + Quaternion q = link.rigidBody.getMotionState().getWorldRotationQuat(); + Quaternion q2 = vars.quat1; + Quaternion q3 = vars.quat2; + + q2.set(q).multLocal(link.initalWorldRotation).normalizeLocal(); + q3.set(targetModel.getWorldRotation()).inverseLocal().mult(q2, q2); + q2.normalizeLocal(); + link.startBlendingPos.set(position); + link.startBlendingRot.set(q2); + link.rigidBody.setKinematic(true); + } + vars.release(); + + for (Bone bone : skeleton.getRoots()) { + RagdollUtils.setUserControl(bone, false); + } + + blendStart = 0; + } + + /** + * Set the control into Kinematic mode In theis mode, the collision shapes + * follow the movements of the skeleton, and can interact with physical + * environement + */ + public void setKinematicMode() { + if (mode != Mode.Kinematic) { + setMode(Mode.Kinematic); + } + } + + /** + * Sets the control into Ragdoll mode The skeleton is entirely controlled by + * physics. + */ + public void setRagdollMode() { + if (mode != Mode.Ragdoll) { + setMode(Mode.Ragdoll); + } + } + + /** + * retruns the mode of this control + * + * @return + */ + public Mode getMode() { + return mode; + } + + /** + * add a + * + * @param listener + */ + public void addCollisionListener(RagdollCollisionListener listener) { + if (listeners == null) { + listeners = new ArrayList(); + } + listeners.add(listener); + } + + public void setRootMass(float rootMass) { + this.rootMass = rootMass; + } + + public float getTotalMass() { + return totalMass; + } + + public float getWeightThreshold() { + return weightThreshold; + } + + public void setWeightThreshold(float weightThreshold) { + this.weightThreshold = weightThreshold; + } + + public float getEventDispatchImpulseThreshold() { + return eventDispatchImpulseThreshold; + } + + public void setEventDispatchImpulseThreshold(float eventDispatchImpulseThreshold) { + this.eventDispatchImpulseThreshold = eventDispatchImpulseThreshold; + } + + /** + * Set the CcdMotionThreshold of all the bone's rigidBodies of the ragdoll + * + * @see PhysicsRigidBody#setCcdMotionThreshold(float) + * @param value + */ + public void setCcdMotionThreshold(float value) { + for (PhysicsBoneLink link : boneLinks.values()) { + link.rigidBody.setCcdMotionThreshold(value); + } + } + + /** + * Set the CcdSweptSphereRadius of all the bone's rigidBodies of the ragdoll + * + * @see PhysicsRigidBody#setCcdSweptSphereRadius(float) + * @param value + */ + public void setCcdSweptSphereRadius(float value) { + for (PhysicsBoneLink link : boneLinks.values()) { + link.rigidBody.setCcdSweptSphereRadius(value); + } + } + + /** + * return the rigidBody associated to the given bone + * + * @param boneName the name of the bone + * @return the associated rigidBody. + */ + public PhysicsRigidBody getBoneRigidBody(String boneName) { + PhysicsBoneLink link = boneLinks.get(boneName); + if (link != null) { + return link.rigidBody; + } + return null; + } + + /** + * For internal use only specific render for the ragdoll(if debugging) + * + * @param rm + * @param vp + */ + @Override + public void render(RenderManager rm, ViewPort vp) { + } + + public Control cloneForSpatial(Spatial spatial) { + KinematicRagdollControl control = new KinematicRagdollControl(preset, weightThreshold); + control.setMode(mode); + control.setRootMass(rootMass); + control.setWeightThreshold(weightThreshold); + control.setApplyPhysicsLocal(applyLocal); + return control; + } + + /** + * serialize this control + * + * @param ex + * @throws IOException + */ + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(boneList.toArray(new String[boneList.size()]), "boneList", new String[0]); + oc.write(boneLinks.values().toArray(new PhysicsBoneLink[boneLinks.size()]), "boneLinks", new PhysicsBoneLink[0]); + oc.write(modelPosition, "modelPosition", new Vector3f()); + oc.write(modelRotation, "modelRotation", new Quaternion()); + oc.write(targetModel, "targetModel", null); + oc.write(skeleton, "skeleton", null); +// oc.write(preset, "preset", null);//TODO + oc.write(initScale, "initScale", null); + oc.write(mode, "mode", null); + oc.write(blendedControl, "blendedControl", false); + oc.write(weightThreshold, "weightThreshold", -1.0f); + oc.write(blendStart, "blendStart", 0.0f); + oc.write(blendTime, "blendTime", 1.0f); + oc.write(eventDispatchImpulseThreshold, "eventDispatchImpulseThreshold", 10); + oc.write(rootMass, "rootMass", 15); + oc.write(totalMass, "totalMass", 0); + } + + /** + * de-serialize this control + * + * @param im + * @throws IOException + */ + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + String[] loadedBoneList = ic.readStringArray("boneList", new String[0]); + boneList.addAll(Arrays.asList(loadedBoneList)); + PhysicsBoneLink[] loadedBoneLinks = (PhysicsBoneLink[]) ic.readSavableArray("boneList", new PhysicsBoneLink[0]); + for (PhysicsBoneLink physicsBoneLink : loadedBoneLinks) { + boneLinks.put(physicsBoneLink.bone.getName(), physicsBoneLink); + } + modelPosition.set((Vector3f) ic.readSavable("modelPosition", new Vector3f())); + modelRotation.set((Quaternion) ic.readSavable("modelRotation", new Quaternion())); + targetModel = (Spatial) ic.readSavable("targetModel", null); + skeleton = (Skeleton) ic.readSavable("skeleton", null); +// preset //TODO + initScale = (Vector3f) ic.readSavable("initScale", null); + mode = ic.readEnum("mode", Mode.class, Mode.Kinematic); + blendedControl = ic.readBoolean("blendedControl", false); + weightThreshold = ic.readFloat("weightThreshold", -1.0f); + blendStart = ic.readFloat("blendStart", 0.0f); + blendTime = ic.readFloat("blendTime", 1.0f); + eventDispatchImpulseThreshold = ic.readFloat("eventDispatchImpulseThreshold", 10); + rootMass = ic.readFloat("rootMass", 15); + totalMass = ic.readFloat("totalMass", 0); + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/PhysicsControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/PhysicsControl.java new file mode 100644 index 000000000..693c15590 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/PhysicsControl.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.control; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.scene.control.Control; + +/** + * + * @author normenhansen + */ +public interface PhysicsControl extends Control { + + /** + * Only used internally, do not call. + * @param space + */ + public void setPhysicsSpace(PhysicsSpace space); + + public PhysicsSpace getPhysicsSpace(); + + /** + * The physics object is removed from the physics space when the control + * is disabled. When the control is enabled again the physics object is + * moved to the current location of the spatial and then added to the physics + * space. This allows disabling/enabling physics to move the spatial freely. + * @param state + */ + public void setEnabled(boolean state); + + /** + * Returns the current enabled state of the physics control + * @return current enabled state + */ + public boolean isEnabled(); +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java new file mode 100644 index 000000000..c4c0f3995 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.control; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.Control; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import java.io.IOException; + +/** + * + * @author normenhansen + */ +public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl { + + protected Spatial spatial; + protected boolean enabled = true; + protected boolean added = false; + protected PhysicsSpace space = null; + protected boolean kinematicSpatial = true; + + public RigidBodyControl() { + } + + /** + * When using this constructor, the CollisionShape for the RigidBody is generated + * automatically when the Control is added to a Spatial. + * @param mass When not 0, a HullCollisionShape is generated, otherwise a MeshCollisionShape is used. For geometries with box or sphere meshes the proper box or sphere collision shape is used. + */ + public RigidBodyControl(float mass) { + this.mass = mass; + } + + /** + * Creates a new PhysicsNode with the supplied collision shape and mass 1 + * @param shape + */ + public RigidBodyControl(CollisionShape shape) { + super(shape); + } + + public RigidBodyControl(CollisionShape shape, float mass) { + super(shape, mass); + } + + public Control cloneForSpatial(Spatial spatial) { + RigidBodyControl control = new RigidBodyControl(collisionShape, mass); + control.setAngularFactor(getAngularFactor()); + control.setAngularSleepingThreshold(getAngularSleepingThreshold()); + control.setCcdMotionThreshold(getCcdMotionThreshold()); + control.setCcdSweptSphereRadius(getCcdSweptSphereRadius()); + control.setCollideWithGroups(getCollideWithGroups()); + control.setCollisionGroup(getCollisionGroup()); + control.setDamping(getLinearDamping(), getAngularDamping()); + control.setFriction(getFriction()); + control.setGravity(getGravity()); + control.setKinematic(isKinematic()); + control.setKinematicSpatial(isKinematicSpatial()); + control.setLinearSleepingThreshold(getLinearSleepingThreshold()); + control.setPhysicsLocation(getPhysicsLocation(null)); + control.setPhysicsRotation(getPhysicsRotationMatrix(null)); + control.setRestitution(getRestitution()); + + if (mass > 0) { + control.setAngularVelocity(getAngularVelocity()); + control.setLinearVelocity(getLinearVelocity()); + } + control.setApplyPhysicsLocal(isApplyPhysicsLocal()); + return control; + } + + public void setSpatial(Spatial spatial) { + this.spatial = spatial; + setUserObject(spatial); + if (spatial == null) { + return; + } + if (collisionShape == null) { + createCollisionShape(); + rebuildRigidBody(); + } + setPhysicsLocation(getSpatialTranslation()); + setPhysicsRotation(getSpatialRotation()); + } + + protected void createCollisionShape() { + if (spatial == null) { + return; + } + if (spatial instanceof Geometry) { + Geometry geom = (Geometry) spatial; + Mesh mesh = geom.getMesh(); + if (mesh instanceof Sphere) { + collisionShape = new SphereCollisionShape(((Sphere) mesh).getRadius()); + return; + } else if (mesh instanceof Box) { + collisionShape = new BoxCollisionShape(new Vector3f(((Box) mesh).getXExtent(), ((Box) mesh).getYExtent(), ((Box) mesh).getZExtent())); + return; + } + } + if (mass > 0) { + collisionShape = CollisionShapeFactory.createDynamicMeshShape(spatial); + } else { + collisionShape = CollisionShapeFactory.createMeshShape(spatial); + } + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if (space != null) { + if (enabled && !added) { + if (spatial != null) { + setPhysicsLocation(getSpatialTranslation()); + setPhysicsRotation(getSpatialRotation()); + } + space.addCollisionObject(this); + added = true; + } else if (!enabled && added) { + space.removeCollisionObject(this); + added = false; + } + } + } + + public boolean isEnabled() { + return enabled; + } + + /** + * Checks if this control is in kinematic spatial mode. + * @return true if the spatial location is applied to this kinematic rigidbody + */ + public boolean isKinematicSpatial() { + return kinematicSpatial; + } + + /** + * Sets this control to kinematic spatial mode so that the spatials transform will + * be applied to the rigidbody in kinematic mode, defaults to true. + * @param kinematicSpatial + */ + public void setKinematicSpatial(boolean kinematicSpatial) { + this.kinematicSpatial = kinematicSpatial; + } + + public boolean isApplyPhysicsLocal() { + return motionState.isApplyPhysicsLocal(); + } + + /** + * When set to true, the physics coordinates will be applied to the local + * translation of the Spatial instead of the world traslation. + * @param applyPhysicsLocal + */ + public void setApplyPhysicsLocal(boolean applyPhysicsLocal) { + motionState.setApplyPhysicsLocal(applyPhysicsLocal); + } + + private Vector3f getSpatialTranslation(){ + if(motionState.isApplyPhysicsLocal()){ + return spatial.getLocalTranslation(); + } + return spatial.getWorldTranslation(); + } + + private Quaternion getSpatialRotation(){ + if(motionState.isApplyPhysicsLocal()){ + return spatial.getLocalRotation(); + } + return spatial.getWorldRotation(); + } + + public void update(float tpf) { + if (enabled && spatial != null) { + if (isKinematic() && kinematicSpatial) { + super.setPhysicsLocation(getSpatialTranslation()); + super.setPhysicsRotation(getSpatialRotation()); + } else { + getMotionState().applyTransform(spatial); + } + } + } + + public void render(RenderManager rm, ViewPort vp) { + } + + 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/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java new file mode 100644 index 000000000..0dc033331 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.control; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.bullet.objects.VehicleWheel; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.Control; +import java.io.IOException; +import java.util.Iterator; + +/** + * + * @author normenhansen + */ +public class VehicleControl extends PhysicsVehicle implements PhysicsControl { + + protected Spatial spatial; + protected boolean enabled = true; + protected PhysicsSpace space = null; + protected boolean added = false; + + public VehicleControl() { + } + + /** + * Creates a new PhysicsNode with the supplied collision shape + * @param shape + */ + public VehicleControl(CollisionShape shape) { + super(shape); + } + + public VehicleControl(CollisionShape shape, float mass) { + super(shape, mass); + } + + public boolean isApplyPhysicsLocal() { + return motionState.isApplyPhysicsLocal(); + } + + /** + * When set to true, the physics coordinates will be applied to the local + * translation of the Spatial + * @param applyPhysicsLocal + */ + public void setApplyPhysicsLocal(boolean applyPhysicsLocal) { + motionState.setApplyPhysicsLocal(applyPhysicsLocal); + for (Iterator 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()); + return control; + } + + public void setSpatial(Spatial spatial) { + this.spatial = spatial; + setUserObject(spatial); + if (spatial == 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(); + } + } + + public void render(RenderManager rm, ViewPort 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/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/HumanoidRagdollPreset.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/HumanoidRagdollPreset.java new file mode 100644 index 000000000..d090cb594 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/HumanoidRagdollPreset.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.control.ragdoll; + +import com.jme3.math.FastMath; + +/** + * + * @author Nehon + */ +public class HumanoidRagdollPreset extends RagdollPreset { + + @Override + protected void initBoneMap() { + boneMap.put("head", new JointPreset(FastMath.QUARTER_PI, -FastMath.QUARTER_PI, FastMath.QUARTER_PI, -FastMath.QUARTER_PI, FastMath.QUARTER_PI, -FastMath.QUARTER_PI)); + + boneMap.put("torso", new JointPreset(FastMath.QUARTER_PI, -FastMath.QUARTER_PI, 0, 0, FastMath.QUARTER_PI, -FastMath.QUARTER_PI)); + + boneMap.put("upperleg", new JointPreset(FastMath.PI, -FastMath.QUARTER_PI, FastMath.QUARTER_PI/2, -FastMath.QUARTER_PI/2, FastMath.QUARTER_PI, -FastMath.QUARTER_PI)); + + boneMap.put("lowerleg", new JointPreset(0, -FastMath.PI, 0, 0, 0, 0)); + + boneMap.put("foot", new JointPreset(0, -FastMath.QUARTER_PI, FastMath.QUARTER_PI, -FastMath.QUARTER_PI, FastMath.QUARTER_PI, -FastMath.QUARTER_PI)); + + boneMap.put("upperarm", new JointPreset(FastMath.HALF_PI, -FastMath.QUARTER_PI, 0, 0, FastMath.HALF_PI, -FastMath.QUARTER_PI)); + + boneMap.put("lowerarm", new JointPreset(FastMath.HALF_PI, 0, 0, 0, 0, 0)); + + boneMap.put("hand", new JointPreset(FastMath.QUARTER_PI, -FastMath.QUARTER_PI, FastMath.QUARTER_PI, -FastMath.QUARTER_PI, FastMath.QUARTER_PI, -FastMath.QUARTER_PI)); + + } + + @Override + protected void initLexicon() { + LexiconEntry entry = new LexiconEntry(); + entry.addSynonym("head", 100); + lexicon.put("head", entry); + + entry = new LexiconEntry(); + entry.addSynonym("torso", 100); + entry.addSynonym("chest", 100); + entry.addSynonym("spine", 45); + entry.addSynonym("high", 25); + lexicon.put("torso", entry); + + entry = new LexiconEntry(); + entry.addSynonym("upperleg", 100); + entry.addSynonym("thigh", 100); + entry.addSynonym("hip", 75); + entry.addSynonym("leg", 40); + entry.addSynonym("high", 10); + entry.addSynonym("up", 15); + entry.addSynonym("upper", 15); + lexicon.put("upperleg", entry); + + entry = new LexiconEntry(); + entry.addSynonym("lowerleg", 100); + entry.addSynonym("calf", 100); + entry.addSynonym("knee", 75); + entry.addSynonym("leg", 50); + entry.addSynonym("low", 10); + entry.addSynonym("lower", 10); + lexicon.put("lowerleg", entry); + + entry = new LexiconEntry(); + entry.addSynonym("foot", 100); + entry.addSynonym("ankle", 75); + lexicon.put("foot", entry); + + + entry = new LexiconEntry(); + entry.addSynonym("upperarm", 100); + entry.addSynonym("humerus", 100); + entry.addSynonym("shoulder", 50); + entry.addSynonym("arm", 40); + entry.addSynonym("high", 10); + entry.addSynonym("up", 15); + entry.addSynonym("upper", 15); + lexicon.put("upperarm", entry); + + entry = new LexiconEntry(); + entry.addSynonym("lowerarm", 100); + entry.addSynonym("ulna", 100); + entry.addSynonym("elbow", 75); + entry.addSynonym("arm", 50); + entry.addSynonym("low", 10); + entry.addSynonym("lower", 10); + lexicon.put("lowerarm", entry); + + entry = new LexiconEntry(); + entry.addSynonym("hand", 100); + entry.addSynonym("fist", 100); + entry.addSynonym("wrist", 75); + lexicon.put("hand", entry); + + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollPreset.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollPreset.java new file mode 100644 index 000000000..ab0b3ef1a --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollPreset.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.control.ragdoll; + +import com.jme3.bullet.joints.SixDofJoint; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author Nehon + */ +public abstract class RagdollPreset { + + protected static final Logger logger = Logger.getLogger(RagdollPreset.class.getName()); + protected Map boneMap = new HashMap(); + protected Map lexicon = new HashMap(); + + protected abstract void initBoneMap(); + + protected abstract void initLexicon(); + + public void setupJointForBone(String boneName, SixDofJoint joint) { + + if (boneMap.isEmpty()) { + initBoneMap(); + } + if (lexicon.isEmpty()) { + initLexicon(); + } + String resultName = ""; + int resultScore = 0; + + for (String key : lexicon.keySet()) { + + int score = lexicon.get(key).getScore(boneName); + if (score > resultScore) { + resultScore = score; + resultName = key; + } + + } + + JointPreset preset = boneMap.get(resultName); + + if (preset != null && resultScore >= 50) { + logger.log(Level.FINE, "Found matching joint for bone {0} : {1} with score {2}", new Object[]{boneName, resultName, resultScore}); + preset.setupJoint(joint); + } else { + logger.log(Level.FINE, "No joint match found for bone {0}", boneName); + if (resultScore > 0) { + logger.log(Level.FINE, "Best match found is {0} with score {1}", new Object[]{resultName, resultScore}); + } + new JointPreset().setupJoint(joint); + } + + } + + protected class JointPreset { + + private float maxX, minX, maxY, minY, maxZ, minZ; + + public JointPreset() { + } + + public JointPreset(float maxX, float minX, float maxY, float minY, float maxZ, float minZ) { + this.maxX = maxX; + this.minX = minX; + this.maxY = maxY; + this.minY = minY; + this.maxZ = maxZ; + this.minZ = minZ; + } + + public void setupJoint(SixDofJoint joint) { + joint.getRotationalLimitMotor(0).setHiLimit(maxX); + joint.getRotationalLimitMotor(0).setLoLimit(minX); + joint.getRotationalLimitMotor(1).setHiLimit(maxY); + joint.getRotationalLimitMotor(1).setLoLimit(minY); + joint.getRotationalLimitMotor(2).setHiLimit(maxZ); + joint.getRotationalLimitMotor(2).setLoLimit(minZ); + } + } + + protected class LexiconEntry extends HashMap { + + public void addSynonym(String word, int score) { + put(word.toLowerCase(), score); + } + + public int getScore(String word) { + int score = 0; + String searchWord = word.toLowerCase(); + for (String key : this.keySet()) { + if (searchWord.indexOf(key) >= 0) { + score += get(key); + } + } + return score; + } + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java new file mode 100644 index 000000000..27470c258 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.control.ragdoll; + +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.joints.SixDofJoint; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer.Type; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.*; + +/** + * + * @author Nehon + */ +public class RagdollUtils { + + public static void setJointLimit(SixDofJoint joint, float maxX, float minX, float maxY, float minY, float maxZ, float minZ) { + + joint.getRotationalLimitMotor(0).setHiLimit(maxX); + joint.getRotationalLimitMotor(0).setLoLimit(minX); + joint.getRotationalLimitMotor(1).setHiLimit(maxY); + joint.getRotationalLimitMotor(1).setLoLimit(minY); + joint.getRotationalLimitMotor(2).setHiLimit(maxZ); + joint.getRotationalLimitMotor(2).setLoLimit(minZ); + } + + public static Map> buildPointMap(Spatial model) { + + + Map> map = new HashMap>(); + if (model instanceof Geometry) { + Geometry g = (Geometry) model; + buildPointMapForMesh(g.getMesh(), map); + } else if (model instanceof Node) { + Node node = (Node) model; + for (Spatial s : node.getChildren()) { + if (s instanceof Geometry) { + Geometry g = (Geometry) s; + buildPointMapForMesh(g.getMesh(), map); + } + } + } + return map; + } + + private static Map> buildPointMapForMesh(Mesh mesh, Map> map) { + + FloatBuffer vertices = mesh.getFloatBuffer(Type.Position); + ByteBuffer boneIndices = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData(); + FloatBuffer boneWeight = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); + + vertices.rewind(); + boneIndices.rewind(); + boneWeight.rewind(); + + int vertexComponents = mesh.getVertexCount() * 3; + int k, start, index; + float maxWeight = 0; + + for (int i = 0; i < vertexComponents; i += 3) { + + + start = i / 3 * 4; + index = 0; + maxWeight = -1; + for (k = start; k < start + 4; k++) { + float weight = boneWeight.get(k); + if (weight > maxWeight) { + maxWeight = weight; + index = boneIndices.get(k); + } + } + List points = map.get(index); + if (points == null) { + points = new ArrayList(); + map.put(index, points); + } + points.add(vertices.get(i)); + points.add(vertices.get(i + 1)); + points.add(vertices.get(i + 2)); + } + return map; + } + + /** + * Create a hull collision shape from linked vertices to this bone. + * Vertices have to be previoulsly gathered in a map using buildPointMap method + * + * @param pointsMap + * @param boneIndices + * @param initialScale + * @param initialPosition + * @return + */ + public static HullCollisionShape makeShapeFromPointMap(Map> pointsMap, List boneIndices, Vector3f initialScale, Vector3f initialPosition) { + + ArrayList points = new ArrayList(); + for (Integer index : boneIndices) { + List l = pointsMap.get(index); + if (l != null) { + + for (int i = 0; i < l.size(); i += 3) { + Vector3f pos = new Vector3f(); + pos.x = l.get(i); + pos.y = l.get(i + 1); + pos.z = l.get(i + 2); + pos.subtractLocal(initialPosition).multLocal(initialScale); + points.add(pos.x); + points.add(pos.y); + points.add(pos.z); + } + } + } + + float[] p = new float[points.size()]; + for (int i = 0; i < points.size(); i++) { + p[i] = points.get(i); + } + + + return new HullCollisionShape(p); + } + + //retruns the list of bone indices of the given bone and its child(if they are not in the boneList) + public static List getBoneIndices(Bone bone, Skeleton skeleton, Set boneList) { + List list = new LinkedList(); + if (boneList.isEmpty()) { + list.add(skeleton.getBoneIndex(bone)); + } else { + list.add(skeleton.getBoneIndex(bone)); + for (Bone chilBone : bone.getChildren()) { + if (!boneList.contains(chilBone.getName())) { + list.addAll(getBoneIndices(chilBone, skeleton, boneList)); + } + } + } + return list; + } + + /** + * Create a hull collision shape from linked vertices to this bone. + * + * @param model + * @param boneIndices + * @param initialScale + * @param initialPosition + * @param weightThreshold + * @return + */ + public static HullCollisionShape makeShapeFromVerticeWeights(Spatial model, List boneIndices, Vector3f initialScale, Vector3f initialPosition, float weightThreshold) { + + ArrayList points = new ArrayList(); + if (model instanceof Geometry) { + Geometry g = (Geometry) model; + for (Integer index : boneIndices) { + points.addAll(getPoints(g.getMesh(), index, initialScale, initialPosition, weightThreshold)); + } + } else if (model instanceof Node) { + Node node = (Node) model; + for (Spatial s : node.getChildren()) { + if (s instanceof Geometry) { + Geometry g = (Geometry) s; + for (Integer index : boneIndices) { + points.addAll(getPoints(g.getMesh(), index, initialScale, initialPosition, weightThreshold)); + } + + } + } + } + float[] p = new float[points.size()]; + for (int i = 0; i < points.size(); i++) { + p[i] = points.get(i); + } + + + return new HullCollisionShape(p); + } + + /** + * returns a list of points for the given bone + * @param mesh + * @param boneIndex + * @param offset + * @param link + * @return + */ + private static List getPoints(Mesh mesh, int boneIndex, Vector3f initialScale, Vector3f offset, float weightThreshold) { + + FloatBuffer vertices = mesh.getFloatBuffer(Type.Position); + ByteBuffer boneIndices = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData(); + FloatBuffer boneWeight = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); + + vertices.rewind(); + boneIndices.rewind(); + boneWeight.rewind(); + + ArrayList results = new ArrayList(); + + int vertexComponents = mesh.getVertexCount() * 3; + + for (int i = 0; i < vertexComponents; i += 3) { + int k; + boolean add = false; + int start = i / 3 * 4; + for (k = start; k < start + 4; k++) { + if (boneIndices.get(k) == boneIndex && boneWeight.get(k) >= weightThreshold) { + add = true; + break; + } + } + if (add) { + + Vector3f pos = new Vector3f(); + pos.x = vertices.get(i); + pos.y = vertices.get(i + 1); + pos.z = vertices.get(i + 2); + pos.subtractLocal(offset).multLocal(initialScale); + results.add(pos.x); + results.add(pos.y); + results.add(pos.z); + + } + } + + return results; + } + + /** + * Updates a bone position and rotation. + * if the child bones are not in the bone list this means, they are not associated with a physic shape. + * So they have to be updated + * @param bone the bone + * @param pos the position + * @param rot the rotation + */ + public static void setTransform(Bone bone, Vector3f pos, Quaternion rot, boolean restoreBoneControl, Set boneList) { + //we ensure that we have the control + if (restoreBoneControl) { + bone.setUserControl(true); + } + //we set te user transforms of the bone + bone.setUserTransformsWorld(pos, rot); + for (Bone childBone : bone.getChildren()) { + //each child bone that is not in the list is updated + if (!boneList.contains(childBone.getName())) { + Transform t = childBone.getCombinedTransform(pos, rot); + setTransform(childBone, t.getTranslation(), t.getRotation(), restoreBoneControl, boneList); + } + } + //we give back the control to the keyframed animation + if (restoreBoneControl) { + bone.setUserControl(false); + } + } + + public static void setUserControl(Bone bone, boolean bool) { + bone.setUserControl(bool); + for (Bone child : bone.getChildren()) { + setUserControl(child, bool); + } + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/AbstractPhysicsDebugControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/debug/AbstractPhysicsDebugControl.java new file mode 100644 index 000000000..75e7bbe45 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/debug/AbstractPhysicsDebugControl.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.debug; + +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.AbstractControl; + +/** + * + * @author normenhansen + */ +public abstract class AbstractPhysicsDebugControl extends AbstractControl { + + private final Quaternion tmp_inverseWorldRotation = new Quaternion(); + protected final BulletDebugAppState debugAppState; + + public AbstractPhysicsDebugControl(BulletDebugAppState debugAppState) { + this.debugAppState = debugAppState; + } + + /** + * This is called on the physics thread for debug controls + */ + @Override + protected abstract void controlUpdate(float tpf); + + protected void applyPhysicsTransform(Vector3f worldLocation, Quaternion worldRotation) { + applyPhysicsTransform(worldLocation, worldRotation, this.spatial); + } + + protected void applyPhysicsTransform(Vector3f worldLocation, Quaternion worldRotation, Spatial spatial) { + if (spatial != null) { + Vector3f localLocation = spatial.getLocalTranslation(); + Quaternion localRotationQuat = spatial.getLocalRotation(); + if (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(worldRotation); + tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().mult(localRotationQuat, localRotationQuat); + spatial.setLocalTranslation(localLocation); + spatial.setLocalRotation(localRotationQuat); + } else { + spatial.setLocalTranslation(worldLocation); + spatial.setLocalRotation(worldRotation); + } + } + + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletCharacterDebugControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletCharacterDebugControl.java new file mode 100644 index 000000000..43c1cfd36 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletCharacterDebugControl.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.debug; + +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.objects.PhysicsCharacter; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; + +/** + * + * @author normenhansen + */ +public class BulletCharacterDebugControl extends AbstractPhysicsDebugControl { + + protected final PhysicsCharacter body; + protected final Vector3f location = new Vector3f(); + protected final Quaternion rotation = new Quaternion(); + protected CollisionShape myShape; + protected Spatial geom; + + public BulletCharacterDebugControl(BulletDebugAppState debugAppState, PhysicsCharacter body) { + super(debugAppState); + this.body = body; + myShape = body.getCollisionShape(); + this.geom = DebugShapeFactory.getDebugShape(body.getCollisionShape()); + geom.setMaterial(debugAppState.DEBUG_PINK); + } + + @Override + public void setSpatial(Spatial spatial) { + if (spatial != null && spatial instanceof Node) { + Node node = (Node) spatial; + node.attachChild(geom); + } else if (spatial == null && this.spatial != null) { + Node node = (Node) this.spatial; + node.detachChild(geom); + } + super.setSpatial(spatial); + } + + @Override + protected void controlUpdate(float tpf) { + if(myShape != body.getCollisionShape()){ + Node node = (Node) this.spatial; + node.detachChild(geom); + geom = DebugShapeFactory.getDebugShape(body.getCollisionShape()); + geom.setMaterial(debugAppState.DEBUG_PINK); + node.attachChild(geom); + } + applyPhysicsTransform(body.getPhysicsLocation(location), Quaternion.IDENTITY); + geom.setLocalScale(body.getCollisionShape().getScale()); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletDebugAppState.java b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletDebugAppState.java new file mode 100644 index 000000000..4019d2c2b --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletDebugAppState.java @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.debug; + +import com.jme3.app.Application; +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetManager; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.PhysicsCharacter; +import com.jme3.bullet.objects.PhysicsGhostObject; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author normenhansen + */ +public class BulletDebugAppState extends AbstractAppState { + + protected static final Logger logger = Logger.getLogger(BulletDebugAppState.class.getName()); + protected DebugAppStateFilter filter; + protected Application app; + protected AssetManager assetManager; + protected final PhysicsSpace space; + protected final Node physicsDebugRootNode = new Node("Physics Debug Root Node"); + protected ViewPort viewPort; + protected RenderManager rm; + public Material DEBUG_BLUE; + public Material DEBUG_RED; + public Material DEBUG_GREEN; + public Material DEBUG_YELLOW; + public Material DEBUG_MAGENTA; + public Material DEBUG_PINK; + protected HashMap bodies = new HashMap(); + protected HashMap joints = new HashMap(); + protected HashMap ghosts = new HashMap(); + protected HashMap characters = new HashMap(); + protected HashMap vehicles = new HashMap(); + + public BulletDebugAppState(PhysicsSpace space) { + this.space = space; + } + + public DebugTools getNewDebugTools() { + return new DebugTools(assetManager); + } + + public void setFilter(DebugAppStateFilter filter) { + this.filter = filter; + } + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + this.app = app; + this.rm = app.getRenderManager(); + this.assetManager = app.getAssetManager(); + setupMaterials(app); + physicsDebugRootNode.setCullHint(Spatial.CullHint.Never); + viewPort = rm.createMainView("Physics Debug Overlay", app.getCamera()); + viewPort.setClearFlags(false, true, false); + viewPort.attachScene(physicsDebugRootNode); + } + + @Override + public void cleanup() { + rm.removeMainView(viewPort); + super.cleanup(); + } + + @Override + public void update(float tpf) { + super.update(tpf); + //update all object links + updateRigidBodies(); + updateGhosts(); + updateCharacters(); + updateJoints(); + updateVehicles(); + //update our debug root node + physicsDebugRootNode.updateLogicalState(tpf); + physicsDebugRootNode.updateGeometricState(); + } + + @Override + public void render(RenderManager rm) { + super.render(rm); + if (viewPort != null) { + rm.renderScene(physicsDebugRootNode, viewPort); + } + } + + private void setupMaterials(Application app) { + AssetManager manager = app.getAssetManager(); + DEBUG_BLUE = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md"); + DEBUG_BLUE.getAdditionalRenderState().setWireframe(true); + DEBUG_BLUE.setColor("Color", ColorRGBA.Blue); + DEBUG_GREEN = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md"); + DEBUG_GREEN.getAdditionalRenderState().setWireframe(true); + DEBUG_GREEN.setColor("Color", ColorRGBA.Green); + DEBUG_RED = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md"); + DEBUG_RED.getAdditionalRenderState().setWireframe(true); + DEBUG_RED.setColor("Color", ColorRGBA.Red); + DEBUG_YELLOW = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md"); + DEBUG_YELLOW.getAdditionalRenderState().setWireframe(true); + DEBUG_YELLOW.setColor("Color", ColorRGBA.Yellow); + DEBUG_MAGENTA = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md"); + DEBUG_MAGENTA.getAdditionalRenderState().setWireframe(true); + DEBUG_MAGENTA.setColor("Color", ColorRGBA.Magenta); + DEBUG_PINK = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md"); + DEBUG_PINK.getAdditionalRenderState().setWireframe(true); + DEBUG_PINK.setColor("Color", ColorRGBA.Pink); + } + + private void updateRigidBodies() { + HashMap oldObjects = bodies; + bodies = new HashMap(); + Collection current = space.getRigidBodyList(); + //create new map + for (Iterator it = current.iterator(); it.hasNext();) { + PhysicsRigidBody physicsObject = it.next(); + //copy existing spatials + if (oldObjects.containsKey(physicsObject)) { + Spatial spat = oldObjects.get(physicsObject); + bodies.put(physicsObject, spat); + oldObjects.remove(physicsObject); + } else { + if (filter == null || filter.displayObject(physicsObject)) { + logger.log(Level.FINE, "Create new debug RigidBody"); + //create new spatial + Node node = new Node(physicsObject.toString()); + node.addControl(new BulletRigidBodyDebugControl(this, physicsObject)); + bodies.put(physicsObject, node); + physicsDebugRootNode.attachChild(node); + } + } + } + //remove leftover spatials + for (Map.Entry entry : oldObjects.entrySet()) { + PhysicsRigidBody object = entry.getKey(); + Spatial spatial = entry.getValue(); + spatial.removeFromParent(); + } + } + + private void updateJoints() { + HashMap oldObjects = joints; + joints = new HashMap(); + Collection current = space.getJointList(); + //create new map + for (Iterator it = current.iterator(); it.hasNext();) { + PhysicsJoint physicsObject = it.next(); + //copy existing spatials + if (oldObjects.containsKey(physicsObject)) { + Spatial spat = oldObjects.get(physicsObject); + joints.put(physicsObject, spat); + oldObjects.remove(physicsObject); + } else { + if (filter == null || filter.displayObject(physicsObject)) { + logger.log(Level.FINE, "Create new debug Joint"); + //create new spatial + Node node = new Node(physicsObject.toString()); + node.addControl(new BulletJointDebugControl(this, physicsObject)); + joints.put(physicsObject, node); + physicsDebugRootNode.attachChild(node); + } + } + } + //remove leftover spatials + for (Map.Entry entry : oldObjects.entrySet()) { + PhysicsJoint object = entry.getKey(); + Spatial spatial = entry.getValue(); + spatial.removeFromParent(); + } + } + + private void updateGhosts() { + HashMap oldObjects = ghosts; + ghosts = new HashMap(); + Collection current = space.getGhostObjectList(); + //create new map + for (Iterator it = current.iterator(); it.hasNext();) { + PhysicsGhostObject physicsObject = it.next(); + //copy existing spatials + if (oldObjects.containsKey(physicsObject)) { + Spatial spat = oldObjects.get(physicsObject); + ghosts.put(physicsObject, spat); + oldObjects.remove(physicsObject); + } else { + if (filter == null || filter.displayObject(physicsObject)) { + logger.log(Level.FINE, "Create new debug GhostObject"); + //create new spatial + Node node = new Node(physicsObject.toString()); + node.addControl(new BulletGhostObjectDebugControl(this, physicsObject)); + ghosts.put(physicsObject, node); + physicsDebugRootNode.attachChild(node); + } + } + } + //remove leftover spatials + for (Map.Entry entry : oldObjects.entrySet()) { + PhysicsGhostObject object = entry.getKey(); + Spatial spatial = entry.getValue(); + spatial.removeFromParent(); + } + } + + private void updateCharacters() { + HashMap oldObjects = characters; + characters = new HashMap(); + Collection current = space.getCharacterList(); + //create new map + for (Iterator it = current.iterator(); it.hasNext();) { + PhysicsCharacter physicsObject = it.next(); + //copy existing spatials + if (oldObjects.containsKey(physicsObject)) { + Spatial spat = oldObjects.get(physicsObject); + characters.put(physicsObject, spat); + oldObjects.remove(physicsObject); + } else { + if (filter == null || filter.displayObject(physicsObject)) { + logger.log(Level.FINE, "Create new debug Character"); + //create new spatial + Node node = new Node(physicsObject.toString()); + node.addControl(new BulletCharacterDebugControl(this, physicsObject)); + characters.put(physicsObject, node); + physicsDebugRootNode.attachChild(node); + } + } + } + //remove leftover spatials + for (Map.Entry entry : oldObjects.entrySet()) { + PhysicsCharacter object = entry.getKey(); + Spatial spatial = entry.getValue(); + spatial.removeFromParent(); + } + } + + private void updateVehicles() { + HashMap oldObjects = vehicles; + vehicles = new HashMap(); + Collection current = space.getVehicleList(); + //create new map + for (Iterator it = current.iterator(); it.hasNext();) { + PhysicsVehicle physicsObject = it.next(); + //copy existing spatials + if (oldObjects.containsKey(physicsObject)) { + Spatial spat = oldObjects.get(physicsObject); + vehicles.put(physicsObject, spat); + oldObjects.remove(physicsObject); + } else { + if (filter == null || filter.displayObject(physicsObject)) { + logger.log(Level.FINE, "Create new debug Vehicle"); + //create new spatial + Node node = new Node(physicsObject.toString()); + node.addControl(new BulletVehicleDebugControl(this, physicsObject)); + vehicles.put(physicsObject, node); + physicsDebugRootNode.attachChild(node); + } + } + } + //remove leftover spatials + for (Map.Entry entry : oldObjects.entrySet()) { + PhysicsVehicle object = entry.getKey(); + Spatial spatial = entry.getValue(); + spatial.removeFromParent(); + } + } + + /** + * Interface that allows filtering out objects from the debug display + */ + public static interface DebugAppStateFilter { + + /** + * Queries an object to be displayed + * + * @param obj The object to be displayed + * @return return true if the object should be displayed, false if not + */ + public boolean displayObject(Object obj); + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletGhostObjectDebugControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletGhostObjectDebugControl.java new file mode 100644 index 000000000..62ab7a214 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletGhostObjectDebugControl.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.debug; + +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.objects.PhysicsGhostObject; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; + +/** + * + * @author normenhansen + */ +public class BulletGhostObjectDebugControl extends AbstractPhysicsDebugControl { + + protected final PhysicsGhostObject body; + protected final Vector3f location = new Vector3f(); + protected final Quaternion rotation = new Quaternion(); + protected CollisionShape myShape; + protected Spatial geom; + + public BulletGhostObjectDebugControl(BulletDebugAppState debugAppState, PhysicsGhostObject body) { + super(debugAppState); + this.body = body; + myShape = body.getCollisionShape(); + this.geom = DebugShapeFactory.getDebugShape(body.getCollisionShape()); + this.geom.setName(body.toString()); + this.geom.setName(body.toString()); + geom.setMaterial(debugAppState.DEBUG_YELLOW); + } + + @Override + public void setSpatial(Spatial spatial) { + if (spatial != null && spatial instanceof Node) { + Node node = (Node) spatial; + node.attachChild(geom); + } else if (spatial == null && this.spatial != null) { + Node node = (Node) this.spatial; + node.detachChild(geom); + } + super.setSpatial(spatial); + } + + @Override + protected void controlUpdate(float tpf) { + if (myShape != body.getCollisionShape()) { + Node node = (Node) this.spatial; + node.detachChild(geom); + geom = DebugShapeFactory.getDebugShape(body.getCollisionShape()); + geom.setMaterial(debugAppState.DEBUG_YELLOW); + node.attachChild(geom); + } + applyPhysicsTransform(body.getPhysicsLocation(location), body.getPhysicsRotation(rotation)); + geom.setLocalScale(body.getCollisionShape().getScale()); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletJointDebugControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletJointDebugControl.java new file mode 100644 index 000000000..571452811 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletJointDebugControl.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.debug; + +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +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.debug.Arrow; + +/** + * + * @author normenhansen + */ +public class BulletJointDebugControl extends AbstractPhysicsDebugControl { + + protected final PhysicsJoint body; + protected final Geometry geomA; + protected final Arrow arrowA; + protected final Geometry geomB; + protected final Arrow arrowB; + protected final Transform a = new Transform(new Vector3f(), new Quaternion()); + protected final Transform b = new Transform(new Vector3f(), new Quaternion()); + protected final Vector3f offA = new Vector3f(); + protected final Vector3f offB = new Vector3f(); + + public BulletJointDebugControl(BulletDebugAppState debugAppState, PhysicsJoint body) { + super(debugAppState); + this.body = body; + this.geomA = new Geometry(body.toString()); + arrowA = new Arrow(Vector3f.ZERO); + geomA.setMesh(arrowA); + geomA.setMaterial(debugAppState.DEBUG_GREEN); + this.geomB = new Geometry(body.toString()); + arrowB = new Arrow(Vector3f.ZERO); + geomB.setMesh(arrowB); + geomB.setMaterial(debugAppState.DEBUG_GREEN); + } + + @Override + public void setSpatial(Spatial spatial) { + if (spatial != null && spatial instanceof Node) { + Node node = (Node) spatial; + node.attachChild(geomA); + node.attachChild(geomB); + } else if (spatial == null && this.spatial != null) { + Node node = (Node) this.spatial; + node.detachChild(geomA); + node.detachChild(geomB); + } + super.setSpatial(spatial); + } + + @Override + protected void controlUpdate(float tpf) { + body.getBodyA().getPhysicsLocation(a.getTranslation()); + body.getBodyA().getPhysicsRotation(a.getRotation()); + + body.getBodyB().getPhysicsLocation(b.getTranslation()); + body.getBodyB().getPhysicsRotation(b.getRotation()); + + geomA.setLocalTransform(a); + geomB.setLocalTransform(b); + + arrowA.setArrowExtent(body.getPivotA()); + arrowB.setArrowExtent(body.getPivotB()); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletRigidBodyDebugControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletRigidBodyDebugControl.java new file mode 100644 index 000000000..3bc10f3e6 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletRigidBodyDebugControl.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.debug; + +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; + +/** + * + * @author normenhansen + */ +public class BulletRigidBodyDebugControl extends AbstractPhysicsDebugControl { + + protected final PhysicsRigidBody body; + protected final Vector3f location = new Vector3f(); + protected final Quaternion rotation = new Quaternion(); + protected CollisionShape myShape; + protected Spatial geom; + + public BulletRigidBodyDebugControl(BulletDebugAppState debugAppState, PhysicsRigidBody body) { + super(debugAppState); + this.body = body; + myShape = body.getCollisionShape(); + this.geom = DebugShapeFactory.getDebugShape(body.getCollisionShape()); + this.geom.setName(body.toString()); + geom.setMaterial(debugAppState.DEBUG_BLUE); + } + + @Override + public void setSpatial(Spatial spatial) { + if (spatial != null && spatial instanceof Node) { + Node node = (Node) spatial; + node.attachChild(geom); + } else if (spatial == null && this.spatial != null) { + Node node = (Node) this.spatial; + node.detachChild(geom); + } + super.setSpatial(spatial); + } + + @Override + protected void controlUpdate(float tpf) { + if(myShape != body.getCollisionShape()){ + Node node = (Node) this.spatial; + node.detachChild(geom); + geom = DebugShapeFactory.getDebugShape(body.getCollisionShape()); + node.attachChild(geom); + } + if(body.isActive()){ + geom.setMaterial(debugAppState.DEBUG_MAGENTA); + }else{ + geom.setMaterial(debugAppState.DEBUG_BLUE); + } + applyPhysicsTransform(body.getPhysicsLocation(location), body.getPhysicsRotation(rotation)); + geom.setLocalScale(body.getCollisionShape().getScale()); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletVehicleDebugControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletVehicleDebugControl.java new file mode 100644 index 000000000..f7adf1208 --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletVehicleDebugControl.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.debug; + +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.bullet.objects.VehicleWheel; +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.debug.Arrow; + +/** + * + * @author normenhansen + */ +public class BulletVehicleDebugControl extends AbstractPhysicsDebugControl { + + protected final PhysicsVehicle body; + protected final Node suspensionNode; + protected final Vector3f location = new Vector3f(); + protected final Quaternion rotation = new Quaternion(); + + public BulletVehicleDebugControl(BulletDebugAppState debugAppState, PhysicsVehicle body) { + super(debugAppState); + this.body = body; + suspensionNode = new Node("Suspension"); + createVehicle(); + } + + @Override + public void setSpatial(Spatial spatial) { + if (spatial != null && spatial instanceof Node) { + Node node = (Node) spatial; + node.attachChild(suspensionNode); + } else if (spatial == null && this.spatial != null) { + Node node = (Node) this.spatial; + node.detachChild(suspensionNode); + } + super.setSpatial(spatial); + } + + private void createVehicle() { + suspensionNode.detachAllChildren(); + for (int i = 0; i < body.getNumWheels(); i++) { + VehicleWheel physicsVehicleWheel = body.getWheel(i); + 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(debugAppState.DEBUG_MAGENTA); + dirGeom.setMaterial(debugAppState.DEBUG_MAGENTA); + axleGeom.setMaterial(debugAppState.DEBUG_MAGENTA); + wheelGeom.setMaterial(debugAppState.DEBUG_MAGENTA); + suspensionNode.attachChild(locGeom); + suspensionNode.attachChild(dirGeom); + suspensionNode.attachChild(axleGeom); + suspensionNode.attachChild(wheelGeom); + } + } + + @Override + protected void controlUpdate(float tpf) { + for (int i = 0; i < body.getNumWheels(); i++) { + VehicleWheel physicsVehicleWheel = body.getWheel(i); + 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) suspensionNode.getChild("WheelLocationDebugShape" + i); + Geometry dirGeom = (Geometry) suspensionNode.getChild("WheelDirectionDebugShape" + i); + Geometry axleGeom = (Geometry) suspensionNode.getChild("WheelAxleDebugShape" + i); + Geometry wheelGeom = (Geometry) suspensionNode.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++; + } + applyPhysicsTransform(body.getPhysicsLocation(location), body.getPhysicsRotation(rotation)); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/DebugTools.java b/jme3-bullet/src/common/java/com/jme3/bullet/debug/DebugTools.java new file mode 100644 index 000000000..2e7246b7a --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/debug/DebugTools.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.debug; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +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.debug.Arrow; + +/** + * + * @author normenhansen + */ +public class DebugTools { + + protected final AssetManager manager; + public Material DEBUG_BLUE; + public Material DEBUG_RED; + public Material DEBUG_GREEN; + public Material DEBUG_YELLOW; + public Material DEBUG_MAGENTA; + public Material DEBUG_PINK; + public Node debugNode = new Node("Debug Node"); + public Arrow arrowBlue = new Arrow(Vector3f.ZERO); + public Geometry arrowBlueGeom = new Geometry("Blue Arrow", arrowBlue); + public Arrow arrowGreen = new Arrow(Vector3f.ZERO); + public Geometry arrowGreenGeom = new Geometry("Green Arrow", arrowGreen); + public Arrow arrowRed = new Arrow(Vector3f.ZERO); + public Geometry arrowRedGeom = new Geometry("Red Arrow", arrowRed); + public Arrow arrowMagenta = new Arrow(Vector3f.ZERO); + public Geometry arrowMagentaGeom = new Geometry("Magenta Arrow", arrowMagenta); + public Arrow arrowYellow = new Arrow(Vector3f.ZERO); + public Geometry arrowYellowGeom = new Geometry("Yellow Arrow", arrowYellow); + public Arrow arrowPink = new Arrow(Vector3f.ZERO); + public Geometry arrowPinkGeom = new Geometry("Pink Arrow", arrowPink); + protected static final Vector3f UNIT_X_CHECK = new Vector3f(1, 0, 0); + protected static final Vector3f UNIT_Y_CHECK = new Vector3f(0, 1, 0); + protected static final Vector3f UNIT_Z_CHECK = new Vector3f(0, 0, 1); + protected static final Vector3f UNIT_XYZ_CHECK = new Vector3f(1, 1, 1); + protected static final Vector3f ZERO_CHECK = new Vector3f(0, 0, 0); + + public DebugTools(AssetManager manager) { + this.manager = manager; + setupMaterials(); + setupDebugNode(); + } + + public void show(RenderManager rm, ViewPort vp) { + if (!Vector3f.UNIT_X.equals(UNIT_X_CHECK) || !Vector3f.UNIT_Y.equals(UNIT_Y_CHECK) || !Vector3f.UNIT_Z.equals(UNIT_Z_CHECK) + || !Vector3f.UNIT_XYZ.equals(UNIT_XYZ_CHECK) || !Vector3f.ZERO.equals(ZERO_CHECK)) { + throw new IllegalStateException("Unit vectors compromised!" + + "\nX: " + Vector3f.UNIT_X + + "\nY: " + Vector3f.UNIT_Y + + "\nZ: " + Vector3f.UNIT_Z + + "\nXYZ: " + Vector3f.UNIT_XYZ + + "\nZERO: " + Vector3f.ZERO); + } + debugNode.updateLogicalState(0); + debugNode.updateGeometricState(); + rm.renderScene(debugNode, vp); + } + + public void setBlueArrow(Vector3f location, Vector3f extent) { + arrowBlueGeom.setLocalTranslation(location); + arrowBlue.setArrowExtent(extent); + } + + public void setGreenArrow(Vector3f location, Vector3f extent) { + arrowGreenGeom.setLocalTranslation(location); + arrowGreen.setArrowExtent(extent); + } + + public void setRedArrow(Vector3f location, Vector3f extent) { + arrowRedGeom.setLocalTranslation(location); + arrowRed.setArrowExtent(extent); + } + + public void setMagentaArrow(Vector3f location, Vector3f extent) { + arrowMagentaGeom.setLocalTranslation(location); + arrowMagenta.setArrowExtent(extent); + } + + public void setYellowArrow(Vector3f location, Vector3f extent) { + arrowYellowGeom.setLocalTranslation(location); + arrowYellow.setArrowExtent(extent); + } + + public void setPinkArrow(Vector3f location, Vector3f extent) { + arrowPinkGeom.setLocalTranslation(location); + arrowPink.setArrowExtent(extent); + } + + protected void setupDebugNode() { + arrowBlueGeom.setMaterial(DEBUG_BLUE); + arrowGreenGeom.setMaterial(DEBUG_GREEN); + arrowRedGeom.setMaterial(DEBUG_RED); + arrowMagentaGeom.setMaterial(DEBUG_MAGENTA); + arrowYellowGeom.setMaterial(DEBUG_YELLOW); + arrowPinkGeom.setMaterial(DEBUG_PINK); + debugNode.attachChild(arrowBlueGeom); + debugNode.attachChild(arrowGreenGeom); + debugNode.attachChild(arrowRedGeom); + debugNode.attachChild(arrowMagentaGeom); + debugNode.attachChild(arrowYellowGeom); + debugNode.attachChild(arrowPinkGeom); + } + + protected void setupMaterials() { + DEBUG_BLUE = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md"); + DEBUG_BLUE.getAdditionalRenderState().setWireframe(true); + DEBUG_BLUE.setColor("Color", ColorRGBA.Blue); + DEBUG_GREEN = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md"); + DEBUG_GREEN.getAdditionalRenderState().setWireframe(true); + DEBUG_GREEN.setColor("Color", ColorRGBA.Green); + DEBUG_RED = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md"); + DEBUG_RED.getAdditionalRenderState().setWireframe(true); + DEBUG_RED.setColor("Color", ColorRGBA.Red); + DEBUG_YELLOW = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md"); + DEBUG_YELLOW.getAdditionalRenderState().setWireframe(true); + DEBUG_YELLOW.setColor("Color", ColorRGBA.Yellow); + DEBUG_MAGENTA = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md"); + DEBUG_MAGENTA.getAdditionalRenderState().setWireframe(true); + DEBUG_MAGENTA.setColor("Color", ColorRGBA.Magenta); + DEBUG_PINK = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md"); + DEBUG_PINK.getAdditionalRenderState().setWireframe(true); + DEBUG_PINK.setColor("Color", ColorRGBA.Pink); + } +} diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/util/CollisionShapeFactory.java b/jme3-bullet/src/common/java/com/jme3/bullet/util/CollisionShapeFactory.java new file mode 100644 index 000000000..38551ea3f --- /dev/null +++ b/jme3-bullet/src/common/java/com/jme3/bullet/util/CollisionShapeFactory.java @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.util; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bullet.collision.shapes.*; +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.math.Matrix3f; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.*; +import com.jme3.terrain.geomipmap.TerrainPatch; +import com.jme3.terrain.geomipmap.TerrainQuad; +import java.util.Iterator; +import java.util.LinkedList; + +/** + * + * @author normenhansen, tim8dev + */ +public class CollisionShapeFactory { + + /** + * returns the correct transform for a collisionshape in relation + * to the ancestor for which the collisionshape is generated + * @param spat + * @param parent + * @return + */ + private static Transform getTransform(Spatial spat, Spatial parent) { + Transform shapeTransform = new Transform(); + Spatial parentNode = spat.getParent() != null ? spat.getParent() : spat; + Spatial currentSpatial = spat; + //if we have parents combine their transforms + while (parentNode != null) { + if (parent == currentSpatial) { + //real parent -> only apply scale, not transform + Transform trans = new Transform(); + trans.setScale(currentSpatial.getLocalScale()); + shapeTransform.combineWithParent(trans); + parentNode = null; + } else { + shapeTransform.combineWithParent(currentSpatial.getLocalTransform()); + parentNode = currentSpatial.getParent(); + currentSpatial = parentNode; + } + } + return shapeTransform; + } + + private static CompoundCollisionShape createCompoundShape(Node realRootNode, + Node rootNode, CompoundCollisionShape shape, boolean meshAccurate, boolean dynamic) { + for (Spatial spatial : rootNode.getChildren()) { + if (spatial instanceof TerrainQuad) { + Boolean bool = spatial.getUserData(UserData.JME_PHYSICSIGNORE); + if (bool != null && bool.booleanValue()) { + continue; // go to the next child in the loop + } + TerrainQuad terrain = (TerrainQuad) spatial; + Transform trans = getTransform(spatial, realRootNode); + shape.addChildShape(new HeightfieldCollisionShape(terrain.getHeightMap(), trans.getScale()), + trans.getTranslation(), + trans.getRotation().toRotationMatrix()); + } else if (spatial instanceof Node) { + createCompoundShape(realRootNode, (Node) spatial, shape, meshAccurate, dynamic); + } else if (spatial instanceof TerrainPatch) { + Boolean bool = spatial.getUserData(UserData.JME_PHYSICSIGNORE); + if (bool != null && bool.booleanValue()) { + continue; // go to the next child in the loop + } + TerrainPatch terrain = (TerrainPatch) spatial; + Transform trans = getTransform(spatial, realRootNode); + shape.addChildShape(new HeightfieldCollisionShape(terrain.getHeightMap(), terrain.getLocalScale()), + trans.getTranslation(), + trans.getRotation().toRotationMatrix()); + } else if (spatial instanceof Geometry) { + Boolean bool = spatial.getUserData(UserData.JME_PHYSICSIGNORE); + if (bool != null && bool.booleanValue()) { + continue; // go to the next child in the loop + } + + if (meshAccurate) { + CollisionShape childShape = dynamic + ? createSingleDynamicMeshShape((Geometry) spatial, realRootNode) + : createSingleMeshShape((Geometry) spatial, realRootNode); + if (childShape != null) { + Transform trans = getTransform(spatial, realRootNode); + shape.addChildShape(childShape, + trans.getTranslation(), + trans.getRotation().toRotationMatrix()); + } + } else { + Transform trans = getTransform(spatial, realRootNode); + shape.addChildShape(createSingleBoxShape(spatial, realRootNode), + trans.getTranslation(), + trans.getRotation().toRotationMatrix()); + } + } + } + return shape; + } + + private static CompoundCollisionShape createCompoundShape( + Node rootNode, CompoundCollisionShape shape, boolean meshAccurate) { + return createCompoundShape(rootNode, rootNode, shape, meshAccurate, false); + } + + /** + * This type of collision shape is mesh-accurate and meant for immovable "world objects". + * Examples include terrain, houses or whole shooter levels.
    + * 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.
    + * Creates a HeightfieldCollisionShape if the supplied spatial is a TerrainQuad. + * @return A MeshCollisionShape or a CompoundCollisionShape with MeshCollisionShapes as children if the supplied spatial is a Node. A HeightieldCollisionShape if a TerrainQuad was supplied. + */ + public static CollisionShape createMeshShape(Spatial spatial) { + if (spatial instanceof TerrainQuad) { + TerrainQuad terrain = (TerrainQuad) spatial; + return new HeightfieldCollisionShape(terrain.getHeightMap(), terrain.getLocalScale()); + } else if (spatial instanceof TerrainPatch) { + TerrainPatch terrain = (TerrainPatch) spatial; + return new HeightfieldCollisionShape(terrain.getHeightMap(), terrain.getLocalScale()); + } else if (spatial instanceof Geometry) { + return createSingleMeshShape((Geometry) spatial, spatial); + } else if (spatial instanceof Node) { + return createMeshCompoundShape((Node) spatial); + } else { + throw new IllegalArgumentException("Supplied spatial must either be Node or Geometry!"); + } + } + + /** + * This method creates a hull shape for the given Spatial.
    + * If you want to have mesh-accurate dynamic shapes (CPU intense!!!) use GImpact shapes, its probably best to do so with a low-poly version of your model. + * @return A HullCollisionShape or a CompoundCollisionShape with HullCollisionShapes as children if the supplied spatial is a Node. + */ + public static CollisionShape createDynamicMeshShape(Spatial spatial) { + if (spatial instanceof Geometry) { + return createSingleDynamicMeshShape((Geometry) spatial, spatial); + } else if (spatial instanceof Node) { + return createCompoundShape((Node) spatial, (Node) spatial, new CompoundCollisionShape(), true, true); + } else { + throw new IllegalArgumentException("Supplied spatial must either be Node or Geometry!"); + } + + } + + public static CollisionShape createBoxShape(Spatial spatial) { + if (spatial instanceof Geometry) { + return createSingleBoxShape((Geometry) spatial, spatial); + } else if (spatial instanceof Node) { + return createBoxCompoundShape((Node) spatial); + } else { + throw new IllegalArgumentException("Supplied spatial must either be Node or Geometry!"); + } + } + + /** + * This type of collision shape is mesh-accurate and meant for immovable "world objects". + * Examples include terrain, houses or whole shooter levels.
    + * Objects with "mesh" type collision shape will not collide with each other. + */ + private static MeshCollisionShape createSingleMeshShape(Geometry geom, Spatial parent) { + Mesh mesh = geom.getMesh(); + Transform trans = getTransform(geom, parent); + if (mesh != null) { + MeshCollisionShape mColl = new MeshCollisionShape(mesh); + mColl.setScale(trans.getScale()); + return mColl; + } else { + return null; + } + } + + /** + * Uses the bounding box of the supplied spatial to create a BoxCollisionShape + * @param spatial + * @return BoxCollisionShape with the size of the spatials BoundingBox + */ + private static BoxCollisionShape createSingleBoxShape(Spatial spatial, Spatial parent) { + //TODO: using world bound here instead of "local world" bound... + BoxCollisionShape shape = new BoxCollisionShape( + ((BoundingBox) spatial.getWorldBound()).getExtent(new Vector3f())); + return shape; + } + + /** + * This method creates a hull collision shape for the given mesh.
    + */ + private static HullCollisionShape createSingleDynamicMeshShape(Geometry geom, Spatial parent) { + Mesh mesh = geom.getMesh(); + Transform trans = getTransform(geom, parent); + if (mesh != null) { + HullCollisionShape dynamicShape = new HullCollisionShape(mesh); + dynamicShape.setScale(trans.getScale()); + return dynamicShape; + } else { + return null; + } + } + + /** + * This method moves each child shape of a compound shape by the given vector + * @param vector + */ + public static void shiftCompoundShapeContents(CompoundCollisionShape compoundShape, Vector3f vector) { + for (Iterator 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/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java new file mode 100644 index 000000000..fd12dcb89 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -0,0 +1,1026 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet; + +import com.jme3.app.AppTask; +import com.jme3.asset.AssetManager; +import com.jme3.bullet.collision.*; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.PhysicsControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.PhysicsCharacter; +import com.jme3.bullet.objects.PhysicsGhostObject; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import java.util.Collection; +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 { + + private static final Logger logger = Logger.getLogger(PhysicsSpace.class.getName()); + public static final int AXIS_X = 0; + public static final int AXIS_Y = 1; + public static final int AXIS_Z = 2; + private long physicsSpaceId = 0; + private static ThreadLocal>> pQueueTL = + new ThreadLocal>>() { + @Override + protected ConcurrentLinkedQueue> initialValue() { + return new ConcurrentLinkedQueue>(); + } + }; + private ConcurrentLinkedQueue> pQueue = new ConcurrentLinkedQueue>(); + private static ThreadLocal physicsSpaceTL = new ThreadLocal(); + private BroadphaseType broadphaseType = BroadphaseType.DBVT; +// private DiscreteDynamicsWorld dynamicsWorld = null; +// private BroadphaseInterface broadphase; +// private CollisionDispatcher dispatcher; +// private ConstraintSolver solver; +// private DefaultCollisionConfiguration collisionConfiguration; +// private Map physicsGhostNodes = new ConcurrentHashMap(); + private Map physicsGhostObjects = new ConcurrentHashMap(); + private Map physicsCharacters = new ConcurrentHashMap(); + private Map physicsBodies = new ConcurrentHashMap(); + private Map physicsJoints = new ConcurrentHashMap(); + private Map physicsVehicles = new ConcurrentHashMap(); + 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, rayTestFlags = 1 << 2; + private AssetManager debugManager; + + static { +// System.loadLibrary("bulletjme"); +// initNativePhysics(); + } + + /** + * 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() { + physicsSpaceId = createPhysicsSpace(worldMin.x, worldMin.y, worldMin.z, worldMax.x, worldMax.y, worldMax.z, broadphaseType.ordinal(), false); + pQueueTL.set(pQueue); + physicsSpaceTL.set(this); + +// collisionConfiguration = new DefaultCollisionConfiguration(); +// dispatcher = new CollisionDispatcher(collisionConfiguration); +// switch (broadphaseType) { +// case SIMPLE: +// broadphase = new SimpleBroadphase(); +// break; +// case AXIS_SWEEP_3: +// broadphase = new AxisSweep3(Converter.convert(worldMin), Converter.convert(worldMax)); +// break; +// case AXIS_SWEEP_3_32: +// broadphase = new AxisSweep3_32(Converter.convert(worldMin), Converter.convert(worldMax)); +// break; +// case DBVT: +// broadphase = new DbvtBroadphase(); +// break; +// } +// +// solver = new SequentialImpulseConstraintSolver(); +// +// dynamicsWorld = new DiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration); +// dynamicsWorld.setGravity(new javax.vecmath.Vector3f(0, -9.81f, 0)); +// +// broadphase.getOverlappingPairCache().setInternalGhostPairCallback(new GhostPairCallback()); +// GImpactCollisionAlgorithm.registerAlgorithm(dispatcher); +// +// //register filter callback for tick / collision +// setTickCallback(); +// setContactCallbacks(); +// //register filter callback for collision groups +// setOverlapFilterCallback(); + } + + private native long createPhysicsSpace(float minX, float minY, float minZ, float maxX, float maxY, float maxZ, int broadphaseType, boolean threading); + + private void preTick_native(float f) { + AppTask task = pQueue.poll(); + task = pQueue.poll(); + while (task != null) { + while (task.isCancelled()) { + task = pQueue.poll(); + } + try { + task.invoke(); + } catch (Exception ex) { + logger.log(Level.SEVERE, null, ex); + } + task = pQueue.poll(); + } + for (Iterator it = tickListeners.iterator(); it.hasNext();) { + PhysicsTickListener physicsTickCallback = it.next(); + physicsTickCallback.prePhysicsTick(this, f); + } + } + + private void postTick_native(float f) { + for (Iterator it = tickListeners.iterator(); it.hasNext();) { + PhysicsTickListener physicsTickCallback = it.next(); + physicsTickCallback.physicsTick(this, f); + } + } + + private void addCollision_native() { + } + + private boolean needCollision_native(PhysicsCollisionObject objectA, PhysicsCollisionObject objectB) { + return false; + } + +// private void setOverlapFilterCallback() { +// OverlapFilterCallback callback = new OverlapFilterCallback() { +// +// public boolean needBroadphaseCollision(BroadphaseProxy bp, BroadphaseProxy bp1) { +// boolean collides = (bp.collisionFilterGroup & bp1.collisionFilterMask) != 0; +// if (collides) { +// collides = (bp1.collisionFilterGroup & bp.collisionFilterMask) != 0; +// } +// if (collides) { +// assert (bp.clientObject instanceof com.bulletphysics.collision.dispatch.CollisionObject && bp.clientObject instanceof com.bulletphysics.collision.dispatch.CollisionObject); +// com.bulletphysics.collision.dispatch.CollisionObject colOb = (com.bulletphysics.collision.dispatch.CollisionObject) bp.clientObject; +// com.bulletphysics.collision.dispatch.CollisionObject colOb1 = (com.bulletphysics.collision.dispatch.CollisionObject) bp1.clientObject; +// assert (colOb.getUserPointer() != null && colOb1.getUserPointer() != null); +// PhysicsCollisionObject collisionObject = (PhysicsCollisionObject) colOb.getUserPointer(); +// PhysicsCollisionObject collisionObject1 = (PhysicsCollisionObject) colOb1.getUserPointer(); +// if ((collisionObject.getCollideWithGroups() & collisionObject1.getCollisionGroup()) > 0 +// || (collisionObject1.getCollideWithGroups() & collisionObject.getCollisionGroup()) > 0) { +// PhysicsCollisionGroupListener listener = collisionGroupListeners.get(collisionObject.getCollisionGroup()); +// PhysicsCollisionGroupListener listener1 = collisionGroupListeners.get(collisionObject1.getCollisionGroup()); +// if (listener != null) { +// return listener.collide(collisionObject, collisionObject1); +// } else if (listener1 != null) { +// return listener1.collide(collisionObject, collisionObject1); +// } +// return true; +// } else { +// return false; +// } +// } +// return collides; +// } +// }; +// dynamicsWorld.getPairCache().setOverlapFilterCallback(callback); +// } +// private void setTickCallback() { +// final PhysicsSpace space = this; +// InternalTickCallback callback2 = new InternalTickCallback() { +// +// @Override +// public void internalTick(DynamicsWorld dw, float f) { +// //execute task list +// AppTask task = pQueue.poll(); +// task = pQueue.poll(); +// while (task != null) { +// while (task.isCancelled()) { +// task = pQueue.poll(); +// } +// try { +// task.invoke(); +// } catch (Exception ex) { +// logger.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; +// } +// }); +// } + private void addCollisionEvent_native(PhysicsCollisionObject node, PhysicsCollisionObject node1, long manifoldPointObjectId) { +// System.out.println("addCollisionEvent:"+node.getObjectId()+" "+ node1.getObjectId()); + collisionEvents.add(eventFactory.getEvent(PhysicsCollisionEvent.TYPE_PROCESSED, node, node1, manifoldPointObjectId)); + } + + /** + * updates the physics space + * + * @param time the current time value + */ + public void update(float time) { + update(time, maxSubSteps); + } + + /** + * updates the physics space, uses maxSteps
    + * + * @param time the current time value + * @param maxSteps + */ + public void update(float time, int maxSteps) { +// if (getDynamicsWorld() == null) { +// return; +// } + //step simulation + stepSimulation(physicsSpaceId, time, maxSteps, accuracy); + } + + private native void stepSimulation(long space, float time, int maxSteps, float accuracy); + + public void distributeEvents() { + //add collision callbacks +// synchronized (collisionEvents) { + for (Iterator 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 Future object + */ + 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); + 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 (physicsJoint.getBodyA() instanceof PhysicsControl) { + add(physicsJoint.getBodyA()); + } else { + addRigidBody(physicsJoint.getBodyA()); + } + if (physicsJoint.getBodyA() instanceof PhysicsControl) { + add(physicsJoint.getBodyB()); + } else { + addRigidBody(physicsJoint.getBodyB()); + } + 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); + 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 (physicsJoint.getBodyA() instanceof PhysicsControl) { + remove(physicsJoint.getBodyA()); + } else { + removeRigidBody(physicsJoint.getBodyA()); + } + if (physicsJoint.getBodyA() instanceof PhysicsControl) { + remove(physicsJoint.getBodyB()); + } else { + removeRigidBody(physicsJoint.getBodyB()); + } + 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(); + removeAll(spat); + } + } + } + + private native void addCollisionObject(long space, long id); + + private native void removeCollisionObject(long space, long id); + + private native void addRigidBody(long space, long id); + + private native void removeRigidBody(long space, long id); + + private native void addCharacterObject(long space, long id); + + private native void removeCharacterObject(long space, long id); + + private native void addAction(long space, long id); + + private native void removeAction(long space, long id); + + private native void addVehicle(long space, long id); + + private native void removeVehicle(long space, long id); + + private native void addConstraint(long space, long id); + + private native void addConstraintC(long space, long id, boolean collision); + + private native void removeConstraint(long space, long id); + + private void addGhostObject(PhysicsGhostObject node) { + if (physicsGhostObjects.containsKey(node.getObjectId())) { + logger.log(Level.WARNING, "GhostObject {0} already exists in PhysicsSpace, cannot add.", node); + return; + } + physicsGhostObjects.put(node.getObjectId(), node); + logger.log(Level.FINE, "Adding ghost object {0} to physics space.", Long.toHexString(node.getObjectId())); + addCollisionObject(physicsSpaceId, node.getObjectId()); + } + + private void removeGhostObject(PhysicsGhostObject node) { + if (!physicsGhostObjects.containsKey(node.getObjectId())) { + logger.log(Level.WARNING, "GhostObject {0} does not exist in PhysicsSpace, cannot remove.", node); + return; + } + physicsGhostObjects.remove(node.getObjectId()); + logger.log(Level.FINE, "Removing ghost object {0} from physics space.", Long.toHexString(node.getObjectId())); + removeCollisionObject(physicsSpaceId, node.getObjectId()); + } + + private void addCharacter(PhysicsCharacter node) { + if (physicsCharacters.containsKey(node.getObjectId())) { + logger.log(Level.WARNING, "Character {0} already exists in PhysicsSpace, cannot add.", node); + return; + } + physicsCharacters.put(node.getObjectId(), node); + logger.log(Level.FINE, "Adding character {0} to physics space.", Long.toHexString(node.getObjectId())); + addCharacterObject(physicsSpaceId, node.getObjectId()); + addAction(physicsSpaceId, node.getControllerId()); +// dynamicsWorld.addCollisionObject(node.getObjectId(), CollisionFilterGroups.CHARACTER_FILTER, (short) (CollisionFilterGroups.STATIC_FILTER | CollisionFilterGroups.DEFAULT_FILTER)); +// dynamicsWorld.addAction(node.getControllerId()); + } + + private void removeCharacter(PhysicsCharacter node) { + if (!physicsCharacters.containsKey(node.getObjectId())) { + logger.log(Level.WARNING, "Character {0} does not exist in PhysicsSpace, cannot remove.", node); + return; + } + physicsCharacters.remove(node.getObjectId()); + logger.log(Level.FINE, "Removing character {0} from physics space.", Long.toHexString(node.getObjectId())); + removeAction(physicsSpaceId, node.getControllerId()); + removeCharacterObject(physicsSpaceId, node.getObjectId()); +// dynamicsWorld.removeAction(node.getControllerId()); +// dynamicsWorld.removeCollisionObject(node.getObjectId()); + } + + private void addRigidBody(PhysicsRigidBody node) { + if (physicsBodies.containsKey(node.getObjectId())) { + logger.log(Level.WARNING, "RigidBody {0} already exists in PhysicsSpace, cannot add.", node); + return; + } + physicsBodies.put(node.getObjectId(), node); + + //Workaround + //It seems that adding a Kinematic RigidBody to the dynamicWorld prevent it from being non kinematic again afterward. + //so we add it non kinematic, then set it kinematic again. + boolean kinematic = false; + if (node.isKinematic()) { + kinematic = true; + node.setKinematic(false); + } + addRigidBody(physicsSpaceId, node.getObjectId()); + if (kinematic) { + node.setKinematic(true); + } + + logger.log(Level.FINE, "Adding RigidBody {0} to physics space.", node.getObjectId()); + if (node instanceof PhysicsVehicle) { + logger.log(Level.FINE, "Adding vehicle constraint {0} to physics space.", Long.toHexString(((PhysicsVehicle) node).getVehicleId())); + physicsVehicles.put(((PhysicsVehicle) node).getVehicleId(), (PhysicsVehicle) node); + addVehicle(physicsSpaceId, ((PhysicsVehicle) node).getVehicleId()); + } + } + + private void removeRigidBody(PhysicsRigidBody node) { + if (!physicsBodies.containsKey(node.getObjectId())) { + logger.log(Level.WARNING, "RigidBody {0} does not exist in PhysicsSpace, cannot remove.", node); + return; + } + if (node instanceof PhysicsVehicle) { + logger.log(Level.FINE, "Removing vehicle constraint {0} from physics space.", Long.toHexString(((PhysicsVehicle) node).getVehicleId())); + physicsVehicles.remove(((PhysicsVehicle) node).getVehicleId()); + removeVehicle(physicsSpaceId, ((PhysicsVehicle) node).getVehicleId()); + } + logger.log(Level.FINE, "Removing RigidBody {0} from physics space.", Long.toHexString(node.getObjectId())); + physicsBodies.remove(node.getObjectId()); + removeRigidBody(physicsSpaceId, node.getObjectId()); + } + + private void addJoint(PhysicsJoint joint) { + if (physicsJoints.containsKey(joint.getObjectId())) { + logger.log(Level.WARNING, "Joint {0} already exists in PhysicsSpace, cannot add.", joint); + return; + } + logger.log(Level.FINE, "Adding Joint {0} to physics space.", Long.toHexString(joint.getObjectId())); + physicsJoints.put(joint.getObjectId(), joint); + addConstraintC(physicsSpaceId, joint.getObjectId(), !joint.isCollisionBetweenLinkedBodys()); +// dynamicsWorld.addConstraint(joint.getObjectId(), !joint.isCollisionBetweenLinkedBodys()); + } + + private void removeJoint(PhysicsJoint joint) { + if (!physicsJoints.containsKey(joint.getObjectId())) { + logger.log(Level.WARNING, "Joint {0} does not exist in PhysicsSpace, cannot remove.", joint); + return; + } + logger.log(Level.FINE, "Removing Joint {0} from physics space.", Long.toHexString(joint.getObjectId())); + physicsJoints.remove(joint.getObjectId()); + removeConstraint(physicsSpaceId, joint.getObjectId()); +// dynamicsWorld.removeConstraint(joint.getObjectId()); + } + + public Collection getRigidBodyList() { + return new LinkedList(physicsBodies.values()); + } + + public Collection getGhostObjectList() { + return new LinkedList(physicsGhostObjects.values()); + } + + public Collection getCharacterList() { + return new LinkedList(physicsCharacters.values()); + } + + public Collection getJointList() { + return new LinkedList(physicsJoints.values()); + } + + public Collection getVehicleList() { + return new LinkedList(physicsVehicles.values()); + } + + /** + * Sets the gravity of the PhysicsSpace, set before adding physics objects! + * + * @param gravity + */ + public void setGravity(Vector3f gravity) { + gravity.set(gravity); + setGravity(physicsSpaceId, gravity); + } + + private native void setGravity(long spaceId, Vector3f gravity); + + //TODO: getGravity + private final Vector3f gravity = new Vector3f(0,-9.81f,0); + public Vector3f getGravity(Vector3f gravity) { + return gravity.set(this.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(); + rayTest(from, to, results); + return (List) results; + } + + /** + * Sets m_flags for raytest, see https://code.google.com/p/bullet/source/browse/trunk/src/BulletCollision/NarrowPhaseCollision/btRaycastCallback.h + * for possible options. Defaults to using the faster, approximate raytest. + */ + public void SetRayTestFlags(int flags) { + rayTestFlags = flags; + } + + /** + * Gets m_flags for raytest, see https://code.google.com/p/bullet/source/browse/trunk/src/BulletCollision/NarrowPhaseCollision/btRaycastCallback.h + * for possible options. + * @return rayTestFlags + */ + public int GetRayTestFlags() { + return rayTestFlags; + } + + /** + * 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(); + rayTest_native(from, to, physicsSpaceId, results, rayTestFlags); + return results; + } + + public native void rayTest_native(Vector3f from, Vector3f to, long physicsSpaceId, List results, int flags); + +// 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.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.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() { + physicsBodies.clear(); + physicsJoints.clear(); + +// dynamicsWorld.destroy(); +// dynamicsWorld = null; + } + + /** + * // * used internally // + * + * @return the dynamicsWorld // + */ + public long getSpaceId() { + return physicsSpaceId; + } + + public BroadphaseType getBroadphaseType() { + return broadphaseType; + } + + public void setBroadphaseType(BroadphaseType broadphaseType) { + this.broadphaseType = broadphaseType; + } + + /** + * Sets the maximum amount of extra steps that will be used to step the + * physics when the fps is below the physics fps. Doing this maintains + * determinism in physics. For example a maximum number of 2 can compensate + * for framerates as low as 30fps when the physics has the default accuracy + * of 60 fps. Note that setting this value too high can make the physics + * drive down its own fps in case its overloaded. + * + * @param steps The maximum number of extra steps, default is 4. + */ + public void setMaxSubSteps(int steps) { + maxSubSteps = steps; + } + + /** + * get the current accuracy of the physics computation + * + * @return the current accuracy + */ + public float getAccuracy() { + return accuracy; + } + + /** + * sets the accuracy of the physics computation, default=1/60s
    + * + * @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. + * + * @deprecated in favor of BulletDebugAppState, use + * BulletAppState.setDebugEnabled(boolean) to add automatically + * @param manager AssetManager to use to create debug materials + */ + @Deprecated + public void enableDebug(AssetManager manager) { + debugManager = manager; + } + + /** + * Disable debug display + */ + public void disableDebug() { + debugManager = null; + } + + public AssetManager getDebugManager() { + return debugManager; + } + + public static native void initNativePhysics(); + + /** + * interface with Broadphase types + */ + public enum BroadphaseType { + + /** + * basic Broadphase + */ + SIMPLE, + /** + * better Broadphase, needs worldBounds , max Object number = 16384 + */ + AXIS_SWEEP_3, + /** + * better Broadphase, needs worldBounds , max Object number = 65536 + */ + AXIS_SWEEP_3_32, + /** + * Broadphase allowing quicker adding/removing of physics objects + */ + DBVT; + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing PhysicsSpace {0}", Long.toHexString(physicsSpaceId)); + finalizeNative(physicsSpaceId); + } + + private native void finalizeNative(long objectId); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java new file mode 100644 index 000000000..bacc21cd4 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.util.EventObject; + +/** + * A CollisionEvent stores all information about a collision in the PhysicsWorld. + * Do not store this Object, as it will be reused after the collision() method has been called. + * Get/reference all data you need in the collide method. + * @author normenhansen + */ +public class PhysicsCollisionEvent extends EventObject { + + public static final int TYPE_ADDED = 0; + public static final int TYPE_PROCESSED = 1; + public static final int TYPE_DESTROYED = 2; + private int type; + private PhysicsCollisionObject nodeA; + private PhysicsCollisionObject nodeB; + private long manifoldPointObjectId = 0; + + public PhysicsCollisionEvent(int type, PhysicsCollisionObject nodeA, PhysicsCollisionObject nodeB, long manifoldPointObjectId) { + super(nodeA); + this.type = type; + this.nodeA = nodeA; + this.nodeB = nodeB; + this.manifoldPointObjectId = manifoldPointObjectId; + } + + /** + * used by event factory, called when event is destroyed + */ + public void clean() { + source = null; + this.type = 0; + this.nodeA = null; + this.nodeB = null; + this.manifoldPointObjectId = 0; + } + + /** + * used by event factory, called when event reused + */ + public void refactor(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, long manifoldPointObjectId) { + this.source = source; + this.type = type; + this.nodeA = source; + this.nodeB = nodeB; + this.manifoldPointObjectId = manifoldPointObjectId; + } + + public int getType() { + return type; + } + + /** + * @return A Spatial if the UserObject of the PhysicsCollisionObject is a Spatial + */ + public Spatial getNodeA() { + if (nodeA.getUserObject() instanceof Spatial) { + return (Spatial) nodeA.getUserObject(); + } + return null; + } + + /** + * @return A Spatial if the UserObject of the PhysicsCollisionObject is a Spatial + */ + public Spatial getNodeB() { + if (nodeB.getUserObject() instanceof Spatial) { + return (Spatial) nodeB.getUserObject(); + } + return null; + } + + public PhysicsCollisionObject getObjectA() { + return nodeA; + } + + public PhysicsCollisionObject getObjectB() { + return nodeB; + } + + public float getAppliedImpulse() { + return getAppliedImpulse(manifoldPointObjectId); + } + private native float getAppliedImpulse(long manifoldPointObjectId); + + public float getAppliedImpulseLateral1() { + return getAppliedImpulseLateral1(manifoldPointObjectId); + } + private native float getAppliedImpulseLateral1(long manifoldPointObjectId); + + public float getAppliedImpulseLateral2() { + return getAppliedImpulseLateral2(manifoldPointObjectId); + } + private native float getAppliedImpulseLateral2(long manifoldPointObjectId); + + public float getCombinedFriction() { + return getCombinedFriction(manifoldPointObjectId); + } + private native float getCombinedFriction(long manifoldPointObjectId); + + public float getCombinedRestitution() { + return getCombinedRestitution(manifoldPointObjectId); + } + private native float getCombinedRestitution(long manifoldPointObjectId); + + public float getDistance1() { + return getDistance1(manifoldPointObjectId); + } + private native float getDistance1(long manifoldPointObjectId); + + public int getIndex0() { + return getIndex0(manifoldPointObjectId); + } + private native int getIndex0(long manifoldPointObjectId); + + public int getIndex1() { + return getIndex1(manifoldPointObjectId); + } + private native int getIndex1(long manifoldPointObjectId); + + public Vector3f getLateralFrictionDir1() { + return getLateralFrictionDir1(new Vector3f()); + } + + public Vector3f getLateralFrictionDir1(Vector3f lateralFrictionDir1) { + getLateralFrictionDir1(manifoldPointObjectId, lateralFrictionDir1); + return lateralFrictionDir1; + } + private native void getLateralFrictionDir1(long manifoldPointObjectId, Vector3f lateralFrictionDir1); + + public Vector3f getLateralFrictionDir2() { + return getLateralFrictionDir2(new Vector3f()); + } + + public Vector3f getLateralFrictionDir2(Vector3f lateralFrictionDir2) { + getLateralFrictionDir2(manifoldPointObjectId, lateralFrictionDir2); + return lateralFrictionDir2; + } + private native void getLateralFrictionDir2(long manifoldPointObjectId, Vector3f lateralFrictionDir2); + + public boolean isLateralFrictionInitialized() { + return isLateralFrictionInitialized(manifoldPointObjectId); + } + private native boolean isLateralFrictionInitialized(long manifoldPointObjectId); + + public int getLifeTime() { + return getLifeTime(manifoldPointObjectId); + } + private native int getLifeTime(long manifoldPointObjectId); + + public Vector3f getLocalPointA() { + return getLocalPointA(new Vector3f()); + } + + public Vector3f getLocalPointA(Vector3f localPointA) { + getLocalPointA(manifoldPointObjectId, localPointA); + return localPointA; + } + private native void getLocalPointA(long manifoldPointObjectId, Vector3f localPointA); + + public Vector3f getLocalPointB() { + return getLocalPointB(new Vector3f()); + } + + public Vector3f getLocalPointB(Vector3f localPointB) { + getLocalPointB(manifoldPointObjectId, localPointB); + return localPointB; + } + private native void getLocalPointB(long manifoldPointObjectId, Vector3f localPointB); + + public Vector3f getNormalWorldOnB() { + return getNormalWorldOnB(new Vector3f()); + } + + public Vector3f getNormalWorldOnB(Vector3f normalWorldOnB) { + getNormalWorldOnB(manifoldPointObjectId, normalWorldOnB); + return normalWorldOnB; + } + private native void getNormalWorldOnB(long manifoldPointObjectId, Vector3f normalWorldOnB); + + public int getPartId0() { + return getPartId0(manifoldPointObjectId); + } + private native int getPartId0(long manifoldPointObjectId); + + public int getPartId1() { + return getPartId1(manifoldPointObjectId); + } + + private native int getPartId1(long manifoldPointObjectId); + + public Vector3f getPositionWorldOnA() { + return getPositionWorldOnA(new Vector3f()); + } + + public Vector3f getPositionWorldOnA(Vector3f positionWorldOnA) { + getPositionWorldOnA(positionWorldOnA); + return positionWorldOnA; + } + private native void getPositionWorldOnA(long manifoldPointObjectId, Vector3f positionWorldOnA); + + public Vector3f getPositionWorldOnB() { + return getPositionWorldOnB(new Vector3f()); + } + + public Vector3f getPositionWorldOnB(Vector3f positionWorldOnB) { + getPositionWorldOnB(manifoldPointObjectId, positionWorldOnB); + return positionWorldOnB; + } + private native void getPositionWorldOnB(long manifoldPointObjectId, Vector3f positionWorldOnB); + +// public Object getUserPersistentData() { +// return userPersistentData; +// } +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java new file mode 100644 index 000000000..8c328a15f --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * + * @author normenhansen + */ +public class PhysicsCollisionEventFactory { + + private ConcurrentLinkedQueue eventBuffer = new ConcurrentLinkedQueue(); + + public PhysicsCollisionEvent getEvent(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, long manifoldPointObjectId) { + PhysicsCollisionEvent event = eventBuffer.poll(); + if (event == null) { + event = new PhysicsCollisionEvent(type, source, nodeB, manifoldPointObjectId); + }else{ + event.refactor(type, source, nodeB, manifoldPointObjectId); + } + return event; + } + + public void recycle(PhysicsCollisionEvent event) { + event.clean(); + eventBuffer.add(event); + } +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java new file mode 100644 index 000000000..c9299f2d0 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.export.*; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Base class for collision objects (PhysicsRigidBody, PhysicsGhostObject) + * @author normenhansen + */ +public abstract class PhysicsCollisionObject implements Savable { + + protected long objectId = 0; + protected 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; + } + + /** + * @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 The collision group + */ + 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; + if (objectId != 0) { + setCollisionGroup(objectId, 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; + if (objectId != 0) { + setCollideWithGroups(objectId, this.collisionGroupsMask); + } + } + + /** + * Remove a group from the list this object collides with. + * @param collisionGroup + */ + public void removeCollideWithGroup(int collisionGroup) { + this.collisionGroupsMask = this.collisionGroupsMask & ~collisionGroup; + if (objectId != 0) { + setCollideWithGroups(this.collisionGroupsMask); + } + } + + /** + * Directly set the bitmask for collision groups that this object collides with. + * @param collisionGroups + */ + public void setCollideWithGroups(int collisionGroups) { + this.collisionGroupsMask = collisionGroups; + if (objectId != 0) { + setCollideWithGroups(objectId, this.collisionGroupsMask); + } + } + + /** + * Gets the bitmask of collision groups that this object collides with. + * @return Collision groups mask + */ + public int getCollideWithGroups() { + return collisionGroupsMask; + } + + protected void initUserPointer() { + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "initUserPointer() objectId = {0}", Long.toHexString(objectId)); + initUserPointer(objectId, collisionGroup, collisionGroupsMask); + } + native void initUserPointer(long objectId, int group, int groups); + + /** + * @return the userObject + */ + public Object getUserObject() { + return userObject; + } + + /** + * @param userObject the userObject to set + */ + public void setUserObject(Object userObject) { + this.userObject = userObject; + } + + public long getObjectId(){ + return objectId; + } + + protected native void attachCollisionShape(long objectId, long collisionShapeId); + native void setCollisionGroup(long objectId, int collisionGroup); + native void setCollideWithGroups(long objectId, int collisionGroups); + + @Override + public void write(JmeExporter e) throws IOException { + OutputCapsule capsule = e.getCapsule(this); + capsule.write(collisionGroup, "collisionGroup", 0x00000001); + capsule.write(collisionGroupsMask, "collisionGroupsMask", 0x00000001); + capsule.write(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); + CollisionShape shape = (CollisionShape) capsule.readSavable("collisionShape", null); + collisionShape = shape; + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing CollisionObject {0}", Long.toHexString(objectId)); + finalizeNative(objectId); + } + + protected native void finalizeNative(long objectId); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java new file mode 100644 index 000000000..bbc231fa7 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +import com.jme3.math.Vector3f; + +/** + * Contains the results of a PhysicsSpace rayTest + * bulletAppState.getPhysicsSpace().rayTest(new Vector3f(0,1000,0),new Vector3f(0,-1000,0)); + javap -s java.util.List + * @author Empire-Phoenix,normenhansen + */ +public class PhysicsRayTestResult { + + private PhysicsCollisionObject collisionObject; + private Vector3f hitNormalLocal; + private float hitFraction; + private boolean normalInWorldSpace = true; + + /** + * allocated by native code only + */ + private PhysicsRayTestResult() { + } + + /** + * @return the collisionObject + */ + public PhysicsCollisionObject getCollisionObject() { + return collisionObject; + } + + /** + * @return the hitNormalLocal + */ + public Vector3f getHitNormalLocal() { + return hitNormalLocal; + } + + /** + * @return the hitFraction + */ + public float getHitFraction() { + return hitFraction; + } + + /** + * @return the normalInWorldSpace + */ + public boolean isNormalInWorldSpace() { + return normalInWorldSpace; + } +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsSweepTestResult.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsSweepTestResult.java new file mode 100644 index 000000000..8e61b66a2 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsSweepTestResult.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +import com.jme3.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/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java new file mode 100644 index 000000000..000bb2dc7 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Basic box collision shape + * @author normenhansen + */ +public class BoxCollisionShape extends CollisionShape { + + private Vector3f halfExtents; + + public BoxCollisionShape() { + } + + /** + * creates a collision box from the given halfExtents + * @param halfExtents the halfExtents of the CollisionBox + */ + public BoxCollisionShape(Vector3f halfExtents) { + this.halfExtents = halfExtents; + createShape(); + } + + public final Vector3f getHalfExtents() { + return halfExtents; + } + + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(halfExtents, "halfExtents", new Vector3f(1, 1, 1)); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + Vector3f halfExtents = (Vector3f) capsule.readSavable("halfExtents", new Vector3f(1, 1, 1)); + this.halfExtents = halfExtents; + createShape(); + } + + protected void createShape() { + objectId = createShape(halfExtents); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); +// cShape = new BoxShape(Converter.convert(halfExtents)); + setScale(scale); + setMargin(margin); + } + + private native long createShape(Vector3f halfExtents); + +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java new file mode 100644 index 000000000..525e2e281 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Basic capsule collision shape + * @author normenhansen + */ +public class CapsuleCollisionShape extends CollisionShape{ + protected float radius,height; + protected int axis; + + public CapsuleCollisionShape() { + } + + /** + * creates a new CapsuleCollisionShape with the given radius and height + * @param radius the radius of the capsule + * @param height the height of the capsule + */ + public CapsuleCollisionShape(float radius, float height) { + this.radius=radius; + this.height=height; + this.axis=1; + createShape(); + } + + /** + * creates a capsule shape around the given axis (0=X,1=Y,2=Z) + * @param radius + * @param height + * @param axis + */ + public CapsuleCollisionShape(float radius, float height, int axis) { + this.radius=radius; + this.height=height; + this.axis=axis; + createShape(); + } + + public float getRadius() { + return radius; + } + + public float getHeight() { + return height; + } + + public int getAxis() { + return axis; + } + + /** + * WARNING - CompoundCollisionShape scaling has no effect. + */ + @Override + public void setScale(Vector3f scale) { + Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "CapsuleCollisionShape cannot be scaled"); + } + + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(radius, "radius", 0.5f); + capsule.write(height, "height", 1); + capsule.write(axis, "axis", 1); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + radius = capsule.readFloat("radius", 0.5f); + height = capsule.readFloat("height", 0.5f); + axis = capsule.readInt("axis", 1); + createShape(); + } + + protected void createShape(){ + objectId = createShape(axis, radius, height); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); + setScale(scale); + setMargin(margin); +// switch(axis){ +// case 0: +// objectId=new CapsuleShapeX(radius,height); +// break; +// case 1: +// objectId=new CapsuleShape(radius,height); +// break; +// case 2: +// objectId=new CapsuleShapeZ(radius,height); +// break; +// } +// objectId.setLocalScaling(Converter.convert(getScale())); +// objectId.setMargin(margin); + } + + private native long createShape(int axis, float radius, float height); + +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java new file mode 100644 index 000000000..c5b390e0e --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.export.*; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This Object holds information about a jbullet CollisionShape to be able to reuse + * CollisionShapes (as suggested in bullet manuals) + * TODO: add static methods to create shapes from nodes (like jbullet-jme constructor) + * @author normenhansen + */ +public abstract class CollisionShape implements Savable { + + protected long objectId = 0; + protected Vector3f scale = new Vector3f(1, 1, 1); + protected float margin = 0.0f; + + public CollisionShape() { + } + +// /** +// * used internally, not safe +// */ +// public void calculateLocalInertia(long objectId, float mass) { +// if (this.objectId == 0) { +// return; +// } +//// if (this instanceof MeshCollisionShape) { +//// vector.set(0, 0, 0); +//// } else { +// calculateLocalInertia(objectId, this.objectId, mass); +//// objectId.calculateLocalInertia(mass, vector); +//// } +// } +// +// private native void calculateLocalInertia(long objectId, long shapeId, float mass); + + /** + * used internally + */ + public long getObjectId() { + return objectId; + } + + /** + * used internally + */ + public void setObjectId(long id) { + this.objectId = id; + } + + public void setScale(Vector3f scale) { + this.scale.set(scale); + setLocalScaling(objectId, scale); + } + + public Vector3f getScale() { + return scale; + } + + public float getMargin() { + return getMargin(objectId); + } + + private native float getMargin(long objectId); + + public void setMargin(float margin) { + setMargin(objectId, margin); + this.margin = margin; + } + + private native void setLocalScaling(long obectId, Vector3f scale); + + private native void setMargin(long objectId, float margin); + + public void write(JmeExporter ex) throws IOException { + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(scale, "scale", new Vector3f(1, 1, 1)); + capsule.write(getMargin(), "margin", 0.0f); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule capsule = im.getCapsule(this); + this.scale = (Vector3f) capsule.readSavable("scale", new Vector3f(1, 1, 1)); + this.margin = capsule.readFloat("margin", 0.0f); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing CollisionShape {0}", Long.toHexString(objectId)); + finalizeNative(objectId); + } + + private native void finalizeNative(long objectId); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java new file mode 100644 index 000000000..9faf7f68b --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A CompoundCollisionShape allows combining multiple base shapes + * to generate a more sophisticated shape. + * @author normenhansen + */ +public class CompoundCollisionShape extends CollisionShape { + + protected ArrayList children = new ArrayList(); + + public CompoundCollisionShape() { + objectId = createShape();//new CompoundShape(); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); + } + + /** + * adds a child shape at the given local translation + * @param shape the child shape to add + * @param location the local location of the child shape + */ + public void addChildShape(CollisionShape shape, Vector3f location) { +// Transform transA = new Transform(Converter.convert(new Matrix3f())); +// Converter.convert(location, transA.origin); +// children.add(new ChildCollisionShape(location.clone(), new Matrix3f(), shape)); +// ((CompoundShape) objectId).addChildShape(transA, shape.getObjectId()); + addChildShape(shape, location, new Matrix3f()); + } + + /** + * adds a child shape at the given local translation + * @param shape the child shape to add + * @param location the local location of the child shape + */ + public void addChildShape(CollisionShape shape, Vector3f location, Matrix3f rotation) { + if(shape instanceof CompoundCollisionShape){ + throw new IllegalStateException("CompoundCollisionShapes cannot have CompoundCollisionShapes as children!"); + } +// Transform transA = new Transform(Converter.convert(rotation)); +// Converter.convert(location, transA.origin); +// Converter.convert(rotation, transA.basis); + children.add(new ChildCollisionShape(location.clone(), rotation.clone(), shape)); + addChildShape(objectId, shape.getObjectId(), location, rotation); +// ((CompoundShape) objectId).addChildShape(transA, shape.getObjectId()); + } + + private void addChildShapeDirect(CollisionShape shape, Vector3f location, Matrix3f rotation) { + if(shape instanceof CompoundCollisionShape){ + throw new IllegalStateException("CompoundCollisionShapes cannot have CompoundCollisionShapes as children!"); + } +// Transform transA = new Transform(Converter.convert(rotation)); +// Converter.convert(location, transA.origin); +// Converter.convert(rotation, transA.basis); + addChildShape(objectId, shape.getObjectId(), location, rotation); +// ((CompoundShape) objectId).addChildShape(transA, shape.getObjectId()); + } + + /** + * removes a child shape + * @param shape the child shape to remove + */ + public void removeChildShape(CollisionShape shape) { + removeChildShape(objectId, shape.getObjectId()); +// ((CompoundShape) objectId).removeChildShape(shape.getObjectId()); + for (Iterator 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"); + } + + private native long createShape(); + + private native long addChildShape(long objectId, long childId, Vector3f location, Matrix3f rotation); + + private native long removeChildShape(long objectId, long childId); + + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.writeSavableArrayList(children, "children", new ArrayList()); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + children = capsule.readSavableArrayList("children", new ArrayList()); + setScale(scale); + 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/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java new file mode 100644 index 000000000..bed5caaee --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author normenhansen + */ +public class ConeCollisionShape extends CollisionShape { + + protected float radius; + protected float height; + protected int axis; + + public ConeCollisionShape() { + } + + public ConeCollisionShape(float radius, float height, int axis) { + this.radius = radius; + this.height = height; + this.axis = axis; + createShape(); + } + + public ConeCollisionShape(float radius, float height) { + this.radius = radius; + this.height = height; + 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", PhysicsSpace.AXIS_Y); + } + + 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", PhysicsSpace.AXIS_Y); + createShape(); + } + + protected void createShape() { + objectId = createShape(axis, radius, height); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); +// if (axis == PhysicsSpace.AXIS_X) { +// objectId = new ConeShapeX(radius, height); +// } else if (axis == PhysicsSpace.AXIS_Y) { +// objectId = new ConeShape(radius, height); +// } else if (axis == PhysicsSpace.AXIS_Z) { +// objectId = new ConeShapeZ(radius, height); +// } +// objectId.setLocalScaling(Converter.convert(getScale())); +// objectId.setMargin(margin); + setScale(scale); + setMargin(margin); + } + + private native long createShape(int axis, float radius, float height); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java new file mode 100644 index 000000000..35edb8449 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Basic cylinder collision shape + * @author normenhansen + */ +public class CylinderCollisionShape extends CollisionShape { + + protected Vector3f halfExtents; + protected int axis; + + public CylinderCollisionShape() { + } + + /** + * creates a cylinder shape from the given halfextents + * @param halfExtents the halfextents to use + */ + public CylinderCollisionShape(Vector3f halfExtents) { + this.halfExtents = halfExtents; + this.axis = 2; + createShape(); + } + + /** + * Creates a cylinder shape around the given axis from the given halfextents + * @param halfExtents the halfextents to use + * @param axis (0=X,1=Y,2=Z) + */ + public CylinderCollisionShape(Vector3f halfExtents, int axis) { + this.halfExtents = halfExtents; + this.axis = axis; + createShape(); + } + + public final Vector3f getHalfExtents() { + return halfExtents; + } + + public int getAxis() { + return axis; + } + + /** + * WARNING - CompoundCollisionShape scaling has no effect. + */ + @Override + public void setScale(Vector3f scale) { + Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "CylinderCollisionShape cannot be scaled"); + } + + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(halfExtents, "halfExtents", new Vector3f(0.5f, 0.5f, 0.5f)); + capsule.write(axis, "axis", 1); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + halfExtents = (Vector3f) capsule.readSavable("halfExtents", new Vector3f(0.5f, 0.5f, 0.5f)); + axis = capsule.readInt("axis", 1); + createShape(); + } + + protected void createShape() { + objectId = createShape(axis, halfExtents); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); +// switch (axis) { +// case 0: +// objectId = new CylinderShapeX(Converter.convert(halfExtents)); +// break; +// case 1: +// objectId = new CylinderShape(Converter.convert(halfExtents)); +// break; +// case 2: +// objectId = new CylinderShapeZ(Converter.convert(halfExtents)); +// break; +// } +// objectId.setLocalScaling(Converter.convert(getScale())); +// objectId.setMargin(margin); + setScale(scale); + setMargin(margin); + } + + private native long createShape(int axis, Vector3f halfExtents); + +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java new file mode 100644 index 000000000..200e6dc3b --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.bullet.util.NativeMeshUtil; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Basic mesh collision shape + * @author normenhansen + */ +public class GImpactCollisionShape extends CollisionShape { + +// protected Vector3f worldScale; + protected int numVertices, numTriangles, vertexStride, triangleIndexStride; + protected ByteBuffer triangleIndexBase, vertexBase; + protected long meshId = 0; +// protected IndexedMesh bulletMesh; + + public GImpactCollisionShape() { + } + + /** + * creates a collision shape from the given Mesh + * @param mesh the Mesh to use + */ + public GImpactCollisionShape(Mesh mesh) { + createCollisionMesh(mesh); + } + + private void createCollisionMesh(Mesh mesh) { + triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4); + vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4); +// triangleIndexBase = ByteBuffer.allocate(mesh.getTriangleCount() * 3 * 4); +// vertexBase = ByteBuffer.allocate(mesh.getVertexCount() * 3 * 4); + numVertices = mesh.getVertexCount(); + vertexStride = 12; //3 verts * 4 bytes per. + numTriangles = mesh.getTriangleCount(); + triangleIndexStride = 12; //3 index entries * 4 bytes each. + + IndexBuffer indices = mesh.getIndicesAsList(); + FloatBuffer vertices = mesh.getFloatBuffer(Type.Position); + vertices.rewind(); + + int verticesLength = mesh.getVertexCount() * 3; + for (int i = 0; i < verticesLength; i++) { + float tempFloat = vertices.get(); + vertexBase.putFloat(tempFloat); + } + + int indicesLength = mesh.getTriangleCount() * 3; + for (int i = 0; i < indicesLength; i++) { + triangleIndexBase.putInt(indices.get(i)); + } + vertices.rewind(); + vertices.clear(); + + createShape(); + } + +// /** +// * creates a jme mesh from the collision shape, only needed for debugging +// */ +// public Mesh createJmeMesh() { +// return Converter.convert(bulletMesh); +// } + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); +// capsule.write(worldScale, "worldScale", new Vector3f(1, 1, 1)); + capsule.write(numVertices, "numVertices", 0); + capsule.write(numTriangles, "numTriangles", 0); + capsule.write(vertexStride, "vertexStride", 0); + capsule.write(triangleIndexStride, "triangleIndexStride", 0); + + capsule.write(triangleIndexBase.array(), "triangleIndexBase", new byte[0]); + capsule.write(vertexBase.array(), "vertexBase", new byte[0]); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); +// worldScale = (Vector3f) capsule.readSavable("worldScale", new Vector3f(1, 1, 1)); + numVertices = capsule.readInt("numVertices", 0); + numTriangles = capsule.readInt("numTriangles", 0); + vertexStride = capsule.readInt("vertexStride", 0); + triangleIndexStride = capsule.readInt("triangleIndexStride", 0); + + triangleIndexBase = ByteBuffer.wrap(capsule.readByteArray("triangleIndexBase", new byte[0])); + vertexBase = ByteBuffer.wrap(capsule.readByteArray("vertexBase", new byte[0])); + createShape(); + } + + protected void createShape() { +// bulletMesh = new IndexedMesh(); +// bulletMesh.numVertices = numVertices; +// bulletMesh.numTriangles = numTriangles; +// bulletMesh.vertexStride = vertexStride; +// bulletMesh.triangleIndexStride = triangleIndexStride; +// bulletMesh.triangleIndexBase = triangleIndexBase; +// bulletMesh.vertexBase = vertexBase; +// bulletMesh.triangleIndexBase = triangleIndexBase; +// TriangleIndexVertexArray tiv = new TriangleIndexVertexArray(numTriangles, triangleIndexBase, triangleIndexStride, numVertices, vertexBase, vertexStride); +// objectId = new GImpactMeshShape(tiv); +// objectId.setLocalScaling(Converter.convert(worldScale)); +// ((GImpactMeshShape)objectId).updateBound(); +// objectId.setLocalScaling(Converter.convert(getScale())); +// objectId.setMargin(margin); + meshId = NativeMeshUtil.createTriangleIndexVertexArray(triangleIndexBase, vertexBase, numTriangles, numVertices, vertexStride, triangleIndexStride); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Mesh {0}", Long.toHexString(meshId)); + objectId = createShape(meshId); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); + setScale(scale); + setMargin(margin); + } + + private native long createShape(long meshId); + + @Override + protected void finalize() throws Throwable { + super.finalize(); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Mesh {0}", Long.toHexString(meshId)); + finalizeNative(meshId); + } + + private native void finalizeNative(long objectId); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java new file mode 100644 index 000000000..e525d3f1c --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Uses Bullet Physics Heightfield terrain collision system. This is MUCH faster + * than using a regular mesh. + * There are a couple tricks though: + * -No rotation or translation is supported. + * -The collision bbox must be centered around 0,0,0 with the height above and below the y-axis being + * equal on either side. If not, the whole collision box is shifted vertically and things don't collide + * as they should. + * + * @author Brent Owens + */ +public class HeightfieldCollisionShape extends CollisionShape { + + protected int heightStickWidth; + protected int heightStickLength; + protected float[] heightfieldData; + protected float heightScale; + protected float minHeight; + protected float maxHeight; + protected int upAxis; + protected boolean flipQuadEdges; + protected ByteBuffer bbuf; +// protected FloatBuffer fbuf; + + public HeightfieldCollisionShape() { + } + + public HeightfieldCollisionShape(float[] heightmap) { + createCollisionHeightfield(heightmap, Vector3f.UNIT_XYZ); + } + + public HeightfieldCollisionShape(float[] heightmap, Vector3f scale) { + createCollisionHeightfield(heightmap, scale); + } + + protected void createCollisionHeightfield(float[] heightmap, Vector3f worldScale) { + this.scale = worldScale; + this.heightScale = 1;//don't change away from 1, we use worldScale instead to scale + + this.heightfieldData = heightmap; + + float min = heightfieldData[0]; + float max = heightfieldData[0]; + // calculate min and max height + for (int i = 0; i < heightfieldData.length; i++) { + if (heightfieldData[i] < min) { + min = heightfieldData[i]; + } + if (heightfieldData[i] > max) { + max = heightfieldData[i]; + } + } + // we need to center the terrain collision box at 0,0,0 for BulletPhysics. And to do that we need to set the + // min and max height to be equal on either side of the y axis, otherwise it gets shifted and collision is incorrect. + if (max < 0) { + max = -min; + } else { + if (Math.abs(max) > Math.abs(min)) { + min = -max; + } else { + max = -min; + } + } + this.minHeight = min; + this.maxHeight = max; + + this.upAxis = 1; + this.flipQuadEdges = false; + + heightStickWidth = (int) FastMath.sqrt(heightfieldData.length); + heightStickLength = heightStickWidth; + + + createShape(); + } + + protected void createShape() { + bbuf = BufferUtils.createByteBuffer(heightfieldData.length * 4); +// fbuf = bbuf.asFloatBuffer();//FloatBuffer.wrap(heightfieldData); +// fbuf.rewind(); +// fbuf.put(heightfieldData); + for (int i = 0; i < heightfieldData.length; i++) { + float f = heightfieldData[i]; + bbuf.putFloat(f); + } +// fbuf.rewind(); + objectId = createShape(heightStickWidth, heightStickLength, bbuf, heightScale, minHeight, maxHeight, upAxis, flipQuadEdges); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); + setScale(scale); + setMargin(margin); + } + + private native long createShape(int heightStickWidth, int heightStickLength, ByteBuffer heightfieldData, float heightScale, float minHeight, float maxHeight, int upAxis, boolean flipQuadEdges); + + public Mesh createJmeMesh() { + //TODO return Converter.convert(bulletMesh); + return null; + } + + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(heightStickWidth, "heightStickWidth", 0); + capsule.write(heightStickLength, "heightStickLength", 0); + capsule.write(heightScale, "heightScale", 0); + capsule.write(minHeight, "minHeight", 0); + capsule.write(maxHeight, "maxHeight", 0); + capsule.write(upAxis, "upAxis", 1); + capsule.write(heightfieldData, "heightfieldData", new float[0]); + capsule.write(flipQuadEdges, "flipQuadEdges", false); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + heightStickWidth = capsule.readInt("heightStickWidth", 0); + heightStickLength = capsule.readInt("heightStickLength", 0); + heightScale = capsule.readFloat("heightScale", 0); + minHeight = capsule.readFloat("minHeight", 0); + maxHeight = capsule.readFloat("maxHeight", 0); + upAxis = capsule.readInt("upAxis", 1); + heightfieldData = capsule.readFloatArray("heightfieldData", new float[0]); + flipQuadEdges = capsule.readBoolean("flipQuadEdges", false); + createShape(); + } +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java new file mode 100644 index 000000000..db40eedac --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class HullCollisionShape extends CollisionShape { + + private float[] points; +// protected FloatBuffer fbuf; + + public HullCollisionShape() { + } + + public HullCollisionShape(Mesh mesh) { + this.points = getPoints(mesh); + createShape(); + } + + public HullCollisionShape(float[] points) { + this.points = points; + createShape(); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(points, "points", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + + // for backwards compatability + Mesh mesh = (Mesh) capsule.readSavable("hullMesh", null); + if (mesh != null) { + this.points = getPoints(mesh); + } else { + this.points = capsule.readFloatArray("points", null); + + } +// fbuf = ByteBuffer.allocateDirect(points.length * 4).asFloatBuffer(); +// fbuf.put(points); +// fbuf = FloatBuffer.wrap(points).order(ByteOrder.nativeOrder()).asFloatBuffer(); + createShape(); + } + + protected void createShape() { +// ObjectArrayList pointList = new ObjectArrayList(); +// for (int i = 0; i < points.length; i += 3) { +// pointList.add(new Vector3f(points[i], points[i + 1], points[i + 2])); +// } +// objectId = new ConvexHullShape(pointList); +// objectId.setLocalScaling(Converter.convert(getScale())); +// objectId.setMargin(margin); + ByteBuffer bbuf=BufferUtils.createByteBuffer(points.length * 4); +// fbuf = bbuf.asFloatBuffer(); +// fbuf.rewind(); +// fbuf.put(points); + for (int i = 0; i < points.length; i++) { + float f = points[i]; + bbuf.putFloat(f); + } + bbuf.rewind(); + objectId = createShape(bbuf); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); + setScale(scale); + setMargin(margin); + } + + private native long createShape(ByteBuffer points); + + protected float[] getPoints(Mesh mesh) { + FloatBuffer vertices = mesh.getFloatBuffer(Type.Position); + vertices.rewind(); + int components = mesh.getVertexCount() * 3; + float[] pointsArray = new float[components]; + for (int i = 0; i < components; i += 3) { + pointsArray[i] = vertices.get(); + pointsArray[i + 1] = vertices.get(); + pointsArray[i + 2] = vertices.get(); + } + return pointsArray; + } +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java new file mode 100644 index 000000000..9f36d28a8 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.bullet.util.NativeMeshUtil; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.BufferUtils; + +/** + * Basic mesh collision shape + * + * @author normenhansen + */ +public class MeshCollisionShape extends CollisionShape { + + private static final String VERTEX_BASE = "vertexBase"; + private static final String TRIANGLE_INDEX_BASE = "triangleIndexBase"; + private static final String TRIANGLE_INDEX_STRIDE = "triangleIndexStride"; + private static final String VERTEX_STRIDE = "vertexStride"; + private static final String NUM_TRIANGLES = "numTriangles"; + private static final String NUM_VERTICES = "numVertices"; + private static final String NATIVE_BVH = "nativeBvh"; + protected int numVertices, numTriangles, vertexStride, triangleIndexStride; + protected ByteBuffer triangleIndexBase, vertexBase; + protected long meshId = 0; + protected long nativeBVHBuffer = 0; + private boolean memoryOptimized; + + public MeshCollisionShape() { + } + + /** + * Creates a collision shape from the given Mesh. + * Default behavior, more optimized for memory usage. + * + * @param mesh + */ + public MeshCollisionShape(Mesh mesh) { + this(mesh, true); + } + + /** + * Creates a collision shape from the given Mesh. + * memoryOptimized determines if optimized instead of + * quantized BVH will be used. + * Internally, memoryOptimized BVH is slower to calculate (~4x) + * but also smaller (~0.5x). + * It is preferable to use the memory optimized version and then serialize + * the resulting MeshCollisionshape as this will also save the + * generated BVH. + * An exception can be procedurally / generated collision shapes, where + * the generation time is more of a concern + * + * @param mesh the Mesh to use + * @param memoryOptimized True to generate a memory optimized BVH, + * false to generate quantized BVH. + */ + public MeshCollisionShape(final Mesh mesh, final boolean memoryOptimized) { + this.memoryOptimized = memoryOptimized; + this.createCollisionMesh(mesh); + } + + /** + * Advanced constructor, usually you don’t want to use this, but the Mesh + * based one. Passing false values can lead to a crash, use at own risk + * + * This constructor bypasses all copy logic normally used, this allows for + * faster bullet shape generation when using procedurally generated Meshes. + * + * + * @param indices the raw index buffer + * @param vertices the raw vertex buffer + * @param memoryOptimized use quantisize BVH, uses less memory, but slower + */ + public MeshCollisionShape(ByteBuffer indices, ByteBuffer vertices, boolean memoryOptimized) { + this.triangleIndexBase = indices; + this.vertexBase = vertices; + this.numVertices = vertices.limit() / 4 / 3; + this.numTriangles = this.triangleIndexBase.limit() / 4 / 3; + this.vertexStride = 12; + this.triangleIndexStride = 12; + this.memoryOptimized = memoryOptimized; + this.createShape(true); + } + + private void createCollisionMesh(Mesh mesh) { + this.triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4); + this.vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4); + this.numVertices = mesh.getVertexCount(); + this.vertexStride = 12; // 3 verts * 4 bytes per. + this.numTriangles = mesh.getTriangleCount(); + this.triangleIndexStride = 12; // 3 index entries * 4 bytes each. + + IndexBuffer indices = mesh.getIndicesAsList(); + FloatBuffer vertices = mesh.getFloatBuffer(Type.Position); + vertices.rewind(); + + int verticesLength = mesh.getVertexCount() * 3; + for (int i = 0; i < verticesLength; i++) { + float tempFloat = vertices.get(); + vertexBase.putFloat(tempFloat); + } + + int indicesLength = mesh.getTriangleCount() * 3; + for (int i = 0; i < indicesLength; i++) { + triangleIndexBase.putInt(indices.get(i)); + } + vertices.rewind(); + vertices.clear(); + + this.createShape(true); + } + + @Override + public void write(final JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(numVertices, MeshCollisionShape.NUM_VERTICES, 0); + capsule.write(numTriangles, MeshCollisionShape.NUM_TRIANGLES, 0); + capsule.write(vertexStride, MeshCollisionShape.VERTEX_STRIDE, 0); + capsule.write(triangleIndexStride, MeshCollisionShape.TRIANGLE_INDEX_STRIDE, 0); + + triangleIndexBase.position(0); + byte[] triangleIndexBasearray = new byte[triangleIndexBase.limit()]; + triangleIndexBase.get(triangleIndexBasearray); + capsule.write(triangleIndexBasearray, MeshCollisionShape.TRIANGLE_INDEX_BASE, null); + + vertexBase.position(0); + byte[] vertexBaseArray = new byte[vertexBase.limit()]; + vertexBase.get(vertexBaseArray); + capsule.write(vertexBaseArray, MeshCollisionShape.VERTEX_BASE, null); + + if (memoryOptimized) { + byte[] data = saveBVH(objectId); + capsule.write(data, MeshCollisionShape.NATIVE_BVH, null); + } + } + + @Override + public void read(final JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + this.numVertices = capsule.readInt(MeshCollisionShape.NUM_VERTICES, 0); + this.numTriangles = capsule.readInt(MeshCollisionShape.NUM_TRIANGLES, 0); + this.vertexStride = capsule.readInt(MeshCollisionShape.VERTEX_STRIDE, 0); + this.triangleIndexStride = capsule.readInt(MeshCollisionShape.TRIANGLE_INDEX_STRIDE, 0); + + this.triangleIndexBase = BufferUtils.createByteBuffer(capsule.readByteArray(MeshCollisionShape.TRIANGLE_INDEX_BASE, null)); + this.vertexBase = BufferUtils.createByteBuffer(capsule.readByteArray(MeshCollisionShape.VERTEX_BASE, null)); + + byte[] nativeBvh = capsule.readByteArray(MeshCollisionShape.NATIVE_BVH, null); + if (nativeBvh == null) { + // Either using non memory optimized BVH or old J3O file + memoryOptimized = false; + createShape(true); + } else { + // Using memory optimized BVH, load from J3O, then assign it. + memoryOptimized = true; + createShape(false); + nativeBVHBuffer = setBVH(nativeBvh, this.objectId); + } + } + + private void createShape(boolean buildBvt) { + this.meshId = NativeMeshUtil.createTriangleIndexVertexArray(this.triangleIndexBase, this.vertexBase, this.numTriangles, this.numVertices, this.vertexStride, this.triangleIndexStride); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Mesh {0}", Long.toHexString(this.meshId)); + this.objectId = createShape(memoryOptimized, buildBvt, this.meshId); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(this.objectId)); + this.setScale(this.scale); + this.setMargin(this.margin); + } + + /** + * returns the pointer to the native buffer used by the in place + * de-serialized shape, must be freed when not used anymore! + */ + private native long setBVH(byte[] buffer, long objectid); + + private native byte[] saveBVH(long objectId); + + private native long createShape(boolean memoryOptimized, boolean buildBvt, long meshId); + + @Override + public void finalize() throws Throwable { + super.finalize(); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Mesh {0}", Long.toHexString(this.meshId)); + if (this.meshId > 0) { + this.finalizeNative(this.meshId, this.nativeBVHBuffer); + } + } + + private native void finalizeNative(long objectId, long nativeBVHBuffer); +} \ No newline at end of file diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java new file mode 100644 index 000000000..caf654899 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Plane; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author normenhansen + */ +public class PlaneCollisionShape extends CollisionShape{ + private Plane plane; + + public PlaneCollisionShape() { + } + + /** + * Creates a plane Collision shape + * @param plane the plane that defines the shape + */ + public PlaneCollisionShape(Plane plane) { + this.plane = plane; + createShape(); + } + + public final Plane getPlane() { + return plane; + } + + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(plane, "collisionPlane", new Plane()); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + plane = (Plane) capsule.readSavable("collisionPlane", new Plane()); + createShape(); + } + + protected void createShape() { + objectId = createShape(plane.getNormal(), plane.getConstant()); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); +// objectId = new StaticPlaneShape(Converter.convert(plane.getNormal()),plane.getConstant()); +// objectId.setLocalScaling(Converter.convert(getScale())); +// objectId.setMargin(margin); + setScale(scale); + setMargin(margin); + } + + private native long createShape(Vector3f normal, float constant); + +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java new file mode 100644 index 000000000..fff1c7058 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A simple point, line, triangle or quad collisionShape based on one to four points- + * @author normenhansen + */ +public class SimplexCollisionShape extends CollisionShape { + + private Vector3f vector1, vector2, vector3, vector4; + + public SimplexCollisionShape() { + } + + public SimplexCollisionShape(Vector3f point1, Vector3f point2, Vector3f point3, Vector3f point4) { + vector1 = point1; + vector2 = point2; + vector3 = point3; + vector4 = point4; + createShape(); + } + + public SimplexCollisionShape(Vector3f point1, Vector3f point2, Vector3f point3) { + vector1 = point1; + vector2 = point2; + vector3 = point3; + createShape(); + } + + public SimplexCollisionShape(Vector3f point1, Vector3f point2) { + vector1 = point1; + vector2 = point2; + createShape(); + } + + public SimplexCollisionShape(Vector3f point1) { + vector1 = point1; + createShape(); + } + + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(vector1, "simplexPoint1", null); + capsule.write(vector2, "simplexPoint2", null); + capsule.write(vector3, "simplexPoint3", null); + capsule.write(vector4, "simplexPoint4", null); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + vector1 = (Vector3f) capsule.readSavable("simplexPoint1", null); + vector2 = (Vector3f) capsule.readSavable("simplexPoint2", null); + vector3 = (Vector3f) capsule.readSavable("simplexPoint3", null); + vector4 = (Vector3f) capsule.readSavable("simplexPoint4", null); + createShape(); + } + + protected void createShape() { + if (vector4 != null) { + objectId = createShape(vector1, vector2, vector3, vector4); +// objectId = new BU_Simplex1to4(Converter.convert(vector1), Converter.convert(vector2), Converter.convert(vector3), Converter.convert(vector4)); + } else if (vector3 != null) { + objectId = createShape(vector1, vector2, vector3); +// objectId = new BU_Simplex1to4(Converter.convert(vector1), Converter.convert(vector2), Converter.convert(vector3)); + } else if (vector2 != null) { + objectId = createShape(vector1, vector2); +// objectId = new BU_Simplex1to4(Converter.convert(vector1), Converter.convert(vector2)); + } else { + objectId = createShape(vector1); +// objectId = new BU_Simplex1to4(Converter.convert(vector1)); + } + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); +// objectId.setLocalScaling(Converter.convert(getScale())); +// objectId.setMargin(margin); + setScale(scale); + setMargin(margin); + } + + private native long createShape(Vector3f vector1); + + private native long createShape(Vector3f vector1, Vector3f vector2); + + private native long createShape(Vector3f vector1, Vector3f vector2, Vector3f vector3); + + private native long createShape(Vector3f vector1, Vector3f vector2, Vector3f vector3, Vector3f vector4); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java new file mode 100644 index 000000000..e5215506f --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Basic 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(); + } + + /** + * WARNING - CompoundCollisionShape scaling has no effect. + */ + @Override + public void setScale(Vector3f scale) { + Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "SphereCollisionShape cannot be scaled"); + } + + protected void createShape() { + objectId = createShape(radius); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); +// new SphereShape(radius); +// objectId.setLocalScaling(Converter.convert(getScale())); +// objectId.setMargin(margin); + setScale(scale); + setMargin(margin); + } + + private native long createShape(float radius); + +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java new file mode 100644 index 000000000..4284e7ad3 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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; + setLimit(objectId, swingSpan1, swingSpan2, twistSpan); + } + + private native void setLimit(long objectId, float swingSpan1, float swingSpan2, float twistSpan); + + public void setAngularOnly(boolean value) { + angularOnly = value; + setAngularOnly(objectId, value); + } + + private native void setAngularOnly(long objectId, boolean value); + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(rotA, "rotA", new Matrix3f()); + capsule.write(rotB, "rotB", new Matrix3f()); + + capsule.write(angularOnly, "angularOnly", false); + capsule.write(swingSpan1, "swingSpan1", 1e30f); + capsule.write(swingSpan2, "swingSpan2", 1e30f); + capsule.write(twistSpan, "twistSpan", 1e30f); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + this.rotA = (Matrix3f) capsule.readSavable("rotA", new Matrix3f()); + this.rotB = (Matrix3f) capsule.readSavable("rotB", new Matrix3f()); + + this.angularOnly = capsule.readBoolean("angularOnly", false); + this.swingSpan1 = capsule.readFloat("swingSpan1", 1e30f); + this.swingSpan2 = capsule.readFloat("swingSpan2", 1e30f); + this.twistSpan = capsule.readFloat("twistSpan", 1e30f); + createJoint(); + } + + protected void createJoint() { + objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); + setLimit(objectId, swingSpan1, swingSpan2, twistSpan); + setAngularOnly(objectId, angularOnly); + } + + private native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Matrix3f rotA, Vector3f pivotB, Matrix3f rotB); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java new file mode 100644 index 000000000..2906d7243 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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(); + } + + /** + * Enables the motor. + * @param enable if true, motor is enabled. + * @param targetVelocity the target velocity of the rotation. + * @param maxMotorImpulse the max force applied to the hinge to rotate it. + */ + public void enableMotor(boolean enable, float targetVelocity, float maxMotorImpulse) { + enableMotor(objectId, enable, targetVelocity, maxMotorImpulse); + } + + private native void enableMotor(long objectId, boolean enable, float targetVelocity, float maxMotorImpulse); + + public boolean getEnableMotor() { + return getEnableAngularMotor(objectId); + } + + private native boolean getEnableAngularMotor(long objectId); + + public float getMotorTargetVelocity() { + return getMotorTargetVelocity(objectId); + } + + private native float getMotorTargetVelocity(long objectId); + + public float getMaxMotorImpulse() { + return getMaxMotorImpulse(objectId); + } + + private native float getMaxMotorImpulse(long objectId); + + /** + * Sets the limits of this joint. + * @param low the low limit in radians. + * @param high the high limit in radians. + */ + public void setLimit(float low, float high) { + setLimit(objectId, low, high); + } + + private native void setLimit(long objectId, float low, float high); + + /** + * Sets the limits of this joint. + * If you're above the softness, velocities that would shoot through the actual limit are slowed down. The bias be in the range of 0.2 - 0.5. + * @param low the low limit in radians. + * @param high the high limit in radians. + * @param _softness the factor at which the velocity error correction starts operating,i.e a softness of 0.9 means that the vel. corr starts at 90% of the limit range. + * @param _biasFactor the magnitude of the position correction. It tells you how strictly the position error (drift ) is corrected. + * @param _relaxationFactor the rate at which velocity errors are corrected. This can be seen as the strength of the limits. A low value will make the the limits more spongy. + */ + public void setLimit(float low, float high, float _softness, float _biasFactor, float _relaxationFactor) { + biasFactor = _biasFactor; + relaxationFactor = _relaxationFactor; + limitSoftness = _softness; + setLimit(objectId, low, high, _softness, _biasFactor, _relaxationFactor); + } + + private native void setLimit(long objectId, float low, float high, float _softness, float _biasFactor, float _relaxationFactor); + + public float getUpperLimit() { + return getUpperLimit(objectId); + } + + private native float getUpperLimit(long objectId); + + public float getLowerLimit() { + return getLowerLimit(objectId); + } + + private native float getLowerLimit(long objectId); + + public void setAngularOnly(boolean angularOnly) { + this.angularOnly = angularOnly; + setAngularOnly(objectId, angularOnly); + } + + private native void setAngularOnly(long objectId, boolean angularOnly); + + public float getHingeAngle() { + return getHingeAngle(objectId); + } + + private native float getHingeAngle(long objectId); + + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(axisA, "axisA", new Vector3f()); + capsule.write(axisB, "axisB", new Vector3f()); + + capsule.write(angularOnly, "angularOnly", false); + + capsule.write(getLowerLimit(), "lowerLimit", 1e30f); + capsule.write(getUpperLimit(), "upperLimit", -1e30f); + + capsule.write(biasFactor, "biasFactor", 0.3f); + capsule.write(relaxationFactor, "relaxationFactor", 1f); + capsule.write(limitSoftness, "limitSoftness", 0.9f); + + capsule.write(getEnableMotor(), "enableAngularMotor", false); + capsule.write(getMotorTargetVelocity(), "targetVelocity", 0.0f); + capsule.write(getMaxMotorImpulse(), "maxMotorImpulse", 0.0f); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + this.axisA = (Vector3f) capsule.readSavable("axisA", new Vector3f()); + this.axisB = (Vector3f) capsule.readSavable("axisB", new Vector3f()); + + this.angularOnly = capsule.readBoolean("angularOnly", false); + float lowerLimit = capsule.readFloat("lowerLimit", 1e30f); + float upperLimit = capsule.readFloat("upperLimit", -1e30f); + + this.biasFactor = capsule.readFloat("biasFactor", 0.3f); + this.relaxationFactor = capsule.readFloat("relaxationFactor", 1f); + this.limitSoftness = capsule.readFloat("limitSoftness", 0.9f); + + boolean enableAngularMotor = capsule.readBoolean("enableAngularMotor", false); + float targetVelocity = capsule.readFloat("targetVelocity", 0.0f); + float maxMotorImpulse = capsule.readFloat("maxMotorImpulse", 0.0f); + + createJoint(); + enableMotor(enableAngularMotor, targetVelocity, maxMotorImpulse); + setLimit(lowerLimit, upperLimit, limitSoftness, biasFactor, relaxationFactor); + } + + protected void createJoint() { + objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, axisA, pivotB, axisB); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); + } + + private native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Vector3f axisA, Vector3f pivotB, Vector3f axisB); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java new file mode 100644 index 000000000..256036ff7 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.export.*; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + *

    PhysicsJoint - Basic Phyiscs Joint

    + * @author normenhansen + */ +public abstract class PhysicsJoint implements Savable { + + protected long objectId = 0; + protected PhysicsRigidBody nodeA; + protected PhysicsRigidBody nodeB; + protected Vector3f pivotA; + protected Vector3f pivotB; + protected boolean collisionBetweenLinkedBodys = true; + + public PhysicsJoint() { + } + + /** + * @param pivotA local translation of the joint connection point in node A + * @param pivotB local translation of the joint connection point in node B + */ + public PhysicsJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB) { + this.nodeA = nodeA; + this.nodeB = nodeB; + this.pivotA = pivotA; + this.pivotB = pivotB; + nodeA.addJoint(this); + nodeB.addJoint(this); + } + + public float getAppliedImpulse() { + return getAppliedImpulse(objectId); + } + + private native float getAppliedImpulse(long objectId); + + /** + * @return the constraint + */ + public long getObjectId() { + return objectId; + } + + /** + * @return the collisionBetweenLinkedBodys + */ + public boolean isCollisionBetweenLinkedBodys() { + return collisionBetweenLinkedBodys; + } + + /** + * toggles collisions between linked bodys
    + * joint has to be removed from and added to PhyiscsSpace to apply this. + * @param collisionBetweenLinkedBodys set to false to have no collisions between linked bodys + */ + public void setCollisionBetweenLinkedBodys(boolean collisionBetweenLinkedBodys) { + this.collisionBetweenLinkedBodys = collisionBetweenLinkedBodys; + } + + public PhysicsRigidBody getBodyA() { + return nodeA; + } + + public PhysicsRigidBody getBodyB() { + return nodeB; + } + + public Vector3f getPivotA() { + return pivotA; + } + + public Vector3f getPivotB() { + return pivotB; + } + + /** + * destroys this joint and removes it from its connected PhysicsRigidBodys joint lists + */ + public void destroy() { + getBodyA().removeJoint(this); + getBodyB().removeJoint(this); + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(nodeA, "nodeA", null); + capsule.write(nodeB, "nodeB", null); + capsule.write(pivotA, "pivotA", null); + capsule.write(pivotB, "pivotB", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule capsule = im.getCapsule(this); + this.nodeA = ((PhysicsRigidBody) capsule.readSavable("nodeA", new PhysicsRigidBody())); + this.nodeB = (PhysicsRigidBody) capsule.readSavable("nodeB", new PhysicsRigidBody()); + this.pivotA = (Vector3f) capsule.readSavable("pivotA", new Vector3f()); + this.pivotB = (Vector3f) capsule.readSavable("pivotB", new Vector3f()); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Joint {0}", Long.toHexString(objectId)); + finalizeNative(objectId); + } + + private native void finalizeNative(long objectId); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java new file mode 100644 index 000000000..312293e99 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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) { + setDamping(objectId, value); + } + + private native void setDamping(long objectId, float value); + + public void setImpulseClamp(float value) { + setImpulseClamp(objectId, value); + } + + private native void setImpulseClamp(long objectId, float value); + + public void setTau(float value) { + setTau(objectId, value); + } + + private native void setTau(long objectId, float value); + + public float getDamping() { + return getDamping(objectId); + } + + private native float getDamping(long objectId); + + public float getImpulseClamp() { + return getImpulseClamp(objectId); + } + + private native float getImpulseClamp(long objectId); + + public float getTau() { + return getTau(objectId); + } + + private native float getTau(long objectId); + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule cap = ex.getCapsule(this); + cap.write(getDamping(), "damping", 1.0f); + cap.write(getTau(), "tau", 0.3f); + cap.write(getImpulseClamp(), "impulseClamp", 0f); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + createJoint(); + InputCapsule cap = im.getCapsule(this); + setDamping(cap.readFloat("damping", 1.0f)); + setDamping(cap.readFloat("tau", 0.3f)); + setDamping(cap.readFloat("impulseClamp", 0f)); + } + + protected void createJoint() { + objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, pivotB); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); + } + + private native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Vector3f pivotB); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java new file mode 100644 index 000000000..648398149 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.jme3.bullet.joints.motors.RotationalLimitMotor; +import com.jme3.bullet.joints.motors.TranslationalLimitMotor; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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 { + + Matrix3f rotA, rotB; + boolean useLinearReferenceFrameA; + LinkedList rotationalMotors = new LinkedList(); + TranslationalLimitMotor translationalMotor; + Vector3f angularUpperLimit = new Vector3f(Vector3f.POSITIVE_INFINITY); + Vector3f angularLowerLimit = new Vector3f(Vector3f.NEGATIVE_INFINITY); + Vector3f linearUpperLimit = new Vector3f(Vector3f.POSITIVE_INFINITY); + Vector3f linearLowerLimit = new Vector3f(Vector3f.NEGATIVE_INFINITY); + + public SixDofJoint() { + } + + /** + * @param pivotA local translation of the joint connection point in node A + * @param pivotB local translation of the joint connection point in node B + */ + public SixDofJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB, boolean useLinearReferenceFrameA) { + super(nodeA, nodeB, pivotA, pivotB); + this.useLinearReferenceFrameA = useLinearReferenceFrameA; + this.rotA = rotA; + this.rotB = rotB; + + objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB, useLinearReferenceFrameA); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); + gatherMotors(); + } + + /** + * @param pivotA local translation of the joint connection point in node A + * @param pivotB local translation of the joint connection point in node B + */ + public SixDofJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, boolean useLinearReferenceFrameA) { + super(nodeA, nodeB, pivotA, pivotB); + this.useLinearReferenceFrameA = useLinearReferenceFrameA; + rotA = new Matrix3f(); + rotB = new Matrix3f(); + + objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB, useLinearReferenceFrameA); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); + gatherMotors(); + } + + private void gatherMotors() { + for (int i = 0; i < 3; i++) { + RotationalLimitMotor rmot = new RotationalLimitMotor(getRotationalLimitMotor(objectId, i)); + rotationalMotors.add(rmot); + } + translationalMotor = new TranslationalLimitMotor(getTranslationalLimitMotor(objectId)); + } + + private native long getRotationalLimitMotor(long objectId, int index); + + private native long getTranslationalLimitMotor(long objectId); + + /** + * returns the TranslationalLimitMotor of this 6DofJoint which allows + * manipulating the translational axis + * @return the TranslationalLimitMotor + */ + public TranslationalLimitMotor getTranslationalLimitMotor() { + return translationalMotor; + } + + /** + * returns one of the three RotationalLimitMotors of this 6DofJoint which + * allow manipulating the rotational axes + * @param index the index of the RotationalLimitMotor + * @return the RotationalLimitMotor at the given index + */ + public RotationalLimitMotor getRotationalLimitMotor(int index) { + return rotationalMotors.get(index); + } + + public void setLinearUpperLimit(Vector3f vector) { + linearUpperLimit.set(vector); + setLinearUpperLimit(objectId, vector); + } + + private native void setLinearUpperLimit(long objctId, Vector3f vector); + + public void setLinearLowerLimit(Vector3f vector) { + linearLowerLimit.set(vector); + setLinearLowerLimit(objectId, vector); + } + + private native void setLinearLowerLimit(long objctId, Vector3f vector); + + public void setAngularUpperLimit(Vector3f vector) { + angularUpperLimit.set(vector); + setAngularUpperLimit(objectId, vector); + } + + private native void setAngularUpperLimit(long objctId, Vector3f vector); + + public void setAngularLowerLimit(Vector3f vector) { + angularLowerLimit.set(vector); + setAngularLowerLimit(objectId, vector); + } + + private native void setAngularLowerLimit(long objctId, Vector3f vector); + + native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Matrix3f rotA, Vector3f pivotB, Matrix3f rotB, boolean useLinearReferenceFrameA); + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + + objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB, useLinearReferenceFrameA); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); + gatherMotors(); + + setAngularUpperLimit((Vector3f) capsule.readSavable("angularUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY))); + setAngularLowerLimit((Vector3f) capsule.readSavable("angularLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY))); + setLinearUpperLimit((Vector3f) capsule.readSavable("linearUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY))); + setLinearLowerLimit((Vector3f) capsule.readSavable("linearLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY))); + + for (int i = 0; i < 3; i++) { + RotationalLimitMotor rotationalLimitMotor = getRotationalLimitMotor(i); + rotationalLimitMotor.setBounce(capsule.readFloat("rotMotor" + i + "_Bounce", 0.0f)); + rotationalLimitMotor.setDamping(capsule.readFloat("rotMotor" + i + "_Damping", 1.0f)); + rotationalLimitMotor.setERP(capsule.readFloat("rotMotor" + i + "_ERP", 0.5f)); + rotationalLimitMotor.setHiLimit(capsule.readFloat("rotMotor" + i + "_HiLimit", Float.POSITIVE_INFINITY)); + rotationalLimitMotor.setLimitSoftness(capsule.readFloat("rotMotor" + i + "_LimitSoftness", 0.5f)); + rotationalLimitMotor.setLoLimit(capsule.readFloat("rotMotor" + i + "_LoLimit", Float.NEGATIVE_INFINITY)); + rotationalLimitMotor.setMaxLimitForce(capsule.readFloat("rotMotor" + i + "_MaxLimitForce", 300.0f)); + rotationalLimitMotor.setMaxMotorForce(capsule.readFloat("rotMotor" + i + "_MaxMotorForce", 0.1f)); + rotationalLimitMotor.setTargetVelocity(capsule.readFloat("rotMotor" + i + "_TargetVelocity", 0)); + rotationalLimitMotor.setEnableMotor(capsule.readBoolean("rotMotor" + i + "_EnableMotor", false)); + } + getTranslationalLimitMotor().setAccumulatedImpulse((Vector3f) capsule.readSavable("transMotor_AccumulatedImpulse", Vector3f.ZERO)); + getTranslationalLimitMotor().setDamping(capsule.readFloat("transMotor_Damping", 1.0f)); + getTranslationalLimitMotor().setLimitSoftness(capsule.readFloat("transMotor_LimitSoftness", 0.7f)); + getTranslationalLimitMotor().setLowerLimit((Vector3f) capsule.readSavable("transMotor_LowerLimit", Vector3f.ZERO)); + getTranslationalLimitMotor().setRestitution(capsule.readFloat("transMotor_Restitution", 0.5f)); + getTranslationalLimitMotor().setUpperLimit((Vector3f) capsule.readSavable("transMotor_UpperLimit", Vector3f.ZERO)); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(angularUpperLimit, "angularUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY)); + capsule.write(angularLowerLimit, "angularLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY)); + capsule.write(linearUpperLimit, "linearUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY)); + capsule.write(linearLowerLimit, "linearLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY)); + int i = 0; + for (Iterator 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/jme3-bullet/src/main/java/com/jme3/bullet/joints/SixDofSpringJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/SixDofSpringJoint.java new file mode 100644 index 000000000..e52f9836c --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/joints/SixDofSpringJoint.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; + +/** + * 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 SixDofSpringJoint extends SixDofJoint { + + final boolean springEnabled[] = new boolean[6]; + final float equilibriumPoint[] = new float[6]; + final float springStiffness[] = new float[6]; + final float springDamping[] = new float[6]; // between 0 and 1 (1 == no damping) + + public SixDofSpringJoint() { + } + + /** + * @param pivotA local translation of the joint connection point in node A + * @param pivotB local translation of the joint connection point in node B + */ + public SixDofSpringJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB, boolean useLinearReferenceFrameA) { + super(nodeA, nodeB, pivotA, pivotB, rotA, rotB, useLinearReferenceFrameA); + } + public void enableSpring(int index, boolean onOff) { + enableSpring(objectId, index, onOff); + } + native void enableSpring(long objctId, int index, boolean onOff); + + public void setStiffness(int index, float stiffness) { + setStiffness(objectId, index, stiffness); + } + native void setStiffness(long objctId, int index, float stiffness); + + public void setDamping(int index, float damping) { + setDamping(objectId, index, damping); + + } + native void setDamping(long objctId, int index, float damping); + public void setEquilibriumPoint() { // set the current constraint position/orientation as an equilibrium point for all DOF + setEquilibriumPoint(objectId); + } + native void setEquilibriumPoint(long objctId); + public void setEquilibriumPoint(int index){ // set the current constraint position/orientation as an equilibrium point for given DOF + setEquilibriumPoint(objectId, index); + } + native void setEquilibriumPoint(long objctId, int index); + @Override + native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Matrix3f rotA, Vector3f pivotB, Matrix3f rotB, boolean useLinearReferenceFrameA); + +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java new file mode 100644 index 000000000..b51c5d849 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java @@ -0,0 +1,538 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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 getLowerLinLimit(objectId); + } + + private native float getLowerLinLimit(long objectId); + + public void setLowerLinLimit(float lowerLinLimit) { + setLowerLinLimit(objectId, lowerLinLimit); + } + + private native void setLowerLinLimit(long objectId, float value); + + public float getUpperLinLimit() { + return getUpperLinLimit(objectId); + } + + private native float getUpperLinLimit(long objectId); + + public void setUpperLinLimit(float upperLinLimit) { + setUpperLinLimit(objectId, upperLinLimit); + } + + private native void setUpperLinLimit(long objectId, float value); + + public float getLowerAngLimit() { + return getLowerAngLimit(objectId); + } + + private native float getLowerAngLimit(long objectId); + + public void setLowerAngLimit(float lowerAngLimit) { + setLowerAngLimit(objectId, lowerAngLimit); + } + + private native void setLowerAngLimit(long objectId, float value); + + public float getUpperAngLimit() { + return getUpperAngLimit(objectId); + } + + private native float getUpperAngLimit(long objectId); + + public void setUpperAngLimit(float upperAngLimit) { + setUpperAngLimit(objectId, upperAngLimit); + } + + private native void setUpperAngLimit(long objectId, float value); + + public float getSoftnessDirLin() { + return getSoftnessDirLin(objectId); + } + + private native float getSoftnessDirLin(long objectId); + + public void setSoftnessDirLin(float softnessDirLin) { + setSoftnessDirLin(objectId, softnessDirLin); + } + + private native void setSoftnessDirLin(long objectId, float value); + + public float getRestitutionDirLin() { + return getRestitutionDirLin(objectId); + } + + private native float getRestitutionDirLin(long objectId); + + public void setRestitutionDirLin(float restitutionDirLin) { + setRestitutionDirLin(objectId, restitutionDirLin); + } + + private native void setRestitutionDirLin(long objectId, float value); + + public float getDampingDirLin() { + return getDampingDirLin(objectId); + } + + private native float getDampingDirLin(long objectId); + + public void setDampingDirLin(float dampingDirLin) { + setDampingDirLin(objectId, dampingDirLin); + } + + private native void setDampingDirLin(long objectId, float value); + + public float getSoftnessDirAng() { + return getSoftnessDirAng(objectId); + } + + private native float getSoftnessDirAng(long objectId); + + public void setSoftnessDirAng(float softnessDirAng) { + setSoftnessDirAng(objectId, softnessDirAng); + } + + private native void setSoftnessDirAng(long objectId, float value); + + public float getRestitutionDirAng() { + return getRestitutionDirAng(objectId); + } + + private native float getRestitutionDirAng(long objectId); + + public void setRestitutionDirAng(float restitutionDirAng) { + setRestitutionDirAng(objectId, restitutionDirAng); + } + + private native void setRestitutionDirAng(long objectId, float value); + + public float getDampingDirAng() { + return getDampingDirAng(objectId); + } + + private native float getDampingDirAng(long objectId); + + public void setDampingDirAng(float dampingDirAng) { + setDampingDirAng(objectId, dampingDirAng); + } + + private native void setDampingDirAng(long objectId, float value); + + public float getSoftnessLimLin() { + return getSoftnessLimLin(objectId); + } + + private native float getSoftnessLimLin(long objectId); + + public void setSoftnessLimLin(float softnessLimLin) { + setSoftnessLimLin(objectId, softnessLimLin); + } + + private native void setSoftnessLimLin(long objectId, float value); + + public float getRestitutionLimLin() { + return getRestitutionLimLin(objectId); + } + + private native float getRestitutionLimLin(long objectId); + + public void setRestitutionLimLin(float restitutionLimLin) { + setRestitutionLimLin(objectId, restitutionLimLin); + } + + private native void setRestitutionLimLin(long objectId, float value); + + public float getDampingLimLin() { + return getDampingLimLin(objectId); + } + + private native float getDampingLimLin(long objectId); + + public void setDampingLimLin(float dampingLimLin) { + setDampingLimLin(objectId, dampingLimLin); + } + + private native void setDampingLimLin(long objectId, float value); + + public float getSoftnessLimAng() { + return getSoftnessLimAng(objectId); + } + + private native float getSoftnessLimAng(long objectId); + + public void setSoftnessLimAng(float softnessLimAng) { + setSoftnessLimAng(objectId, softnessLimAng); + } + + private native void setSoftnessLimAng(long objectId, float value); + + public float getRestitutionLimAng() { + return getRestitutionLimAng(objectId); + } + + private native float getRestitutionLimAng(long objectId); + + public void setRestitutionLimAng(float restitutionLimAng) { + setRestitutionLimAng(objectId, restitutionLimAng); + } + + private native void setRestitutionLimAng(long objectId, float value); + + public float getDampingLimAng() { + return getDampingLimAng(objectId); + } + + private native float getDampingLimAng(long objectId); + + public void setDampingLimAng(float dampingLimAng) { + setDampingLimAng(objectId, dampingLimAng); + } + + private native void setDampingLimAng(long objectId, float value); + + public float getSoftnessOrthoLin() { + return getSoftnessOrthoLin(objectId); + } + + private native float getSoftnessOrthoLin(long objectId); + + public void setSoftnessOrthoLin(float softnessOrthoLin) { + setSoftnessOrthoLin(objectId, softnessOrthoLin); + } + + private native void setSoftnessOrthoLin(long objectId, float value); + + public float getRestitutionOrthoLin() { + return getRestitutionOrthoLin(objectId); + } + + private native float getRestitutionOrthoLin(long objectId); + + public void setRestitutionOrthoLin(float restitutionOrthoLin) { + setDampingOrthoLin(objectId, restitutionOrthoLin); + } + + private native void setRestitutionOrthoLin(long objectId, float value); + + public float getDampingOrthoLin() { + return getDampingOrthoLin(objectId); + } + + private native float getDampingOrthoLin(long objectId); + + public void setDampingOrthoLin(float dampingOrthoLin) { + setDampingOrthoLin(objectId, dampingOrthoLin); + } + + private native void setDampingOrthoLin(long objectId, float value); + + public float getSoftnessOrthoAng() { + return getSoftnessOrthoAng(objectId); + } + + private native float getSoftnessOrthoAng(long objectId); + + public void setSoftnessOrthoAng(float softnessOrthoAng) { + setSoftnessOrthoAng(objectId, softnessOrthoAng); + } + + private native void setSoftnessOrthoAng(long objectId, float value); + + public float getRestitutionOrthoAng() { + return getRestitutionOrthoAng(objectId); + } + + private native float getRestitutionOrthoAng(long objectId); + + public void setRestitutionOrthoAng(float restitutionOrthoAng) { + setRestitutionOrthoAng(objectId, restitutionOrthoAng); + } + + private native void setRestitutionOrthoAng(long objectId, float value); + + public float getDampingOrthoAng() { + return getDampingOrthoAng(objectId); + } + + private native float getDampingOrthoAng(long objectId); + + public void setDampingOrthoAng(float dampingOrthoAng) { + setDampingOrthoAng(objectId, dampingOrthoAng); + } + + private native void setDampingOrthoAng(long objectId, float value); + + public boolean isPoweredLinMotor() { + return isPoweredLinMotor(objectId); + } + + private native boolean isPoweredLinMotor(long objectId); + + public void setPoweredLinMotor(boolean poweredLinMotor) { + setPoweredLinMotor(objectId, poweredLinMotor); + } + + private native void setPoweredLinMotor(long objectId, boolean value); + + public float getTargetLinMotorVelocity() { + return getTargetLinMotorVelocity(objectId); + } + + private native float getTargetLinMotorVelocity(long objectId); + + public void setTargetLinMotorVelocity(float targetLinMotorVelocity) { + setTargetLinMotorVelocity(objectId, targetLinMotorVelocity); + } + + private native void setTargetLinMotorVelocity(long objectId, float value); + + public float getMaxLinMotorForce() { + return getMaxLinMotorForce(objectId); + } + + private native float getMaxLinMotorForce(long objectId); + + public void setMaxLinMotorForce(float maxLinMotorForce) { + setMaxLinMotorForce(objectId, maxLinMotorForce); + } + + private native void setMaxLinMotorForce(long objectId, float value); + + public boolean isPoweredAngMotor() { + return isPoweredAngMotor(objectId); + } + + private native boolean isPoweredAngMotor(long objectId); + + public void setPoweredAngMotor(boolean poweredAngMotor) { + setPoweredAngMotor(objectId, poweredAngMotor); + } + + private native void setPoweredAngMotor(long objectId, boolean value); + + public float getTargetAngMotorVelocity() { + return getTargetAngMotorVelocity(objectId); + } + + private native float getTargetAngMotorVelocity(long objectId); + + public void setTargetAngMotorVelocity(float targetAngMotorVelocity) { + setTargetAngMotorVelocity(objectId, targetAngMotorVelocity); + } + + private native void setTargetAngMotorVelocity(long objectId, float value); + + public float getMaxAngMotorForce() { + return getMaxAngMotorForce(objectId); + } + + private native float getMaxAngMotorForce(long objectId); + + public void setMaxAngMotorForce(float maxAngMotorForce) { + setMaxAngMotorForce(objectId, maxAngMotorForce); + } + + private native void setMaxAngMotorForce(long objectId, float value); + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + //TODO: standard values.. + capsule.write(getDampingDirAng(), "dampingDirAng", 0f); + capsule.write(getDampingDirLin(), "dampingDirLin", 0f); + capsule.write(getDampingLimAng(), "dampingLimAng", 0f); + capsule.write(getDampingLimLin(), "dampingLimLin", 0f); + capsule.write(getDampingOrthoAng(), "dampingOrthoAng", 0f); + capsule.write(getDampingOrthoLin(), "dampingOrthoLin", 0f); + capsule.write(getLowerAngLimit(), "lowerAngLimit", 0f); + capsule.write(getLowerLinLimit(), "lowerLinLimit", 0f); + capsule.write(getMaxAngMotorForce(), "maxAngMotorForce", 0f); + capsule.write(getMaxLinMotorForce(), "maxLinMotorForce", 0f); + capsule.write(isPoweredAngMotor(), "poweredAngMotor", false); + capsule.write(isPoweredLinMotor(), "poweredLinMotor", false); + capsule.write(getRestitutionDirAng(), "restitutionDirAng", 0f); + capsule.write(getRestitutionDirLin(), "restitutionDirLin", 0f); + capsule.write(getRestitutionLimAng(), "restitutionLimAng", 0f); + capsule.write(getRestitutionLimLin(), "restitutionLimLin", 0f); + capsule.write(getRestitutionOrthoAng(), "restitutionOrthoAng", 0f); + capsule.write(getRestitutionOrthoLin(), "restitutionOrthoLin", 0f); + + capsule.write(getSoftnessDirAng(), "softnessDirAng", 0f); + capsule.write(getSoftnessDirLin(), "softnessDirLin", 0f); + capsule.write(getSoftnessLimAng(), "softnessLimAng", 0f); + capsule.write(getSoftnessLimLin(), "softnessLimLin", 0f); + capsule.write(getSoftnessOrthoAng(), "softnessOrthoAng", 0f); + capsule.write(getSoftnessOrthoLin(), "softnessOrthoLin", 0f); + + capsule.write(getTargetAngMotorVelocity(), "targetAngMotorVelicoty", 0f); + capsule.write(getTargetLinMotorVelocity(), "targetLinMotorVelicoty", 0f); + + capsule.write(getUpperAngLimit(), "upperAngLimit", 0f); + capsule.write(getUpperLinLimit(), "upperLinLimit", 0f); + + capsule.write(useLinearReferenceFrameA, "useLinearReferenceFrameA", false); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + float dampingDirAng = capsule.readFloat("dampingDirAng", 0f); + float dampingDirLin = capsule.readFloat("dampingDirLin", 0f); + float dampingLimAng = capsule.readFloat("dampingLimAng", 0f); + float dampingLimLin = capsule.readFloat("dampingLimLin", 0f); + float dampingOrthoAng = capsule.readFloat("dampingOrthoAng", 0f); + float dampingOrthoLin = capsule.readFloat("dampingOrthoLin", 0f); + float lowerAngLimit = capsule.readFloat("lowerAngLimit", 0f); + float lowerLinLimit = capsule.readFloat("lowerLinLimit", 0f); + float maxAngMotorForce = capsule.readFloat("maxAngMotorForce", 0f); + float maxLinMotorForce = capsule.readFloat("maxLinMotorForce", 0f); + boolean poweredAngMotor = capsule.readBoolean("poweredAngMotor", false); + boolean poweredLinMotor = capsule.readBoolean("poweredLinMotor", false); + float restitutionDirAng = capsule.readFloat("restitutionDirAng", 0f); + float restitutionDirLin = capsule.readFloat("restitutionDirLin", 0f); + float restitutionLimAng = capsule.readFloat("restitutionLimAng", 0f); + float restitutionLimLin = capsule.readFloat("restitutionLimLin", 0f); + float restitutionOrthoAng = capsule.readFloat("restitutionOrthoAng", 0f); + float restitutionOrthoLin = capsule.readFloat("restitutionOrthoLin", 0f); + + float softnessDirAng = capsule.readFloat("softnessDirAng", 0f); + float softnessDirLin = capsule.readFloat("softnessDirLin", 0f); + float softnessLimAng = capsule.readFloat("softnessLimAng", 0f); + float softnessLimLin = capsule.readFloat("softnessLimLin", 0f); + float softnessOrthoAng = capsule.readFloat("softnessOrthoAng", 0f); + float softnessOrthoLin = capsule.readFloat("softnessOrthoLin", 0f); + + float targetAngMotorVelicoty = capsule.readFloat("targetAngMotorVelicoty", 0f); + float targetLinMotorVelicoty = capsule.readFloat("targetLinMotorVelicoty", 0f); + + float upperAngLimit = capsule.readFloat("upperAngLimit", 0f); + float upperLinLimit = capsule.readFloat("upperLinLimit", 0f); + + useLinearReferenceFrameA = capsule.readBoolean("useLinearReferenceFrameA", false); + + createJoint(); + + setDampingDirAng(dampingDirAng); + setDampingDirLin(dampingDirLin); + setDampingLimAng(dampingLimAng); + setDampingLimLin(dampingLimLin); + setDampingOrthoAng(dampingOrthoAng); + setDampingOrthoLin(dampingOrthoLin); + setLowerAngLimit(lowerAngLimit); + setLowerLinLimit(lowerLinLimit); + setMaxAngMotorForce(maxAngMotorForce); + setMaxLinMotorForce(maxLinMotorForce); + setPoweredAngMotor(poweredAngMotor); + setPoweredLinMotor(poweredLinMotor); + setRestitutionDirAng(restitutionDirAng); + setRestitutionDirLin(restitutionDirLin); + setRestitutionLimAng(restitutionLimAng); + setRestitutionLimLin(restitutionLimLin); + setRestitutionOrthoAng(restitutionOrthoAng); + setRestitutionOrthoLin(restitutionOrthoLin); + + setSoftnessDirAng(softnessDirAng); + setSoftnessDirLin(softnessDirLin); + setSoftnessLimAng(softnessLimAng); + setSoftnessLimLin(softnessLimLin); + setSoftnessOrthoAng(softnessOrthoAng); + setSoftnessOrthoLin(softnessOrthoLin); + + setTargetAngMotorVelocity(targetAngMotorVelicoty); + setTargetLinMotorVelocity(targetLinMotorVelicoty); + + setUpperAngLimit(upperAngLimit); + setUpperLinLimit(upperLinLimit); + } + + protected void createJoint() { + objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB, useLinearReferenceFrameA); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); + // = new SliderConstraint(nodeA.getObjectId(), nodeB.getObjectId(), transA, transB, useLinearReferenceFrameA); + } + + private native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Matrix3f rotA, Vector3f pivotB, Matrix3f rotB, boolean useLinearReferenceFrameA); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/motors/RotationalLimitMotor.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/motors/RotationalLimitMotor.java new file mode 100644 index 000000000..4b4b4aa95 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/joints/motors/RotationalLimitMotor.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints.motors; + +/** + * + * @author normenhansen + */ +public class RotationalLimitMotor { + + private long motorId = 0; + + public RotationalLimitMotor(long motor) { + this.motorId = motor; + } + + public long getMotor() { + return motorId; + } + + public float getLoLimit() { + return getLoLimit(motorId); + } + + private native float getLoLimit(long motorId); + + public void setLoLimit(float loLimit) { + setLoLimit(motorId, loLimit); + } + + private native void setLoLimit(long motorId, float loLimit); + + public float getHiLimit() { + return getHiLimit(motorId); + } + + private native float getHiLimit(long motorId); + + public void setHiLimit(float hiLimit) { + setHiLimit(motorId, hiLimit); + } + + private native void setHiLimit(long motorId, float hiLimit); + + public float getTargetVelocity() { + return getTargetVelocity(motorId); + } + + private native float getTargetVelocity(long motorId); + + public void setTargetVelocity(float targetVelocity) { + setTargetVelocity(motorId, targetVelocity); + } + + private native void setTargetVelocity(long motorId, float targetVelocity); + + public float getMaxMotorForce() { + return getMaxMotorForce(motorId); + } + + private native float getMaxMotorForce(long motorId); + + public void setMaxMotorForce(float maxMotorForce) { + setMaxMotorForce(motorId, maxMotorForce); + } + + private native void setMaxMotorForce(long motorId, float maxMotorForce); + + public float getMaxLimitForce() { + return getMaxLimitForce(motorId); + } + + private native float getMaxLimitForce(long motorId); + + public void setMaxLimitForce(float maxLimitForce) { + setMaxLimitForce(motorId, maxLimitForce); + } + + private native void setMaxLimitForce(long motorId, float maxLimitForce); + + public float getDamping() { + return getDamping(motorId); + } + + private native float getDamping(long motorId); + + public void setDamping(float damping) { + setDamping(motorId, damping); + } + + private native void setDamping(long motorId, float damping); + + public float getLimitSoftness() { + return getLimitSoftness(motorId); + } + + private native float getLimitSoftness(long motorId); + + public void setLimitSoftness(float limitSoftness) { + setLimitSoftness(motorId, limitSoftness); + } + + private native void setLimitSoftness(long motorId, float limitSoftness); + + public float getERP() { + return getERP(motorId); + } + + private native float getERP(long motorId); + + public void setERP(float ERP) { + setERP(motorId, ERP); + } + + private native void setERP(long motorId, float ERP); + + public float getBounce() { + return getBounce(motorId); + } + + private native float getBounce(long motorId); + + public void setBounce(float bounce) { + setBounce(motorId, bounce); + } + + private native void setBounce(long motorId, float limitSoftness); + + public boolean isEnableMotor() { + return isEnableMotor(motorId); + } + + private native boolean isEnableMotor(long motorId); + + public void setEnableMotor(boolean enableMotor) { + setEnableMotor(motorId, enableMotor); + } + + private native void setEnableMotor(long motorId, boolean enableMotor); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java new file mode 100644 index 000000000..3b7690697 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints.motors; + +import com.jme3.math.Vector3f; + +/** + * + * @author normenhansen + */ +public class TranslationalLimitMotor { + + private long motorId = 0; + + public TranslationalLimitMotor(long motor) { + this.motorId = motor; + } + + public long getMotor() { + return motorId; + } + + public Vector3f getLowerLimit() { + Vector3f vec = new Vector3f(); + getLowerLimit(motorId, vec); + return vec; + } + + private native void getLowerLimit(long motorId, Vector3f vector); + + public void setLowerLimit(Vector3f lowerLimit) { + setLowerLimit(motorId, lowerLimit); + } + + private native void setLowerLimit(long motorId, Vector3f vector); + + public Vector3f getUpperLimit() { + Vector3f vec = new Vector3f(); + getUpperLimit(motorId, vec); + return vec; + } + + private native void getUpperLimit(long motorId, Vector3f vector); + + public void setUpperLimit(Vector3f upperLimit) { + setUpperLimit(motorId, upperLimit); + } + + private native void setUpperLimit(long motorId, Vector3f vector); + + public Vector3f getAccumulatedImpulse() { + Vector3f vec = new Vector3f(); + getAccumulatedImpulse(motorId, vec); + return vec; + } + + private native void getAccumulatedImpulse(long motorId, Vector3f vector); + + public void setAccumulatedImpulse(Vector3f accumulatedImpulse) { + setAccumulatedImpulse(motorId, accumulatedImpulse); + } + + private native void setAccumulatedImpulse(long motorId, Vector3f vector); + + public float getLimitSoftness() { + return getLimitSoftness(motorId); + } + + private native float getLimitSoftness(long motorId); + + public void setLimitSoftness(float limitSoftness) { + setLimitSoftness(motorId, limitSoftness); + } + + private native void setLimitSoftness(long motorId, float limitSoftness); + + public float getDamping() { + return getDamping(motorId); + } + + private native float getDamping(long motorId); + + public void setDamping(float damping) { + setDamping(motorId, damping); + } + + private native void setDamping(long motorId, float damping); + + public float getRestitution() { + return getRestitution(motorId); + } + + private native float getRestitution(long motorId); + + public void setRestitution(float restitution) { + setRestitution(motorId, restitution); + } + + private native void setRestitution(long motorId, float restitution); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java new file mode 100644 index 000000000..0ac13ae9f --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects; + +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Basic Bullet Character + * @author normenhansen + */ +public class PhysicsCharacter extends PhysicsCollisionObject { + + protected long characterId = 0; + protected float stepHeight; + protected Vector3f walkDirection = new Vector3f(); + protected float fallSpeed = 55.0f; + protected float jumpSpeed = 10.0f; + protected int upAxis = 1; + protected boolean locationDirty = false; + //TEMP VARIABLES + protected final Quaternion tmp_inverseWorldRotation = new Quaternion(); + + public PhysicsCharacter() { + } + + /** + * @param shape The CollisionShape (no Mesh or CompoundCollisionShapes) + * @param stepHeight The quantization size for vertical movement + */ + public PhysicsCharacter(CollisionShape shape, float stepHeight) { + this.collisionShape = shape; +// if (shape instanceof MeshCollisionShape || shape instanceof CompoundCollisionShape) { +// throw (new UnsupportedOperationException("Kinematic character nodes cannot have mesh or compound collision shapes")); +// } + this.stepHeight = stepHeight; + buildObject(); + } + + protected void buildObject() { + if (objectId == 0) { + objectId = createGhostObject(); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Creating GhostObject {0}", Long.toHexString(objectId)); + initUserPointer(); + } + setCharacterFlags(objectId); + attachCollisionShape(objectId, collisionShape.getObjectId()); + if (characterId != 0) { + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Clearing Character {0}", Long.toHexString(objectId)); + finalizeNativeCharacter(characterId); + } + characterId = createCharacterObject(objectId, collisionShape.getObjectId(), stepHeight); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Creating Character {0}", Long.toHexString(characterId)); + } + + private native long createGhostObject(); + + private native void setCharacterFlags(long objectId); + + private native long createCharacterObject(long objectId, long shapeId, float stepHeight); + + /** + * Sets the location of this physics character + * @param location + */ + public void warp(Vector3f location) { + warp(characterId, location); + } + + private native void warp(long characterId, Vector3f location); + + /** + * Set the walk direction, works continuously. + * This should probably be called setPositionIncrementPerSimulatorStep. + * This is neither a direction nor a velocity, but the amount to + * increment the position each physics tick. So vector length = accuracy*speed in m/s + * @param vec the walk direction to set + */ + public void setWalkDirection(Vector3f vec) { + walkDirection.set(vec); + setWalkDirection(characterId, vec); + } + + private native void setWalkDirection(long characterId, Vector3f vec); + + /** + * @return the currently set walkDirection + */ + public Vector3f getWalkDirection() { + return walkDirection; + } + + public void setUpAxis(int axis) { + upAxis = axis; + setUpAxis(characterId, axis); + } + + private native void setUpAxis(long characterId, int axis); + + public int getUpAxis() { + return upAxis; + } + + public void setFallSpeed(float fallSpeed) { + this.fallSpeed = fallSpeed; + setFallSpeed(characterId, fallSpeed); + } + + private native void setFallSpeed(long characterId, float fallSpeed); + + public float getFallSpeed() { + return fallSpeed; + } + + public void setJumpSpeed(float jumpSpeed) { + this.jumpSpeed = jumpSpeed; + setJumpSpeed(characterId, jumpSpeed); + } + + private native void setJumpSpeed(long characterId, float jumpSpeed); + + public float getJumpSpeed() { + return jumpSpeed; + } + + public void setGravity(float value) { + setGravity(characterId, value); + } + + private native void setGravity(long characterId, float gravity); + + public float getGravity() { + return getGravity(characterId); + } + + private native float getGravity(long characterId); + + public void setMaxSlope(float slopeRadians) { + setMaxSlope(characterId, slopeRadians); + } + + private native void setMaxSlope(long characterId, float slopeRadians); + + public float getMaxSlope() { + return getMaxSlope(characterId); + } + + private native float getMaxSlope(long characterId); + + public boolean onGround() { + return onGround(characterId); + } + + private native boolean onGround(long characterId); + + public void jump() { + jump(characterId); + } + + private native void jump(long characterId); + + @Override + public void setCollisionShape(CollisionShape collisionShape) { +// if (!(collisionShape.getObjectId() instanceof ConvexShape)) { +// throw (new UnsupportedOperationException("Kinematic character nodes cannot have mesh collision shapes")); +// } + super.setCollisionShape(collisionShape); + if (objectId == 0) { + buildObject(); + } else { + attachCollisionShape(objectId, collisionShape.getObjectId()); + } + } + + /** + * Set the physics location (same as warp()) + * @param location the location of the actual physics object + */ + public void setPhysicsLocation(Vector3f location) { + warp(location); + } + + /** + * @return the physicsLocation + */ + public Vector3f getPhysicsLocation(Vector3f trans) { + if (trans == null) { + trans = new Vector3f(); + } + getPhysicsLocation(objectId, trans); + return trans; + } + + private native void getPhysicsLocation(long objectId, Vector3f vec); + + /** + * @return the physicsLocation + */ + public Vector3f getPhysicsLocation() { + return getPhysicsLocation(null); + } + + public void setCcdSweptSphereRadius(float radius) { + setCcdSweptSphereRadius(objectId, radius); + } + + private native void setCcdSweptSphereRadius(long objectId, float radius); + + public void setCcdMotionThreshold(float threshold) { + setCcdMotionThreshold(objectId, threshold); + } + + private native void setCcdMotionThreshold(long objectId, float threshold); + + public float getCcdSweptSphereRadius() { + return getCcdSweptSphereRadius(objectId); + } + + private native float getCcdSweptSphereRadius(long objectId); + + public float getCcdMotionThreshold() { + return getCcdMotionThreshold(objectId); + } + + private native float getCcdMotionThreshold(long objectId); + + public float getCcdSquareMotionThreshold() { + return getCcdSquareMotionThreshold(objectId); + } + + private native float getCcdSquareMotionThreshold(long objectId); + + /** + * used internally + */ + public long getControllerId() { + return characterId; + } + + public void destroy() { + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(stepHeight, "stepHeight", 1.0f); + capsule.write(getGravity(), "gravity", 9.8f * 3); + capsule.write(getMaxSlope(), "maxSlope", 1.0f); + capsule.write(fallSpeed, "fallSpeed", 55.0f); + capsule.write(jumpSpeed, "jumpSpeed", 10.0f); + capsule.write(upAxis, "upAxis", 1); + capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0); + capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0); + capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f()); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + stepHeight = capsule.readFloat("stepHeight", 1.0f); + buildObject(); + setGravity(capsule.readFloat("gravity", 9.8f * 3)); + setMaxSlope(capsule.readFloat("maxSlope", 1.0f)); + setFallSpeed(capsule.readFloat("fallSpeed", 55.0f)); + setJumpSpeed(capsule.readFloat("jumpSpeed", 10.0f)); + setUpAxis(capsule.readInt("upAxis", 1)); + setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0)); + setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0)); + setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f())); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + finalizeNativeCharacter(characterId); + } + + private native void finalizeNativeCharacter(long characterId); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java new file mode 100644 index 000000000..052e280e4 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects; + +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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 boolean locationDirty = false; + protected final Quaternion tmp_inverseWorldRotation = new Quaternion(); + 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 (objectId == 0) { +// gObject = new PairCachingGhostObject(); + objectId = createGhostObject(); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Ghost Object {0}", Long.toHexString(objectId)); + setGhostFlags(objectId); + initUserPointer(); + } +// if (gObject == null) { +// gObject = new PairCachingGhostObject(); +// gObject.setCollisionFlags(gObject.getCollisionFlags() | CollisionFlags.NO_CONTACT_RESPONSE); +// } + attachCollisionShape(objectId, collisionShape.getObjectId()); + } + + private native long createGhostObject(); + + private native void setGhostFlags(long objectId); + + @Override + public void setCollisionShape(CollisionShape collisionShape) { + super.setCollisionShape(collisionShape); + if (objectId == 0) { + buildObject(); + } else { + attachCollisionShape(objectId, collisionShape.getObjectId()); + } + } + + /** + * Sets the physics object location + * @param location the location of the actual physics object + */ + public void setPhysicsLocation(Vector3f location) { + setPhysicsLocation(objectId, location); + } + + private native void setPhysicsLocation(long objectId, Vector3f location); + + /** + * Sets the physics object rotation + * @param rotation the rotation of the actual physics object + */ + public void setPhysicsRotation(Matrix3f rotation) { + setPhysicsRotation(objectId, rotation); + } + + private native void setPhysicsRotation(long objectId, Matrix3f rotation); + + /** + * Sets the physics object rotation + * @param rotation the rotation of the actual physics object + */ + public void setPhysicsRotation(Quaternion rotation) { + setPhysicsRotation(objectId, rotation); + } + + private native void setPhysicsRotation(long objectId, Quaternion rotation); + + /** + * @return the physicsLocation + */ + public Vector3f getPhysicsLocation(Vector3f trans) { + if (trans == null) { + trans = new Vector3f(); + } + getPhysicsLocation(objectId, trans); + return trans; + } + + private native void getPhysicsLocation(long objectId, Vector3f vector); + + /** + * @return the physicsLocation + */ + public Quaternion getPhysicsRotation(Quaternion rot) { + if (rot == null) { + rot = new Quaternion(); + } + getPhysicsRotation(objectId, rot); + return rot; + } + + private native void getPhysicsRotation(long objectId, Quaternion rot); + + /** + * @return the physicsLocation + */ + public Matrix3f getPhysicsRotationMatrix(Matrix3f rot) { + if (rot == null) { + rot = new Matrix3f(); + } + getPhysicsRotationMatrix(objectId, rot); + return rot; + } + + private native void getPhysicsRotationMatrix(long objectId, Matrix3f rot); + + /** + * @return the physicsLocation + */ + public Vector3f getPhysicsLocation() { + Vector3f vec = new Vector3f(); + getPhysicsLocation(objectId, vec); + return vec; + } + + /** + * @return the physicsLocation + */ + public Quaternion getPhysicsRotation() { + Quaternion quat = new Quaternion(); + getPhysicsRotation(objectId, quat); + return quat; + } + + public Matrix3f getPhysicsRotationMatrix() { + Matrix3f mtx = new Matrix3f(); + getPhysicsRotationMatrix(objectId, mtx); + return mtx; + } + + /** + * used internally + */ +// public PairCachingGhostObject getObjectId() { +// return gObject; +// } + /** + * destroys this PhysicsGhostNode and removes it from memory + */ + public void destroy() { + } + + /** + * Another Object is overlapping with this GhostNode, + * if and if only there CollisionShapes overlaps. + * They could be both regular PhysicsRigidBodys or PhysicsGhostObjects. + * @return All CollisionObjects overlapping with this GhostNode. + */ + public List getOverlappingObjects() { + overlappingObjects.clear(); + getOverlappingObjects(objectId); +// for (com.bulletphysics.collision.dispatch.CollisionObject collObj : gObject.getOverlappingPairs()) { +// overlappingObjects.add((PhysicsCollisionObject) collObj.getUserPointer()); +// } + return overlappingObjects; + } + + protected native void getOverlappingObjects(long objectId); + + private void addOverlappingObject_native(PhysicsCollisionObject co) { + overlappingObjects.add(co); + } + + /** + * + * @return With how many other CollisionObjects this GhostNode is currently overlapping. + */ + public int getOverlappingCount() { + return getOverlappingCount(objectId); + } + + private native int getOverlappingCount(long objectId); + + /** + * + * @param index The index of the overlapping Node to retrieve. + * @return The Overlapping CollisionObject at the given index. + */ + public PhysicsCollisionObject getOverlapping(int index) { + return overlappingObjects.get(index); + } + + public void setCcdSweptSphereRadius(float radius) { + setCcdSweptSphereRadius(objectId, radius); + } + + private native void setCcdSweptSphereRadius(long objectId, float radius); + + public void setCcdMotionThreshold(float threshold) { + setCcdMotionThreshold(objectId, threshold); + } + + private native void setCcdMotionThreshold(long objectId, float threshold); + + public float getCcdSweptSphereRadius() { + return getCcdSweptSphereRadius(objectId); + } + + private native float getCcdSweptSphereRadius(long objectId); + + public float getCcdMotionThreshold() { + return getCcdMotionThreshold(objectId); + } + + private native float getCcdMotionThreshold(long objectId); + + public float getCcdSquareMotionThreshold() { + return getCcdSquareMotionThreshold(objectId); + } + + private native float getCcdSquareMotionThreshold(long objectId); + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f()); + capsule.write(getPhysicsRotationMatrix(new Matrix3f()), "physicsRotation", new Matrix3f()); + capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0); + capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + buildObject(); + setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f())); + setPhysicsRotation(((Matrix3f) capsule.readSavable("physicsRotation", new Matrix3f()))); + setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0)); + setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0)); + } +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java new file mode 100644 index 000000000..40417c775 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java @@ -0,0 +1,717 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.infos.RigidBodyMotionState; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + *

    PhysicsRigidBody - Basic physics object

    + * @author normenhansen + */ +public class PhysicsRigidBody extends PhysicsCollisionObject { + + protected RigidBodyMotionState motionState = new RigidBodyMotionState(); + protected float mass = 1.0f; + protected boolean kinematic = false; + 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 (collisionShape instanceof MeshCollisionShape && mass != 0) { + throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!"); + } + if (objectId != 0) { + if (isInWorld(objectId)) { + PhysicsSpace.getPhysicsSpace().remove(this); + removed = true; + } + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Clearing RigidBody {0}", Long.toHexString(objectId)); + finalizeNative(objectId); + } + preRebuild(); + objectId = createRigidBody(mass, motionState.getObjectId(), collisionShape.getObjectId()); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created RigidBody {0}", Long.toHexString(objectId)); + postRebuild(); + if (removed) { + PhysicsSpace.getPhysicsSpace().add(this); + } + } + + protected void preRebuild() { + } + + private native long createRigidBody(float mass, long motionStateId, long collisionShapeId); + + protected void postRebuild() { + if (mass == 0.0f) { + setStatic(objectId, true); + } else { + setStatic(objectId, false); + } + initUserPointer(); + } + + /** + * @return the motionState + */ + public RigidBodyMotionState getMotionState() { + return motionState; + } + + public boolean isInWorld() { + return isInWorld(objectId); + } + + private native boolean isInWorld(long objectId); + + /** + * Sets the physics object location + * @param location the location of the actual physics object + */ + public void setPhysicsLocation(Vector3f location) { + setPhysicsLocation(objectId, location); + } + + private native void setPhysicsLocation(long objectId, Vector3f location); + + /** + * Sets the physics object rotation + * @param rotation the rotation of the actual physics object + */ + public void setPhysicsRotation(Matrix3f rotation) { + setPhysicsRotation(objectId, rotation); + } + + private native void setPhysicsRotation(long objectId, Matrix3f rotation); + + /** + * Sets the physics object rotation + * @param rotation the rotation of the actual physics object + */ + public void setPhysicsRotation(Quaternion rotation) { + setPhysicsRotation(objectId, rotation); + } + + private native void setPhysicsRotation(long objectId, Quaternion rotation); + + /** + * @return the physicsLocation + */ + public Vector3f getPhysicsLocation(Vector3f trans) { + if (trans == null) { + trans = new Vector3f(); + } + getPhysicsLocation(objectId, trans); + return trans; + } + + private native void getPhysicsLocation(long objectId, Vector3f vector); + + /** + * @return the physicsLocation + */ + public Quaternion getPhysicsRotation(Quaternion rot) { + if (rot == null) { + rot = new Quaternion(); + } + getPhysicsRotation(objectId, rot); + return rot; + } + + private native void getPhysicsRotation(long objectId, Quaternion rot); + + /** + * @return the physicsLocation + */ + public Matrix3f getPhysicsRotationMatrix(Matrix3f rot) { + if (rot == null) { + rot = new Matrix3f(); + } + getPhysicsRotationMatrix(objectId, rot); + return rot; + } + + private native void getPhysicsRotationMatrix(long objectId, Matrix3f rot); + + /** + * @return the physicsLocation + */ + public Vector3f getPhysicsLocation() { + Vector3f vec = new Vector3f(); + getPhysicsLocation(objectId, vec); + return vec; + } + + /** + * @return the physicsLocation + */ + public Quaternion getPhysicsRotation() { + Quaternion quat = new Quaternion(); + getPhysicsRotation(objectId, quat); + return quat; + } + + public Matrix3f getPhysicsRotationMatrix() { + Matrix3f mtx = new Matrix3f(); + getPhysicsRotationMatrix(objectId, mtx); + return mtx; + } + +// /** +// * Gets the physics object location +// * @param location the location of the actual physics object is stored in this Vector3f +// */ +// public Vector3f getInterpolatedPhysicsLocation(Vector3f location) { +// if (location == null) { +// location = new Vector3f(); +// } +// rBody.getInterpolationWorldTransform(tempTrans); +// return Converter.convert(tempTrans.origin, location); +// } +// +// /** +// * Gets the physics object rotation +// * @param rotation the rotation of the actual physics object is stored in this Matrix3f +// */ +// public Matrix3f getInterpolatedPhysicsRotation(Matrix3f rotation) { +// if (rotation == null) { +// rotation = new Matrix3f(); +// } +// rBody.getInterpolationWorldTransform(tempTrans); +// return Converter.convert(tempTrans.basis, rotation); +// } + /** + * Sets the node to kinematic mode. in this mode the node is not affected by physics + * but affects other physics objects. Iits kinetic force is calculated by the amount + * of movement it is exposed to and its weight. + * @param kinematic + */ + public void setKinematic(boolean kinematic) { + this.kinematic = kinematic; + setKinematic(objectId, kinematic); + } + + private native void setKinematic(long objectId, boolean kinematic); + + public boolean isKinematic() { + return kinematic; + } + + public void setCcdSweptSphereRadius(float radius) { + setCcdSweptSphereRadius(objectId, radius); + } + + private native void setCcdSweptSphereRadius(long objectId, float radius); + + /** + * Sets the amount of motion that has to happen in one physics tick to trigger the continuous motion detection
    + * This avoids the problem of fast objects moving through other objects, set to zero to disable (default) + * @param threshold + */ + public void setCcdMotionThreshold(float threshold) { + setCcdMotionThreshold(objectId, threshold); + } + + private native void setCcdMotionThreshold(long objectId, float threshold); + + public float getCcdSweptSphereRadius() { + return getCcdSweptSphereRadius(objectId); + } + + private native float getCcdSweptSphereRadius(long objectId); + + public float getCcdMotionThreshold() { + return getCcdMotionThreshold(objectId); + } + + private native float getCcdMotionThreshold(long objectId); + + public float getCcdSquareMotionThreshold() { + return getCcdSquareMotionThreshold(objectId); + } + + private native float getCcdSquareMotionThreshold(long objectId); + + public float getMass() { + return mass; + } + + /** + * Sets the mass of this PhysicsRigidBody, objects with mass=0 are static. + * @param mass + */ + public void setMass(float mass) { + this.mass = mass; + if (collisionShape instanceof MeshCollisionShape && mass != 0) { + throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!"); + } + if (objectId != 0) { + if (collisionShape != null) { + updateMassProps(objectId, collisionShape.getObjectId(), mass); + } + if (mass == 0.0f) { + setStatic(objectId, true); + } else { + setStatic(objectId, false); + } + } + } + + private native void setStatic(long objectId, boolean state); + + private native long updateMassProps(long objectId, long collisionShapeId, float mass); + + public Vector3f getGravity() { + return getGravity(null); + } + + public Vector3f getGravity(Vector3f gravity) { + if (gravity == null) { + gravity = new Vector3f(); + } + getGravity(objectId, gravity); + return gravity; + } + + private native void getGravity(long objectId, Vector3f gravity); + + /** + * Set the local gravity of this PhysicsRigidBody
    + * Set this after adding the node to the PhysicsSpace, + * the PhysicsSpace assigns its current gravity to the physics node when its added. + * @param gravity the gravity vector to set + */ + public void setGravity(Vector3f gravity) { + setGravity(objectId, gravity); + } + + private native void setGravity(long objectId, Vector3f gravity); + + public float getFriction() { + return getFriction(objectId); + } + + private native float getFriction(long objectId); + + /** + * Sets the friction of this physics object + * @param friction the friction of this physics object + */ + public void setFriction(float friction) { + setFriction(objectId, friction); + } + + private native void setFriction(long objectId, float friction); + + public void setDamping(float linearDamping, float angularDamping) { + setDamping(objectId, linearDamping, angularDamping); + } + + private native void setDamping(long objectId, float linearDamping, float angularDamping); + +// private native void setRestitution(long objectId, float factor); +// +// public void setLinearDamping(float linearDamping) { +// constructionInfo.linearDamping = linearDamping; +// rBody.setDamping(linearDamping, constructionInfo.angularDamping); +// } +// +// private native void setRestitution(long objectId, float factor); +// + public void setLinearDamping(float linearDamping) { + setDamping(objectId, linearDamping, getAngularDamping()); + } + + public void setAngularDamping(float angularDamping) { + setAngularDamping(objectId, angularDamping); + } + private native void setAngularDamping(long objectId, float factor); + + public float getLinearDamping() { + return getLinearDamping(objectId); + } + + private native float getLinearDamping(long objectId); + + public float getAngularDamping() { + return getAngularDamping(objectId); + } + + private native float getAngularDamping(long objectId); + + public float getRestitution() { + return getRestitution(objectId); + } + + private native float getRestitution(long objectId); + + /** + * The "bouncyness" of the PhysicsRigidBody, best performance if restitution=0 + * @param restitution + */ + public void setRestitution(float restitution) { + setRestitution(objectId, restitution); + } + + private native void setRestitution(long objectId, float factor); + + /** + * Get the current angular velocity of this PhysicsRigidBody + * @return the current linear velocity + */ + public Vector3f getAngularVelocity() { + Vector3f vec = new Vector3f(); + getAngularVelocity(objectId, vec); + return vec; + } + + private native void getAngularVelocity(long objectId, Vector3f vec); + + /** + * Get the current angular velocity of this PhysicsRigidBody + * @param vec the vector to store the velocity in + */ + public void getAngularVelocity(Vector3f vec) { + getAngularVelocity(objectId, vec); + } + + /** + * Sets the angular velocity of this PhysicsRigidBody + * @param vec the angular velocity of this PhysicsRigidBody + */ + public void setAngularVelocity(Vector3f vec) { + setAngularVelocity(objectId, vec); + activate(); + } + + private native void setAngularVelocity(long objectId, Vector3f vec); + + /** + * Get the current linear velocity of this PhysicsRigidBody + * @return the current linear velocity + */ + public Vector3f getLinearVelocity() { + Vector3f vec = new Vector3f(); + getLinearVelocity(objectId, vec); + return vec; + } + + private native void getLinearVelocity(long objectId, Vector3f vec); + + /** + * Get the current linear velocity of this PhysicsRigidBody + * @param vec the vector to store the velocity in + */ + public void getLinearVelocity(Vector3f vec) { + getLinearVelocity(objectId, vec); + } + + /** + * Sets the linear velocity of this PhysicsRigidBody + * @param vec the linear velocity of this PhysicsRigidBody + */ + public void setLinearVelocity(Vector3f vec) { + setLinearVelocity(objectId, vec); + activate(); + } + + private native void setLinearVelocity(long objectId, Vector3f vec); + + /** + * Apply a force to the PhysicsRigidBody, only applies force if the next physics update call + * updates the physics space.
    + * To apply an impulse, use applyImpulse, use applyContinuousForce to apply continuous force. + * @param force the force + * @param location the location of the force + */ + public void applyForce(Vector3f force, Vector3f location) { + applyForce(objectId, force, location); + activate(); + } + + private native void applyForce(long objectId, Vector3f force, Vector3f location); + + /** + * Apply a force to the PhysicsRigidBody, only applies force if the next physics update call + * updates the physics space.
    + * To apply an impulse, use applyImpulse. + * + * @param force the force + */ + public void applyCentralForce(Vector3f force) { + applyCentralForce(objectId, force); + activate(); + } + + private native void applyCentralForce(long objectId, Vector3f force); + + /** + * Apply a force to the PhysicsRigidBody, only applies force if the next physics update call + * updates the physics space.
    + * To apply an impulse, use applyImpulse. + * + * @param torque the torque + */ + public void applyTorque(Vector3f torque) { + applyTorque(objectId, torque); + activate(); + } + + private native void applyTorque(long objectId, Vector3f vec); + + /** + * Apply an impulse to the PhysicsRigidBody in the next physics update. + * @param impulse applied impulse + * @param rel_pos location relative to object + */ + public void applyImpulse(Vector3f impulse, Vector3f rel_pos) { + applyImpulse(objectId, impulse, rel_pos); + activate(); + } + + private native void applyImpulse(long objectId, Vector3f impulse, Vector3f rel_pos); + + /** + * Apply a torque impulse to the PhysicsRigidBody in the next physics update. + * @param vec + */ + public void applyTorqueImpulse(Vector3f vec) { + applyTorqueImpulse(objectId, vec); + activate(); + } + + private native void applyTorqueImpulse(long objectId, Vector3f vec); + + /** + * Clear all forces from the PhysicsRigidBody + * + */ + public void clearForces() { + clearForces(objectId); + } + + private native void clearForces(long objectId); + + public void setCollisionShape(CollisionShape collisionShape) { + super.setCollisionShape(collisionShape); + if (collisionShape instanceof MeshCollisionShape && mass != 0) { + throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!"); + } + if (objectId == 0) { + rebuildRigidBody(); + } else { + setCollisionShape(objectId, collisionShape.getObjectId()); + updateMassProps(objectId, collisionShape.getObjectId(), mass); + } + } + + private native void setCollisionShape(long objectId, long collisionShapeId); + + /** + * reactivates this PhysicsRigidBody when it has been deactivated because it was not moving + */ + public void activate() { + activate(objectId); + } + + private native void activate(long objectId); + + public boolean isActive() { + return isActive(objectId); + } + + private native boolean isActive(long objectId); + + /** + * sets the sleeping thresholds, these define when the object gets deactivated + * to save ressources. Low values keep the object active when it barely moves + * @param linear the linear sleeping threshold + * @param angular the angular sleeping threshold + */ + public void setSleepingThresholds(float linear, float angular) { + setSleepingThresholds(objectId, linear, angular); + } + + private native void setSleepingThresholds(long objectId, float linear, float angular); + + public void setLinearSleepingThreshold(float linearSleepingThreshold) { + setLinearSleepingThreshold(objectId, linearSleepingThreshold); + } + + private native void setLinearSleepingThreshold(long objectId, float linearSleepingThreshold); + + public void setAngularSleepingThreshold(float angularSleepingThreshold) { + setAngularSleepingThreshold(objectId, angularSleepingThreshold); + } + + private native void setAngularSleepingThreshold(long objectId, float angularSleepingThreshold); + + public float getLinearSleepingThreshold() { + return getLinearSleepingThreshold(objectId); + } + + private native float getLinearSleepingThreshold(long objectId); + + public float getAngularSleepingThreshold() { + return getAngularSleepingThreshold(objectId); + } + + private native float getAngularSleepingThreshold(long objectId); + + public float getAngularFactor() { + return getAngularFactor(objectId); + } + + private native float getAngularFactor(long objectId); + + public void setAngularFactor(float factor) { + setAngularFactor(objectId, factor); + } + + private native void setAngularFactor(long objectId, float factor); + + /** + * do not use manually, joints are added automatically + */ + public void addJoint(PhysicsJoint joint) { + if (!joints.contains(joint)) { + joints.add(joint); + } + } + + /** + * + */ + 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; + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + + capsule.write(getMass(), "mass", 1.0f); + + capsule.write(getGravity(), "gravity", Vector3f.ZERO); + capsule.write(getFriction(), "friction", 0.5f); + capsule.write(getRestitution(), "restitution", 0); + capsule.write(getAngularFactor(), "angularFactor", 1); + capsule.write(kinematic, "kinematic", false); + + capsule.write(getLinearDamping(), "linearDamping", 0); + capsule.write(getAngularDamping(), "angularDamping", 0); + capsule.write(getLinearSleepingThreshold(), "linearSleepingThreshold", 0.8f); + capsule.write(getAngularSleepingThreshold(), "angularSleepingThreshold", 1.0f); + + capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0); + capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0); + + capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f()); + capsule.write(getPhysicsRotationMatrix(new Matrix3f()), "physicsRotation", new Matrix3f()); + + capsule.writeSavableArrayList(joints, "joints", null); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + + InputCapsule capsule = e.getCapsule(this); + float mass = capsule.readFloat("mass", 1.0f); + this.mass = mass; + rebuildRigidBody(); + setGravity((Vector3f) capsule.readSavable("gravity", Vector3f.ZERO.clone())); + setFriction(capsule.readFloat("friction", 0.5f)); + setKinematic(capsule.readBoolean("kinematic", false)); + + setRestitution(capsule.readFloat("restitution", 0)); + setAngularFactor(capsule.readFloat("angularFactor", 1)); + setDamping(capsule.readFloat("linearDamping", 0), capsule.readFloat("angularDamping", 0)); + setSleepingThresholds(capsule.readFloat("linearSleepingThreshold", 0.8f), capsule.readFloat("angularSleepingThreshold", 1.0f)); + setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0)); + setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0)); + + setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f())); + setPhysicsRotation((Matrix3f) capsule.readSavable("physicsRotation", new Matrix3f())); + + joints = capsule.readSavableArrayList("joints", null); + } +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java new file mode 100644 index 000000000..63d90ef9f --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java @@ -0,0 +1,534 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.objects.infos.VehicleTuning; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.io.IOException; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + *

    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 long vehicleId = 0; + protected long rayCasterId = 0; + protected VehicleTuning tuning = new VehicleTuning(); + 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 (vehicleId != 0) { + for (int i = 0; i < wheels.size(); i++) { + updateWheelTransform(vehicleId, i, true); + wheels.get(i).updatePhysicsState(); + } + } + } + + private native void updateWheelTransform(long vehicleId, int wheel, boolean interpolated); + + /** + * used internally + */ + public void applyWheelTransforms() { + if (wheels != null) { + for (int i = 0; i < wheels.size(); i++) { + wheels.get(i).applyWheelTransform(); + } + } + } + + @Override + protected void postRebuild() { + super.postRebuild(); + motionState.setVehicle(this); + createVehicle(physicsSpace); + } + + /** + * Used internally, creates the actual vehicle constraint when vehicle is added to phyicsspace + */ + public void createVehicle(PhysicsSpace space) { + physicsSpace = space; + if (space == null) { + return; + } + if (space.getSpaceId() == 0) { + throw new IllegalStateException("Physics space is not initialized!"); + } + if (rayCasterId != 0) { + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Clearing RayCaster {0}", Long.toHexString(rayCasterId)); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Clearing Vehicle {0}", Long.toHexString(vehicleId)); + finalizeNative(rayCasterId, vehicleId); + } + rayCasterId = createVehicleRaycaster(objectId, space.getSpaceId()); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created RayCaster {0}", Long.toHexString(rayCasterId)); + vehicleId = createRaycastVehicle(objectId, rayCasterId); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Vehicle {0}", Long.toHexString(vehicleId)); + setCoordinateSystem(vehicleId, 0, 1, 2); + for (VehicleWheel wheel : wheels) { + wheel.setVehicleId(vehicleId, addWheel(vehicleId, wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), tuning, wheel.isFrontWheel())); + } + } + + private native long createVehicleRaycaster(long objectId, long physicsSpaceId); + + private native long createRaycastVehicle(long objectId, long rayCasterId); + + private native void setCoordinateSystem(long objectId, int a, int b, int c); + + private native int addWheel(long objectId, Vector3f location, Vector3f direction, Vector3f axle, float restLength, float radius, VehicleTuning tuning, boolean frontWheel); + + /** + * Add a wheel to this vehicle + * @param connectionPoint The starting point of the ray, where the suspension connects to the chassis (chassis space) + * @param direction the direction of the wheel (should be -Y / 0,-1,0 for a normal car) + * @param axle The axis of the wheel, pointing right in vehicle direction (should be -X / -1,0,0 for a normal car) + * @param suspensionRestLength The current length of the suspension (metres) + * @param wheelRadius the wheel radius + * @param isFrontWheel sets if this wheel is a front wheel (steering) + * @return the PhysicsVehicleWheel object to get/set infos on the wheel + */ + public VehicleWheel addWheel(Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) { + return addWheel(null, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel); + } + + /** + * Add a wheel to this vehicle + * @param spat the wheel Geometry + * @param connectionPoint The starting point of the ray, where the suspension connects to the chassis (chassis space) + * @param direction the direction of the wheel (should be -Y / 0,-1,0 for a normal car) + * @param axle The axis of the wheel, pointing right in vehicle direction (should be -X / -1,0,0 for a normal car) + * @param suspensionRestLength The current length of the suspension (metres) + * @param wheelRadius the wheel radius + * @param isFrontWheel sets if this wheel is a front wheel (steering) + * @return the PhysicsVehicleWheel object to get/set infos on the wheel + */ + public VehicleWheel addWheel(Spatial spat, Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) { + VehicleWheel wheel = null; + if (spat == null) { + wheel = new VehicleWheel(connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel); + } else { + wheel = new VehicleWheel(spat, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel); + } + wheel.setFrictionSlip(tuning.frictionSlip); + wheel.setMaxSuspensionTravelCm(tuning.maxSuspensionTravelCm); + wheel.setSuspensionStiffness(tuning.suspensionStiffness); + wheel.setWheelsDampingCompression(tuning.suspensionCompression); + wheel.setWheelsDampingRelaxation(tuning.suspensionDamping); + wheel.setMaxSuspensionForce(tuning.maxSuspensionForce); + wheels.add(wheel); + if (vehicleId != 0) { + wheel.setVehicleId(vehicleId, addWheel(vehicleId, wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), tuning, wheel.isFrontWheel())); + } + 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() { + resetSuspension(vehicleId); + } + + private native void resetSuspension(long vehicleId); + + /** + * Apply the given engine force to all wheels, works continuously + * @param force the force + */ + public void accelerate(float force) { + for (int i = 0; i < wheels.size(); i++) { + accelerate(i, force); + } + } + + /** + * Apply the given engine force, works continuously + * @param wheel the wheel to apply the force on + * @param force the force + */ + public void accelerate(int wheel, float force) { + applyEngineForce(vehicleId, wheel, force); + + } + + private native void applyEngineForce(long vehicleId, int wheel, float force); + + /** + * Set the given steering value to all front wheels (0 = forward) + * @param value the steering angle of the front wheels (Pi = 360deg) + */ + public void steer(float value) { + for (int i = 0; i < wheels.size(); i++) { + if (getWheel(i).isFrontWheel()) { + steer(i, value); + } + } + } + + /** + * Set the given steering value to the given wheel (0 = forward) + * @param wheel the wheel to set the steering on + * @param value the steering angle of the front wheels (Pi = 360deg) + */ + public void steer(int wheel, float value) { + steer(vehicleId, wheel, value); + } + + private native void steer(long vehicleId, int wheel, float value); + + /** + * Apply the given brake force to all wheels, works continuously + * @param force the force + */ + public void brake(float force) { + for (int i = 0; i < wheels.size(); i++) { + brake(i, force); + } + } + + /** + * Apply the given brake force, works continuously + * @param wheel the wheel to apply the force on + * @param force the force + */ + public void brake(int wheel, float force) { + brake(vehicleId, wheel, force); + } + + private native void brake(long vehicleId, int wheel, float force); + + /** + * Get the current speed of the vehicle in km/h + * @return + */ + public float getCurrentVehicleSpeedKmHour() { + return getCurrentVehicleSpeedKmHour(vehicleId); + } + + private native float getCurrentVehicleSpeedKmHour(long vehicleId); + + /** + * Get the current forward vector of the vehicle in world coordinates + * @param vector + * @return + */ + public Vector3f getForwardVector(Vector3f vector) { + if (vector == null) { + vector = new Vector3f(); + } + getForwardVector(vehicleId, vector); + return vector; + } + + private native void getForwardVector(long objectId, Vector3f vector); + + /** + * used internally + */ + public long getVehicleId() { + return vehicleId; + } + + @Override + 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); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing RayCaster {0}", Long.toHexString(rayCasterId)); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Vehicle {0}", Long.toHexString(vehicleId)); + finalizeNative(rayCasterId, vehicleId); + } + + private native void finalizeNative(long rayCaster, long vehicle); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java new file mode 100644 index 000000000..99f132ff1 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java @@ -0,0 +1,423 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects; + +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.export.*; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.io.IOException; + +/** + * Stores info about one wheel of a PhysicsVehicle + * @author normenhansen + */ +public class VehicleWheel implements Savable { + + protected long wheelId = 0; + protected int wheelIndex = 0; + protected boolean frontWheel; + protected Vector3f location = new Vector3f(); + protected Vector3f direction = new Vector3f(); + protected Vector3f axle = new Vector3f(); + protected float suspensionStiffness = 20.0f; + protected float wheelsDampingRelaxation = 2.3f; + protected float wheelsDampingCompression = 4.4f; + protected float frictionSlip = 10.5f; + protected float rollInfluence = 1.0f; + protected float maxSuspensionTravelCm = 500f; + protected float maxSuspensionForce = 6000f; + protected float radius = 0.5f; + protected float restLength = 1f; + protected Vector3f wheelWorldLocation = new Vector3f(); + protected Quaternion wheelWorldRotation = new Quaternion(); + protected Spatial wheelSpatial; + protected Matrix3f tmp_Matrix = new com.jme3.math.Matrix3f(); + protected final Quaternion tmp_inverseWorldRotation = new Quaternion(); + private boolean applyLocal = false; + + public VehicleWheel() { + } + + public VehicleWheel(Spatial spat, Vector3f location, Vector3f direction, Vector3f axle, + float restLength, float radius, boolean frontWheel) { + this(location, direction, axle, restLength, radius, frontWheel); + wheelSpatial = spat; + } + + public VehicleWheel(Vector3f location, Vector3f direction, Vector3f axle, + float restLength, float radius, boolean frontWheel) { + this.location.set(location); + this.direction.set(direction); + this.axle.set(axle); + this.frontWheel = frontWheel; + this.restLength = restLength; + this.radius = radius; + } + + public void updatePhysicsState() { + getWheelLocation(wheelId, wheelIndex, wheelWorldLocation); + getWheelRotation(wheelId, wheelIndex, tmp_Matrix); + wheelWorldRotation.fromRotationMatrix(tmp_Matrix); + } + + private native void getWheelLocation(long vehicleId, int wheelId, Vector3f location); + + private native void getWheelRotation(long vehicleId, int wheelId, Matrix3f location); + + public void applyWheelTransform() { + if (wheelSpatial == null) { + return; + } + Quaternion localRotationQuat = wheelSpatial.getLocalRotation(); + Vector3f localLocation = wheelSpatial.getLocalTranslation(); + if (!applyLocal && wheelSpatial.getParent() != null) { + localLocation.set(wheelWorldLocation).subtractLocal(wheelSpatial.getParent().getWorldTranslation()); + localLocation.divideLocal(wheelSpatial.getParent().getWorldScale()); + tmp_inverseWorldRotation.set(wheelSpatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation); + + localRotationQuat.set(wheelWorldRotation); + tmp_inverseWorldRotation.set(wheelSpatial.getParent().getWorldRotation()).inverseLocal().mult(localRotationQuat, localRotationQuat); + + wheelSpatial.setLocalTranslation(localLocation); + wheelSpatial.setLocalRotation(localRotationQuat); + } else { + wheelSpatial.setLocalTranslation(wheelWorldLocation); + wheelSpatial.setLocalRotation(wheelWorldRotation); + } + } + + public long getWheelId() { + return wheelId; + } + + public void setVehicleId(long vehicleId, int wheelIndex) { + this.wheelId = vehicleId; + this.wheelIndex = wheelIndex; + applyInfo(); + } + + public boolean isFrontWheel() { + return frontWheel; + } + + public void setFrontWheel(boolean frontWheel) { + this.frontWheel = frontWheel; + applyInfo(); + } + + public Vector3f getLocation() { + return location; + } + + public Vector3f getDirection() { + return direction; + } + + public Vector3f getAxle() { + return axle; + } + + public float getSuspensionStiffness() { + return suspensionStiffness; + } + + /** + * the stiffness constant for the suspension. 10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car + * @param suspensionStiffness + */ + public void setSuspensionStiffness(float suspensionStiffness) { + this.suspensionStiffness = suspensionStiffness; + applyInfo(); + } + + public float getWheelsDampingRelaxation() { + return wheelsDampingRelaxation; + } + + /** + * the damping coefficient for when the suspension is expanding. + * See the comments for setWheelsDampingCompression for how to set k. + * @param wheelsDampingRelaxation + */ + public void setWheelsDampingRelaxation(float wheelsDampingRelaxation) { + this.wheelsDampingRelaxation = wheelsDampingRelaxation; + applyInfo(); + } + + public float getWheelsDampingCompression() { + return wheelsDampingCompression; + } + + /** + * the damping coefficient for when the suspension is compressed. + * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.
    + * 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 (wheelId == 0) { + return; + } + applyInfo(wheelId, wheelIndex, suspensionStiffness, wheelsDampingRelaxation, wheelsDampingCompression, frictionSlip, rollInfluence, maxSuspensionTravelCm, maxSuspensionForce, radius, frontWheel, restLength); + } + + private native void applyInfo(long wheelId, int wheelIndex, + float suspensionStiffness, + float wheelsDampingRelaxation, + float wheelsDampingCompression, + float frictionSlip, + float rollInfluence, + float maxSuspensionTravelCm, + float maxSuspensionForce, + float wheelsRadius, + boolean frontWheel, + float suspensionRestLength); + + public float getRadius() { + return radius; + } + + public void setRadius(float radius) { + this.radius = radius; + applyInfo(); + } + + public float getRestLength() { + return restLength; + } + + public void setRestLength(float restLength) { + this.restLength = restLength; + applyInfo(); + } + + /** + * returns the object this wheel is in contact with or null if no contact + * @return the PhysicsCollisionObject (PhysicsRigidBody, PhysicsGhostObject) + */ + public PhysicsCollisionObject getGroundObject() { +// if (wheelInfo.raycastInfo.groundObject == null) { +// return null; +// } else if (wheelInfo.raycastInfo.groundObject instanceof RigidBody) { +// System.out.println("RigidBody"); +// return (PhysicsRigidBody) ((RigidBody) wheelInfo.raycastInfo.groundObject).getUserPointer(); +// } else { + return null; +// } + } + + /** + * returns the location where the wheel collides with the ground (world space) + */ + public Vector3f getCollisionLocation(Vector3f vec) { + getCollisionLocation(wheelId, wheelIndex, vec); + return vec; + } + + private native void getCollisionLocation(long wheelId, int wheelIndex, Vector3f vec); + + /** + * returns the location where the wheel collides with the ground (world space) + */ + public Vector3f getCollisionLocation() { + Vector3f vec = new Vector3f(); + getCollisionLocation(wheelId, wheelIndex, vec); + return vec; + } + + /** + * returns the normal where the wheel collides with the ground (world space) + */ + public Vector3f getCollisionNormal(Vector3f vec) { + getCollisionNormal(wheelId, wheelIndex, vec); + return vec; + } + + private native void getCollisionNormal(long wheelId, int wheelIndex, Vector3f vec); + + /** + * returns the normal where the wheel collides with the ground (world space) + */ + public Vector3f getCollisionNormal() { + Vector3f vec = new Vector3f(); + getCollisionNormal(wheelId, wheelIndex, vec); + return vec; + } + + /** + * returns how much the wheel skids on the ground (for skid sounds/smoke etc.)
    + * 0.0 = wheels are sliding, 1.0 = wheels have traction. + */ + public float getSkidInfo() { + return getSkidInfo(wheelId, wheelIndex); + } + + public native float getSkidInfo(long wheelId, int wheelIndex); + + /** + * returns how many degrees the wheel has turned since the last physics + * step. + */ + public float getDeltaRotation() { + return getDeltaRotation(wheelId, wheelIndex); + } + + public native float getDeltaRotation(long wheelId, int wheelIndex); + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule capsule = im.getCapsule(this); + wheelSpatial = (Spatial) capsule.readSavable("wheelSpatial", null); + frontWheel = capsule.readBoolean("frontWheel", false); + location = (Vector3f) capsule.readSavable("wheelLocation", new Vector3f()); + direction = (Vector3f) capsule.readSavable("wheelDirection", new Vector3f()); + axle = (Vector3f) capsule.readSavable("wheelAxle", new Vector3f()); + suspensionStiffness = capsule.readFloat("suspensionStiffness", 20.0f); + wheelsDampingRelaxation = capsule.readFloat("wheelsDampingRelaxation", 2.3f); + wheelsDampingCompression = capsule.readFloat("wheelsDampingCompression", 4.4f); + frictionSlip = capsule.readFloat("frictionSlip", 10.5f); + rollInfluence = capsule.readFloat("rollInfluence", 1.0f); + maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f); + maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f); + radius = capsule.readFloat("wheelRadius", 0.5f); + restLength = capsule.readFloat("restLength", 1f); + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(wheelSpatial, "wheelSpatial", null); + capsule.write(frontWheel, "frontWheel", false); + capsule.write(location, "wheelLocation", new Vector3f()); + capsule.write(direction, "wheelDirection", new Vector3f()); + capsule.write(axle, "wheelAxle", new Vector3f()); + capsule.write(suspensionStiffness, "suspensionStiffness", 20.0f); + capsule.write(wheelsDampingRelaxation, "wheelsDampingRelaxation", 2.3f); + capsule.write(wheelsDampingCompression, "wheelsDampingCompression", 4.4f); + capsule.write(frictionSlip, "frictionSlip", 10.5f); + capsule.write(rollInfluence, "rollInfluence", 1.0f); + capsule.write(maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f); + capsule.write(maxSuspensionForce, "maxSuspensionForce", 6000f); + capsule.write(radius, "wheelRadius", 0.5f); + capsule.write(restLength, "restLength", 1f); + } + + /** + * @return the wheelSpatial + */ + public Spatial getWheelSpatial() { + return wheelSpatial; + } + + /** + * @param wheelSpatial the wheelSpatial to set + */ + public void setWheelSpatial(Spatial wheelSpatial) { + this.wheelSpatial = wheelSpatial; + } + + public boolean isApplyLocal() { + return applyLocal; + } + + public void setApplyLocal(boolean applyLocal) { + this.applyLocal = applyLocal; + } + +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java new file mode 100644 index 000000000..3406e7ae5 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects.infos; + +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * stores transform info of a PhysicsNode in a threadsafe manner to + * allow multithreaded access from the jme scenegraph and the bullet physicsspace + * @author normenhansen + */ +public class RigidBodyMotionState { + long motionStateId = 0; + private Vector3f worldLocation = new Vector3f(); + private Matrix3f worldRotation = new Matrix3f(); + private Quaternion worldRotationQuat = new Quaternion(); + private Quaternion tmp_inverseWorldRotation = new Quaternion(); + private PhysicsVehicle vehicle; + private boolean applyPhysicsLocal = false; +// protected LinkedList listeners = new LinkedList(); + + public RigidBodyMotionState() { + this.motionStateId = createMotionState(); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created MotionState {0}", Long.toHexString(motionStateId)); + } + + private native long createMotionState(); + + /** + * applies the current transform to the given jme Node if the location has been updated on the physics side + * @param spatial + */ + public boolean applyTransform(Spatial spatial) { + Vector3f localLocation = spatial.getLocalTranslation(); + Quaternion localRotationQuat = spatial.getLocalRotation(); + boolean physicsLocationDirty = applyTransform(motionStateId, localLocation, localRotationQuat); + if (!physicsLocationDirty) { + return false; + } + if (!applyPhysicsLocal && spatial.getParent() != null) { + localLocation.subtractLocal(spatial.getParent().getWorldTranslation()); + localLocation.divideLocal(spatial.getParent().getWorldScale()); + tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation); + +// localRotationQuat.set(worldRotationQuat); + tmp_inverseWorldRotation.mult(localRotationQuat, localRotationQuat); + + spatial.setLocalTranslation(localLocation); + spatial.setLocalRotation(localRotationQuat); + } else { + spatial.setLocalTranslation(localLocation); + spatial.setLocalRotation(localRotationQuat); +// spatial.setLocalTranslation(worldLocation); +// spatial.setLocalRotation(worldRotationQuat); + } + if (vehicle != null) { + vehicle.updateWheels(); + } + return true; + } + + private native boolean applyTransform(long stateId, Vector3f location, Quaternion rotation); + + /** + * @return the worldLocation + */ + public Vector3f getWorldLocation() { + getWorldLocation(motionStateId, worldLocation); + return worldLocation; + } + + private native void getWorldLocation(long stateId, Vector3f vec); + + /** + * @return the worldRotation + */ + public Matrix3f getWorldRotation() { + getWorldRotation(motionStateId, worldRotation); + return worldRotation; + } + + private native void getWorldRotation(long stateId, Matrix3f vec); + + /** + * @return the worldRotationQuat + */ + public Quaternion getWorldRotationQuat() { + getWorldRotationQuat(motionStateId, worldRotationQuat); + return worldRotationQuat; + } + + private native void getWorldRotationQuat(long stateId, Quaternion vec); + + /** + * @param vehicle the vehicle to set + */ + public void setVehicle(PhysicsVehicle vehicle) { + this.vehicle = vehicle; + } + + public boolean isApplyPhysicsLocal() { + return applyPhysicsLocal; + } + + public void setApplyPhysicsLocal(boolean applyPhysicsLocal) { + this.applyPhysicsLocal = applyPhysicsLocal; + } + + public long getObjectId(){ + return motionStateId; + } +// public void addMotionStateListener(PhysicsMotionStateListener listener){ +// listeners.add(listener); +// } +// +// public void removeMotionStateListener(PhysicsMotionStateListener listener){ +// listeners.remove(listener); +// } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing MotionState {0}", Long.toHexString(motionStateId)); + finalizeNative(motionStateId); + } + + private native void finalizeNative(long objectId); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/infos/VehicleTuning.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/infos/VehicleTuning.java new file mode 100644 index 000000000..47f344bd7 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/objects/infos/VehicleTuning.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects.infos; + +/** + * + * @author normenhansen + */ +public class VehicleTuning { + public float suspensionStiffness = 5.88f; + public float suspensionCompression = 0.83f; + public float suspensionDamping = 0.88f; + public float maxSuspensionTravelCm = 500f; + public float maxSuspensionForce = 6000f; + public float frictionSlip = 10.5f; +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugMeshCallback.java b/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugMeshCallback.java new file mode 100644 index 000000000..fbec02c9c --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugMeshCallback.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.util; + +import com.jme3.math.Vector3f; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; +import java.util.ArrayList; + +/** + * + * @author normenhansen + */ +public class DebugMeshCallback { + + private ArrayList list = new ArrayList(); + + public void addVector(float x, float y, float z, int part, int index) { + list.add(new Vector3f(x, y, z)); + } + + public FloatBuffer getVertices() { + FloatBuffer buf = BufferUtils.createFloatBuffer(list.size() * 3); + for (int i = 0; i < list.size(); i++) { + Vector3f vector3f = list.get(i); + buf.put(vector3f.x); + buf.put(vector3f.y); + buf.put(vector3f.z); + } + return buf; + } +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java b/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java new file mode 100644 index 000000000..c731035ca --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.util; + +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.math.Matrix3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.TempVars; +import java.util.Iterator; +import java.util.List; + +/** + * + * @author CJ Hare, normenhansen + */ +public class DebugShapeFactory { + + /** The maximum corner for the aabb used for triangles to include in ConcaveShape processing.*/ +// private static final Vector3f aabbMax = new Vector3f(1e30f, 1e30f, 1e30f); + /** The minimum corner for the aabb used for triangles to include in ConcaveShape processing.*/ +// private static final Vector3f aabbMin = new Vector3f(-1e30f, -1e30f, -1e30f); + + /** + * Creates a debug shape from the given collision shape. This is mostly used internally.
    + * 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(); + Matrix3f tempRot = vars.tempMat3; + + tempRot.set(geometry.getLocalRotation()); + childCollisionShape.rotation.mult(tempRot, tempRot); + geometry.setLocalRotation(tempRot); + + vars.release(); + + node.attachChild(geometry); + } + debugShape = node; + } else { + debugShape = createDebugShape(collisionShape); + } + if (debugShape == null) { + return null; + } + debugShape.updateGeometricState(); + return debugShape; + } + + private static Geometry createDebugShape(CollisionShape shape) { + Geometry geom = new Geometry(); + geom.setMesh(DebugShapeFactory.getDebugMesh(shape)); +// geom.setLocalScale(shape.getScale()); + geom.updateModelBound(); + return geom; + } + + public static Mesh getDebugMesh(CollisionShape shape) { + Mesh mesh = new Mesh(); + mesh = new Mesh(); + DebugMeshCallback callback = new DebugMeshCallback(); + getVertices(shape.getObjectId(), callback); + mesh.setBuffer(Type.Position, 3, callback.getVertices()); + mesh.getFloatBuffer(Type.Position).clear(); + return mesh; + } + + private static native void getVertices(long shapeId, DebugMeshCallback buffer); +} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/util/NativeMeshUtil.java b/jme3-bullet/src/main/java/com/jme3/bullet/util/NativeMeshUtil.java new file mode 100644 index 000000000..fc3ad53d6 --- /dev/null +++ b/jme3-bullet/src/main/java/com/jme3/bullet/util/NativeMeshUtil.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.util; + +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +/** + * + * @author normenhansen + */ +public class NativeMeshUtil { + + public static long getTriangleIndexVertexArray(Mesh mesh){ + ByteBuffer triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4); + ByteBuffer vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4); + int numVertices = mesh.getVertexCount(); + int vertexStride = 12; //3 verts * 4 bytes per. + int numTriangles = mesh.getTriangleCount(); + int triangleIndexStride = 12; //3 index entries * 4 bytes each. + + IndexBuffer indices = mesh.getIndicesAsList(); + FloatBuffer vertices = mesh.getFloatBuffer(Type.Position); + vertices.rewind(); + + int verticesLength = mesh.getVertexCount() * 3; + for (int i = 0; i < verticesLength; i++) { + float tempFloat = vertices.get(); + vertexBase.putFloat(tempFloat); + } + + int indicesLength = mesh.getTriangleCount() * 3; + for (int i = 0; i < indicesLength; i++) { + triangleIndexBase.putInt(indices.get(i)); + } + vertices.rewind(); + vertices.clear(); + + return createTriangleIndexVertexArray(triangleIndexBase, vertexBase, numTriangles, numVertices, vertexStride, triangleIndexStride); + } + + public static native long createTriangleIndexVertexArray(ByteBuffer triangleIndexBase, ByteBuffer vertexBase, int numTraingles, int numVertices, int vertextStride, int triangleIndexStride); + +} diff --git a/jme3-core/src/main/java/checkers/quals/DefaultLocation.java b/jme3-core/src/main/java/checkers/quals/DefaultLocation.java new file mode 100644 index 000000000..90dc78a03 --- /dev/null +++ b/jme3-core/src/main/java/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 parameterized classes that provide a lot of + // static methods with the same generic parameters as the class. + UPPER_BOUNDS; + +} diff --git a/jme3-core/src/main/java/checkers/quals/DefaultQualifier.java b/jme3-core/src/main/java/checkers/quals/DefaultQualifier.java new file mode 100644 index 000000000..5caf5dabb --- /dev/null +++ b/jme3-core/src/main/java/checkers/quals/DefaultQualifier.java @@ -0,0 +1,44 @@ +package checkers.quals; + +import java.lang.annotation.Documented; +import static java.lang.annotation.ElementType.*; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Applied to a declaration of a package, type, method, variable, etc., + * specifies that the given annotation should be the default. The default is + * applied to all types within the declaration for which no other + * annotation is explicitly written. + * If multiple DefaultQualifier annotations are in scope, the innermost one + * takes precedence. + * DefaultQualifier takes precedence over {@link DefaultQualifierInHierarchy}. + *

    + * + * 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/jme3-core/src/main/java/checkers/quals/DefaultQualifierInHierarchy.java b/jme3-core/src/main/java/checkers/quals/DefaultQualifierInHierarchy.java new file mode 100644 index 000000000..949846dd6 --- /dev/null +++ b/jme3-core/src/main/java/checkers/quals/DefaultQualifierInHierarchy.java @@ -0,0 +1,29 @@ +package checkers.quals; + +import java.lang.annotation.Documented; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the annotated qualifier is the default qualifier in the + * qualifier hierarchy: it applies if the programmer writes no explicit + * qualifier. + *

    + * + * 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/jme3-core/src/main/java/checkers/quals/DefaultQualifiers.java b/jme3-core/src/main/java/checkers/quals/DefaultQualifiers.java new file mode 100644 index 000000000..e483e63e7 --- /dev/null +++ b/jme3-core/src/main/java/checkers/quals/DefaultQualifiers.java @@ -0,0 +1,35 @@ +package checkers.quals; + +import java.lang.annotation.Documented; +import static java.lang.annotation.ElementType.*; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies the annotations to be included in a type without having to provide + * them explicitly. + * + * This annotation permits specifying multiple default qualifiers for more + * than one type system. It is necessary because Java forbids multiple + * annotations of the same name at a single location. + * + * Example: + * + *

    + *   @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/jme3-core/src/main/java/checkers/quals/Dependent.java b/jme3-core/src/main/java/checkers/quals/Dependent.java new file mode 100644 index 000000000..88d3b39d1 --- /dev/null +++ b/jme3-core/src/main/java/checkers/quals/Dependent.java @@ -0,0 +1,38 @@ +package checkers.quals; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Refines the qualified type of the annotated field or variable based on the + * qualified type of the receiver. The annotation declares a relationship + * between multiple type qualifier hierarchies. + * + *

    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/jme3-core/src/main/java/checkers/quals/SubtypeOf.java b/jme3-core/src/main/java/checkers/quals/SubtypeOf.java new file mode 100644 index 000000000..ffc8be045 --- /dev/null +++ b/jme3-core/src/main/java/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/jme3-core/src/main/java/checkers/quals/TypeQualifier.java b/jme3-core/src/main/java/checkers/quals/TypeQualifier.java new file mode 100644 index 000000000..25c4f7bbd --- /dev/null +++ b/jme3-core/src/main/java/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/jme3-core/src/main/java/checkers/quals/Unqualified.java b/jme3-core/src/main/java/checkers/quals/Unqualified.java new file mode 100644 index 000000000..a210b704b --- /dev/null +++ b/jme3-core/src/main/java/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/jme3-core/src/main/java/checkers/quals/Unused.java b/jme3-core/src/main/java/checkers/quals/Unused.java new file mode 100644 index 000000000..17a9c8039 --- /dev/null +++ b/jme3-core/src/main/java/checkers/quals/Unused.java @@ -0,0 +1,43 @@ +package checkers.quals; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.FIELD; + +/** + * 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/jme3-core/src/main/java/checkers/quals/package-info.java b/jme3-core/src/main/java/checkers/quals/package-info.java new file mode 100644 index 000000000..5ae80a7d6 --- /dev/null +++ b/jme3-core/src/main/java/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/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java b/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java new file mode 100644 index 000000000..7c79bae22 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.math.FastMath; +import com.jme3.util.TempVars; +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 static final float DEFAULT_BLEND_TIME = 0.15f; + + private AnimControl control; + + private BitSet affectedBones; + + private Animation animation; + private Animation blendFrom; + private float time; + private float speed; + private float timeBlendFrom; + private float speedBlendFrom; + private boolean notified=false; + + private LoopMode loopMode, loopModeBlendFrom; + + private float blendAmount = 1f; + private float blendRate = 0; + + private static float clampWrapTime(float t, float max, LoopMode loopMode){ + if (t == 0) { + return 0; // prevent division by 0 errors + } + + switch (loopMode) { + case Cycle: + boolean sign = ((int) (t / max) % 2) != 0; + float result; + +// if (t < 0){ +// result = sign ? t % max : -(max + (t % max)); +// } else { + // NOTE: This algorithm seems stable for both high and low + // tpf so for now its a keeper. + result = sign ? -(max - (t % max)) : t % max; +// } + +// if (result <= 0 || result >= max) { +// System.out.println("SIGN: " + sign + ", RESULT: " + result + ", T: " + t + ", M: " + max); +// } + + return result; + case DontLoop: + return t > max ? max : (t < 0 ? 0 : t); + case Loop: + return t % max; + } + return t; + + +// if (max == Float.POSITIVE_INFINITY) +// return t; +// +// if (t < 0f){ +// //float tMod = -(-t % max); +// switch (loopMode){ +// case DontLoop: +// return 0; +// case Cycle: +// return t; +// case Loop: +// return max - t; +// } +// }else if (t > max){ +// switch (loopMode){ +// case DontLoop: +// return max; +// case Cycle: +// return -(2f * max - t) % max; +// case Loop: +// return t % max; +// } +// } +// +// return t; + } + + AnimChannel(AnimControl control){ + this.control = control; + } + + /** + * Returns the parent control of this AnimChannel. + * + * @return the parent control of this AnimChannel. + * @see AnimControl + */ + public AnimControl getControl() { + return control; + } + + /** + * @return The name of the currently playing animation, or null if + * none is assigned. + * + * @see AnimChannel#setAnim(java.lang.String) + */ + public String getAnimationName() { + return animation != null ? animation.getName() : null; + } + + /** + * @return The loop mode currently set for the animation. The loop mode + * determines what will happen to the animation once it finishes + * playing. + * + * For more information, see the LoopMode enum class. + * @see LoopMode + * @see AnimChannel#setLoopMode(com.jme3.animation.LoopMode) + */ + public LoopMode getLoopMode() { + return loopMode; + } + + /** + * @param loopMode Set the loop mode for the channel. The loop mode + * determines what will happen to the animation once it finishes + * playing. + * + * For more information, see the LoopMode enum class. + * @see LoopMode + */ + public void setLoopMode(LoopMode loopMode) { + this.loopMode = loopMode; + } + + /** + * @return The speed that is assigned to the animation channel. The speed + * is a scale value starting from 0.0, at 1.0 the animation will play + * at its default speed. + * + * @see AnimChannel#setSpeed(float) + */ + public float getSpeed() { + return speed; + } + + /** + * @param speed Set the speed of the animation channel. The speed + * is a scale value starting from 0.0, at 1.0 the animation will play + * at its default speed. + */ + public void setSpeed(float speed) { + this.speed = speed; + } + + /** + * @return The time of the currently playing animation. The time + * starts at 0 and continues on until getAnimMaxTime(). + * + * @see AnimChannel#setTime(float) + */ + public float getTime() { + return time; + } + + /** + * @param time Set the time of the currently playing animation, the time + * is clamped from 0 to {@link #getAnimMaxTime()}. + */ + public void setTime(float time) { + this.time = FastMath.clamp(time, 0, getAnimMaxTime()); + } + + /** + * @return The length of the currently playing animation, or zero + * if no animation is playing. + * + * @see AnimChannel#getTime() + */ + public float getAnimMaxTime(){ + return animation != null ? animation.getLength() : 0f; + } + + /** + * Set the current animation that is played by this AnimChannel. + *

    + * 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 IllegalArgumentException("name cannot be null"); + + if (blendTime < 0f) + throw new IllegalArgumentException("blendTime cannot be less than zero"); + + Animation anim = control.animationMap.get(name); + if (anim == null) + throw new IllegalArgumentException("Cannot find animation named: '"+name+"'"); + + control.notifyAnimChange(this, name); + + if (animation != null && blendTime > 0f){ + // activate blending + blendFrom = animation; + timeBlendFrom = time; + speedBlendFrom = speed; + loopModeBlendFrom = loopMode; + blendAmount = 0f; + blendRate = 1f / blendTime; + }else{ + blendFrom = null; + } + + animation = anim; + time = 0; + speed = 1f; + loopMode = LoopMode.Loop; + notified = false; + } + + /** + * Set the current animation that is played by this AnimChannel. + *

    + * See {@link #setAnim(java.lang.String, float)}. + * The blendTime argument by default is 150 milliseconds. + * + * @param name The name of the animation to play + */ + public void setAnim(String name){ + setAnim(name, DEFAULT_BLEND_TIME); + } + + /** + * Add all the bones of the model's skeleton to be + * influenced by this animation channel. + */ + public void addAllBones() { + affectedBones = null; + } + + /** + * Add a single bone to be influenced by this animation channel. + */ + public void addBone(String name) { + addBone(control.getSkeleton().getBone(name)); + } + + /** + * Add a single bone to be influenced by this animation channel. + */ + public void addBone(Bone bone) { + int boneIndex = control.getSkeleton().getBoneIndex(bone); + if(affectedBones == null) { + affectedBones = new BitSet(control.getSkeleton().getBoneCount()); + } + affectedBones.set(boneIndex); + } + + /** + * Add bones to be influenced by this animation channel starting from the + * given bone name and going toward the root bone. + */ + public void addToRootBone(String name) { + addToRootBone(control.getSkeleton().getBone(name)); + } + + /** + * Add bones to be influenced by this animation channel starting from the + * given bone and going toward the root bone. + */ + public void addToRootBone(Bone bone) { + addBone(bone); + while (bone.getParent() != null) { + bone = bone.getParent(); + addBone(bone); + } + } + + /** + * Add bones to be influenced by this animation channel, starting + * from the given named bone and going toward its children. + */ + public void addFromRootBone(String name) { + addFromRootBone(control.getSkeleton().getBone(name)); + } + + /** + * Add bones to be influenced by this animation channel, starting + * from the given bone and going toward its children. + */ + public void addFromRootBone(Bone bone) { + addBone(bone); + if (bone.getChildren() == null) + return; + for (Bone childBone : bone.getChildren()) { + addBone(childBone); + addFromRootBone(childBone); + } + } + + BitSet getAffectedBones(){ + return affectedBones; + } + + public void reset(boolean rewind){ + if(rewind){ + setTime(0); + if(control.getSkeleton()!=null){ + control.getSkeleton().resetAndUpdate(); + }else{ + TempVars vars = TempVars.get(); + update(0, vars); + vars.release(); + } + } + animation = null; + // System.out.println("Setting notified false"); + notified = false; + } + + void update(float tpf, TempVars vars) { + if (animation == null) + return; + + if (blendFrom != null && blendAmount != 1.0f){ + // The blendFrom anim is set, the actual animation + // playing will be set +// blendFrom.setTime(timeBlendFrom, 1f, control, this, vars); + blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars); + + timeBlendFrom += tpf * speedBlendFrom; + timeBlendFrom = clampWrapTime(timeBlendFrom, + blendFrom.getLength(), + loopModeBlendFrom); + if (timeBlendFrom < 0){ + timeBlendFrom = -timeBlendFrom; + speedBlendFrom = -speedBlendFrom; + } + + blendAmount += tpf * blendRate; + if (blendAmount > 1f){ + blendAmount = 1f; + blendFrom = null; + } + } + + animation.setTime(time, blendAmount, control, this, vars); + time += tpf * speed; + + if (animation.getLength() > 0){ + if (!notified && (time >= animation.getLength() || time < 0)) { + if (loopMode == LoopMode.DontLoop) { + // Note that this flag has to be set before calling the notify + // since the notify may start a new animation and then unset + // the flag. + notified = true; + } + control.notifyAnimCycleDone(this, animation.getName()); + } + } + + time = clampWrapTime(time, animation.getLength(), loopMode); + if (time < 0){ + // Negative time indicates that speed should be inverted + // (for cycle loop mode only) + time = -time; + speed = -speed; + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimControl.java b/jme3-core/src/main/java/com/jme3/animation/AnimControl.java new file mode 100644 index 000000000..6c25da7ec --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/AnimControl.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.*; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.control.Control; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map.Entry; + +/** + * 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 Cloneable { + + /** + * Skeleton object must contain corresponding data for the targets' weight buffers. + */ + Skeleton skeleton; + /** only used for backward compatibility */ + @Deprecated + private SkeletonControl skeletonControl; + /** + * List of animations + */ + HashMap animationMap = new HashMap(); + /** + * Animation channels + */ + private transient ArrayList channels = new ArrayList(); + /** + * Animation event listeners + */ + private transient ArrayList listeners = new ArrayList(); + + /** + * Creates a new animation control for the given skeleton. + * The method {@link AnimControl#setAnimations(java.util.HashMap) } + * must be called after initialization in order for this class to be useful. + * + * @param skeleton The skeleton to animate + */ + public AnimControl(Skeleton skeleton) { + this.skeleton = skeleton; + reset(); + } + + /** + * Serialization only. Do not use. + */ + public AnimControl() { + } + + /** + * Internal use only. + */ + public Control cloneForSpatial(Spatial spatial) { + try { + AnimControl clone = (AnimControl) super.clone(); + clone.spatial = spatial; + clone.channels = new ArrayList(); + clone.listeners = new ArrayList(); + + if (skeleton != null) { + clone.skeleton = new Skeleton(skeleton); + } + + // animationMap is cloned, but only ClonableTracks will be cloned as they need a reference to a cloned spatial + for (Entry animEntry : animationMap.entrySet()) { + clone.animationMap.put(animEntry.getKey(), animEntry.getValue().cloneForSpatial(spatial)); + } + + 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 Animation 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(Animation 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(Animation anim) { + if (!animationMap.containsKey(anim.getName())) { + throw new IllegalArgumentException("Given animation does not exist " + + "in this AnimControl"); + } + + animationMap.remove(anim.getName()); + } + + /** + * Create a new animation channel, by default assigned to all bones + * in the skeleton. + * + * @return A new animation channel for this 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() { + for (AnimChannel animChannel : channels) { + for (AnimEventListener list : listeners) { + list.onAnimCycleDone(this, animChannel, animChannel.getAnimationName()); + } + } + channels.clear(); + } + + /** + * @return The skeleton of this AnimControl. + */ + public Skeleton getSkeleton() { + return skeleton; + } + + /** + * Adds a new listener to receive animation related events. + * @param listener The listener to add. + */ + public void addListener(AnimEventListener listener) { + if (listeners.contains(listener)) { + throw new IllegalArgumentException("The given listener is already " + + "registed at this AnimControl"); + } + + listeners.add(listener); + } + + /** + * Removes the given listener from listening to events. + * @param listener + * @see AnimControl#addListener(com.jme3.animation.AnimEventListener) + */ + public void removeListener(AnimEventListener listener) { + if (!listeners.remove(listener)) { + throw new IllegalArgumentException("The given listener is not " + + "registed at this AnimControl"); + } + } + + /** + * Clears all the listeners added to this 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); + } + } + + /** + * Internal use only. + */ + @Override + public void setSpatial(Spatial spatial) { + if (spatial == null && skeletonControl != null) { + this.spatial.removeControl(skeletonControl); + } + + super.setSpatial(spatial); + + //Backward compatibility. + if (spatial != null && skeletonControl != null) { + spatial.addControl(skeletonControl); + } + } + + final void reset() { + if (skeleton != null) { + skeleton.resetAndUpdate(); + } + } + + /** + * @return The names of all animations that this 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) { + Animation a = animationMap.get(name); + if (a == null) { + throw new IllegalArgumentException("The animation " + name + + " does not exist in this AnimControl"); + } + + return a.getLength(); + } + + /** + * Internal use only. + */ + @Override + protected void controlUpdate(float tpf) { + if (skeleton != null) { + skeleton.reset(); // reset skeleton to bind pose + } + + TempVars vars = TempVars.get(); + for (int i = 0; i < channels.size(); i++) { + channels.get(i).update(tpf, vars); + } + vars.release(); + + if (skeleton != null) { + skeleton.updateWorldVectors(); + } + } + + /** + * Internal use only. + */ + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(skeleton, "skeleton", null); + oc.writeStringSavableMap(animationMap, "animations", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + skeleton = (Skeleton) in.readSavable("skeleton", null); + HashMap loadedAnimationMap = (HashMap) in.readStringSavableMap("animations", null); + if (loadedAnimationMap != null) { + animationMap = loadedAnimationMap; + } + + if (im.getFormatVersion() == 0) { + // Changed for backward compatibility with j3o files generated + // before the AnimControl/SkeletonControl split. + + // If we find a target mesh array the AnimControl creates the + // SkeletonControl for old files and add it to the spatial. + // When backward compatibility won't be needed anymore this can deleted + Savable[] sav = in.readSavableArray("targets", null); + if (sav != null) { + Mesh[] targets = new Mesh[sav.length]; + System.arraycopy(sav, 0, targets, 0, sav.length); + skeletonControl = new SkeletonControl(targets, skeleton); + spatial.addControl(skeletonControl); + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java b/jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java new file mode 100644 index 000000000..d9b48459a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +/** + * 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/jme3-core/src/main/java/com/jme3/animation/Animation.java b/jme3-core/src/main/java/com/jme3/animation/Animation.java new file mode 100644 index 000000000..5ae34628e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/Animation.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.*; +import com.jme3.scene.Spatial; +import com.jme3.util.SafeArrayList; +import com.jme3.util.TempVars; +import java.io.IOException; + +/** + * The animation class updates the animation target with the tracks of a given type. + * + * @author Kirill Vainer, Marcin Roguski (Kaelthas) + */ +public class Animation implements Savable, Cloneable { + + /** + * The name of the animation. + */ + private String name; + /** + * The length of the animation. + */ + private float length; + /** + * The tracks of the animation. + */ + private SafeArrayList tracks = new SafeArrayList(Track.class); + + /** + * Serialization-only. Do not use. + */ + public Animation() { + } + + /** + * Creates a new Animation with the given name and length. + * + * @param name The name of the animation. + * @param length Length in seconds of the animation. + */ + public Animation(String name, float length) { + this.name = name; + this.length = length; + } + + /** + * The name of the bone animation + * @return name of the bone animation + */ + public String getName() { + return name; + } + + /** + * Returns the length in seconds of this animation + * + * @return the length in seconds of this animation + */ + public float getLength() { + return length; + } + + /** + * This method sets the current time of the animation. + * This method behaves differently for every known track type. + * Override this method if you have your own type of track. + * + * @param time the time of the animation + * @param blendAmount the blend amount factor + * @param control the animation control + * @param channel the animation channel + */ + void setTime(float time, float blendAmount, AnimControl control, AnimChannel channel, TempVars vars) { + if (tracks == null) { + return; + } + + for (Track track : tracks) { + track.setTime(time, blendAmount, control, channel, vars); + } + } + + /** + * Set the {@link Track}s to be used by this animation. + * + * @param tracksArray The tracks to set. + */ + public void setTracks(Track[] tracksArray) { + for (Track track : tracksArray) { + tracks.add(track); + } + } + + /** + * Adds a track to this animation + * @param track the track to add + */ + public void addTrack(Track track) { + tracks.add(track); + } + + /** + * removes a track from this animation + * @param track the track to remove + */ + public void removeTrack(Track track) { + tracks.remove(track); + if (track instanceof ClonableTrack) { + ((ClonableTrack) track).cleanUp(); + } + } + + /** + * Returns the tracks set in {@link #setTracks(com.jme3.animation.Track[]) }. + * + * @return the tracks set previously + */ + public Track[] getTracks() { + return tracks.getArray(); + } + + /** + * This method creates a clone of the current object. + * @return a clone of the current object + */ + @Override + public Animation clone() { + try { + Animation result = (Animation) super.clone(); + result.tracks = new SafeArrayList(Track.class); + for (Track track : tracks) { + result.tracks.add(track.clone()); + } + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + /** + * + * @param spat + * @return + */ + public Animation cloneForSpatial(Spatial spat) { + try { + Animation result = (Animation) super.clone(); + result.tracks = new SafeArrayList(Track.class); + for (Track track : tracks) { + if (track instanceof ClonableTrack) { + result.tracks.add(((ClonableTrack) track).cloneForSpatial(spat)); + } else { + result.tracks.add(track); + } + } + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']'; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + out.write(name, "name", null); + out.write(length, "length", 0f); + out.write(tracks.getArray(), "tracks", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + name = in.readString("name", null); + length = in.readFloat("length", 0f); + + Savable[] arr = in.readSavableArray("tracks", null); + if (arr != null) { + // NOTE: Backward compat only .. Some animations have no + // tracks set at all even though it makes no sense. + // Since there's a null check in setTime(), + // its only appropriate that the check is made here as well. + tracks = new SafeArrayList(Track.class); + for (Savable savable : arr) { + tracks.add((Track) savable); + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimationFactory.java b/jme3-core/src/main/java/com/jme3/animation/AnimationFactory.java new file mode 100644 index 000000000..38e22ea30 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/AnimationFactory.java @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; + +/** + * A convenience class to easily setup a spatial keyframed animation + * you can add some keyFrames for a given time or a given keyFrameIndex, for translation rotation and scale. + * The animationHelper will then generate an appropriate SpatialAnimation by interpolating values between the keyFrames. + *

    + * Usage is :
    + * - Create the AnimationHelper
    + * - add some keyFrames
    + * - call the buildAnimation() method that will retruna new Animation
    + * - add the generated Animation to any existing AnimationControl
    + *

    + * Note that the first keyFrame (index 0) is defaulted with the identy transforms. + * If you want to change that you have to replace this keyFrame with any transform you want. + * + * @author Nehon + */ +public class AnimationFactory { + + /** + * step for splitting rotation that have a n ange above PI/2 + */ + private final static float EULER_STEP = FastMath.QUARTER_PI * 3; + + /** + * enum to determine the type of interpolation + */ + private enum Type { + + Translation, Rotation, Scale; + } + + /** + * Inner Rotation type class to kep track on a rotation Euler angle + */ + protected class Rotation { + + /** + * The rotation Quaternion + */ + Quaternion rotation = new Quaternion(); + /** + * This rotation expressed in Euler angles + */ + Vector3f eulerAngles = new Vector3f(); + /** + * the index of the parent key frame is this keyFrame is a splitted rotation + */ + int masterKeyFrame = -1; + + public Rotation() { + rotation.loadIdentity(); + } + + void set(Quaternion rot) { + rotation.set(rot); + float[] a = new float[3]; + rotation.toAngles(a); + eulerAngles.set(a[0], a[1], a[2]); + } + + void set(float x, float y, float z) { + float[] a = {x, y, z}; + rotation.fromAngles(a); + eulerAngles.set(x, y, z); + } + } + /** + * Name of the animation + */ + protected String name; + /** + * frames per seconds + */ + protected int fps; + /** + * Animation duration in seconds + */ + protected float duration; + /** + * total number of frames + */ + protected int totalFrames; + /** + * time per frame + */ + protected float tpf; + /** + * Time array for this animation + */ + protected float[] times; + /** + * Translation array for this animation + */ + protected Vector3f[] translations; + /** + * rotation array for this animation + */ + protected Quaternion[] rotations; + /** + * scales array for this animation + */ + protected Vector3f[] scales; + /** + * The map of keyFrames to compute the animation. The key is the index of the frame + */ + protected Vector3f[] keyFramesTranslation; + protected Vector3f[] keyFramesScale; + protected Rotation[] keyFramesRotation; + + /** + * Creates and AnimationHelper + * @param duration the desired duration for the resulting animation + * @param name the name of the resulting animation + */ + public AnimationFactory(float duration, String name) { + this(duration, name, 30); + } + + /** + * Creates and AnimationHelper + * @param duration the desired duration for the resulting animation + * @param name the name of the resulting animation + * @param fps the number of frames per second for this animation (default is 30) + */ + public AnimationFactory(float duration, String name, int fps) { + this.name = name; + this.duration = duration; + this.fps = fps; + totalFrames = (int) (fps * duration) + 1; + tpf = 1 / (float) fps; + times = new float[totalFrames]; + translations = new Vector3f[totalFrames]; + rotations = new Quaternion[totalFrames]; + scales = new Vector3f[totalFrames]; + keyFramesTranslation = new Vector3f[totalFrames]; + keyFramesTranslation[0] = new Vector3f(); + keyFramesScale = new Vector3f[totalFrames]; + keyFramesScale[0] = new Vector3f(1, 1, 1); + keyFramesRotation = new Rotation[totalFrames]; + keyFramesRotation[0] = new Rotation(); + + } + + /** + * Adds a key frame for the given Transform at the given time + * @param time the time at which the keyFrame must be inserted + * @param transform the transforms to use for this keyFrame + */ + public void addTimeTransform(float time, Transform transform) { + addKeyFrameTransform((int) (time / tpf), transform); + } + + /** + * Adds a key frame for the given Transform at the given keyFrame index + * @param keyFrameIndex the index at which the keyFrame must be inserted + * @param transform the transforms to use for this keyFrame + */ + public void addKeyFrameTransform(int keyFrameIndex, Transform transform) { + addKeyFrameTranslation(keyFrameIndex, transform.getTranslation()); + addKeyFrameScale(keyFrameIndex, transform.getScale()); + addKeyFrameRotation(keyFrameIndex, transform.getRotation()); + } + + /** + * Adds a key frame for the given translation at the given time + * @param time the time at which the keyFrame must be inserted + * @param translation the translation to use for this keyFrame + */ + public void addTimeTranslation(float time, Vector3f translation) { + addKeyFrameTranslation((int) (time / tpf), translation); + } + + /** + * Adds a key frame for the given translation at the given keyFrame index + * @param keyFrameIndex the index at which the keyFrame must be inserted + * @param translation the translation to use for this keyFrame + */ + public void addKeyFrameTranslation(int keyFrameIndex, Vector3f translation) { + Vector3f t = getTranslationForFrame(keyFrameIndex); + t.set(translation); + } + + /** + * Adds a key frame for the given rotation at the given time
    + * This can't be used if the interpolated angle is higher than PI (180°)
    + * Use {@link #addTimeRotationAngles(float time, float x, float y, float z)} instead that uses Euler angles rotations.
    * + * @param time the time at which the keyFrame must be inserted + * @param rotation the rotation Quaternion to use for this keyFrame + * @see #addTimeRotationAngles(float time, float x, float y, float z) + */ + public void addTimeRotation(float time, Quaternion rotation) { + addKeyFrameRotation((int) (time / tpf), rotation); + } + + /** + * Adds a key frame for the given rotation at the given keyFrame index
    + * This can't be used if the interpolated angle is higher than PI (180°)
    + * Use {@link #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)} instead that uses Euler angles rotations. + * @param keyFrameIndex the index at which the keyFrame must be inserted + * @param rotation the rotation Quaternion to use for this keyFrame + * @see #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) + */ + public void addKeyFrameRotation(int keyFrameIndex, Quaternion rotation) { + Rotation r = getRotationForFrame(keyFrameIndex); + r.set(rotation); + } + + /** + * Adds a key frame for the given rotation at the given time.
    + * Rotation is expressed by Euler angles values in radians.
    + * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)
    + * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation
    + * + * @param time the time at which the keyFrame must be inserted + * @param x the rotation around the x axis (aka yaw) in radians + * @param y the rotation around the y axis (aka roll) in radians + * @param z the rotation around the z axis (aka pitch) in radians + */ + public void addTimeRotationAngles(float time, float x, float y, float z) { + addKeyFrameRotationAngles((int) (time / tpf), x, y, z); + } + + /** + * Adds a key frame for the given rotation at the given key frame index.
    + * Rotation is expressed by Euler angles values in radians.
    + * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)
    + * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation
    + * + * @param keyFrameIndex the index at which the keyFrame must be inserted + * @param x the rotation around the x axis (aka yaw) in radians + * @param y the rotation around the y axis (aka roll) in radians + * @param z the rotation around the z axis (aka pitch) in radians + */ + public void addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) { + Rotation r = getRotationForFrame(keyFrameIndex); + r.set(x, y, z); + + // if the delta of euler angles is higher than PI, we create intermediate keyframes + // since we are using quaternions and slerp for rotation interpolation, we cannot interpolate over an angle higher than PI + int prev = getPreviousKeyFrame(keyFrameIndex, keyFramesRotation); + if (prev != -1) { + //previous rotation keyframe + Rotation prevRot = keyFramesRotation[prev]; + //the maximum delta angle (x,y or z) + float delta = Math.max(Math.abs(x - prevRot.eulerAngles.x), Math.abs(y - prevRot.eulerAngles.y)); + delta = Math.max(delta, Math.abs(z - prevRot.eulerAngles.z)); + //if delta > PI we have to create intermediates key frames + if (delta >= FastMath.PI) { + //frames delta + int dF = keyFrameIndex - prev; + //angle per frame for x,y ,z + float dXAngle = (x - prevRot.eulerAngles.x) / (float) dF; + float dYAngle = (y - prevRot.eulerAngles.y) / (float) dF; + float dZAngle = (z - prevRot.eulerAngles.z) / (float) dF; + + // the keyFrame step + int keyStep = (int) (((float) (dF)) / delta * (float) EULER_STEP); + // the current keyFrame + int cursor = prev + keyStep; + while (cursor < keyFrameIndex) { + //for each step we create a new rotation by interpolating the angles + Rotation dr = getRotationForFrame(cursor); + dr.masterKeyFrame = keyFrameIndex; + dr.set(prevRot.eulerAngles.x + cursor * dXAngle, prevRot.eulerAngles.y + cursor * dYAngle, prevRot.eulerAngles.z + cursor * dZAngle); + cursor += keyStep; + } + + } + } + + } + + /** + * Adds a key frame for the given scale at the given time + * @param time the time at which the keyFrame must be inserted + * @param scale the scale to use for this keyFrame + */ + public void addTimeScale(float time, Vector3f scale) { + addKeyFrameScale((int) (time / tpf), scale); + } + + /** + * Adds a key frame for the given scale at the given keyFrame index + * @param keyFrameIndex the index at which the keyFrame must be inserted + * @param scale the scale to use for this keyFrame + */ + public void addKeyFrameScale(int keyFrameIndex, Vector3f scale) { + Vector3f s = getScaleForFrame(keyFrameIndex); + s.set(scale); + } + + /** + * returns the translation for a given frame index + * creates the translation if it doesn't exists + * @param keyFrameIndex index + * @return the translation + */ + private Vector3f getTranslationForFrame(int keyFrameIndex) { + if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) { + throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")"); + } + Vector3f v = keyFramesTranslation[keyFrameIndex]; + if (v == null) { + v = new Vector3f(); + keyFramesTranslation[keyFrameIndex] = v; + } + return v; + } + + /** + * returns the scale for a given frame index + * creates the scale if it doesn't exists + * @param keyFrameIndex index + * @return the scale + */ + private Vector3f getScaleForFrame(int keyFrameIndex) { + if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) { + throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")"); + } + Vector3f v = keyFramesScale[keyFrameIndex]; + if (v == null) { + v = new Vector3f(); + keyFramesScale[keyFrameIndex] = v; + } + return v; + } + + /** + * returns the rotation for a given frame index + * creates the rotation if it doesn't exists + * @param keyFrameIndex index + * @return the rotation + */ + private Rotation getRotationForFrame(int keyFrameIndex) { + if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) { + throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")"); + } + Rotation v = keyFramesRotation[keyFrameIndex]; + if (v == null) { + v = new Rotation(); + keyFramesRotation[keyFrameIndex] = v; + } + return v; + } + + /** + * Creates an Animation based on the keyFrames previously added to the helper. + * @return the generated animation + */ + public Animation buildAnimation() { + interpolateTime(); + interpolate(keyFramesTranslation, Type.Translation); + interpolate(keyFramesRotation, Type.Rotation); + interpolate(keyFramesScale, Type.Scale); + + SpatialTrack spatialTrack = new SpatialTrack(times, translations, rotations, scales); + + //creating the animation + Animation spatialAnimation = new Animation(name, duration); + spatialAnimation.setTracks(new SpatialTrack[]{spatialTrack}); + + return spatialAnimation; + } + + /** + * interpolates time values + */ + private void interpolateTime() { + for (int i = 0; i < totalFrames; i++) { + times[i] = i * tpf; + } + } + + /** + * Interpolates over the key frames for the given keyFrame array and the given type of transform + * @param keyFrames the keyFrames array + * @param type the type of transforms + */ + private void interpolate(Object[] keyFrames, Type type) { + int i = 0; + while (i < totalFrames) { + //fetching the next keyFrame index transform in the array + int key = getNextKeyFrame(i, keyFrames); + if (key != -1) { + //computing the frame span to interpolate over + int span = key - i; + //interating over the frames + for (int j = i; j <= key; j++) { + // computing interpolation value + float val = (float) (j - i) / (float) span; + //interpolationg depending on the transform type + switch (type) { + case Translation: + translations[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]); + break; + case Rotation: + Quaternion rot = new Quaternion(); + rotations[j] = rot.slerp(((Rotation) keyFrames[i]).rotation, ((Rotation) keyFrames[key]).rotation, val); + break; + case Scale: + scales[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]); + break; + } + } + //jumping to the next keyFrame + i = key; + } else { + //No more key frame, filling the array witht he last transform computed. + for (int j = i; j < totalFrames; j++) { + + switch (type) { + case Translation: + translations[j] = ((Vector3f) keyFrames[i]).clone(); + break; + case Rotation: + rotations[j] = ((Quaternion) ((Rotation) keyFrames[i]).rotation).clone(); + break; + case Scale: + scales[j] = ((Vector3f) keyFrames[i]).clone(); + break; + } + } + //we're done + i = totalFrames; + } + } + } + + /** + * Get the index of the next keyFrame that as a transform + * @param index the start index + * @param keyFrames the keyFrames array + * @return the index of the next keyFrame + */ + private int getNextKeyFrame(int index, Object[] keyFrames) { + for (int i = index + 1; i < totalFrames; i++) { + if (keyFrames[i] != null) { + return i; + } + } + return -1; + } + + /** + * Get the index of the previous keyFrame that as a transform + * @param index the start index + * @param keyFrames the keyFrames array + * @return the index of the previous keyFrame + */ + private int getPreviousKeyFrame(int index, Object[] keyFrames) { + for (int i = index - 1; i >= 0; i--) { + if (keyFrames[i] != null) { + return i; + } + } + return -1; + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java b/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java new file mode 100644 index 000000000..1440af160 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.audio.AudioNode; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AudioTrack is a track to add to an existing animation, to paly a sound during + * an animations for example : gun shot, foot step, shout, etc... + * + * usage is + *

    + * AnimControl control model.getControl(AnimControl.class);
    + * AudioTrack track = new AudioTrack(existionAudioNode, control.getAnim("TheAnim").getLength());
    + * control.getAnim("TheAnim").addTrack(track);
    + * 
    + * + * This is mostly intended for short sounds, playInstance will be called on the + * AudioNode at time 0 + startOffset. + * + * + * @author Nehon + */ +public class AudioTrack implements ClonableTrack { + + private static final Logger logger = Logger.getLogger(AudioTrack.class.getName()); + private AudioNode audio; + private float startOffset = 0; + private float length = 0; + private boolean initialized = false; + private boolean started = false; + private boolean played = false; + + //Animation listener to stop the sound when the animation ends or is changed + private class OnEndListener implements AnimEventListener { + + public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { + stop(); + } + + public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { + } + } + + /** + * default constructor for serialization only + */ + public AudioTrack() { + } + + /** + * Creates an AudioTrack + * + * @param audio the AudioNode + * @param length the length of the track (usually the length of the + * animation you want to add the track to) + */ + public AudioTrack(AudioNode audio, float length) { + this.audio = audio; + this.length = length; + setUserData(this); + } + + /** + * Creates an AudioTrack + * + * @param audio the AudioNode + * @param length the length of the track (usually the length of the + * animation you want to add the track to) + * @param startOffset the time in second when the sound will be played after + * the animation starts (default is 0) + */ + public AudioTrack(AudioNode audio, float length, float startOffset) { + this(audio, length); + this.startOffset = startOffset; + } + + /** + * Internal use only + * + * @see Track#setTime(float, float, com.jme3.animation.AnimControl, + * com.jme3.animation.AnimChannel, com.jme3.util.TempVars) + */ + public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { + + if (time >= length) { + return; + } + if (!initialized) { + control.addListener(new OnEndListener()); + initialized = true; + } + if (!started && time >= startOffset) { + started = true; + audio.playInstance(); + } + } + + //stops the sound + private void stop() { + audio.stop(); + started = false; + } + + /** + * Retruns the length of the track + * + * @return length of the track + */ + public float getLength() { + return length; + } + + /** + * Clone this track + * + * @return + */ + @Override + public Track clone() { + return new AudioTrack(audio, length, startOffset); + } + + /** + * This method clone the Track and search for the cloned counterpart of the + * original audio node in the given cloned spatial. The spatial is assumed + * to be the Spatial holding the AnimControl controling the animation using + * this Track. + * + * @param spatial the Spatial holding the AnimControl + * @return the cloned Track with proper reference + */ + public Track cloneForSpatial(Spatial spatial) { + AudioTrack audioTrack = new AudioTrack(); + audioTrack.length = this.length; + audioTrack.startOffset = this.startOffset; + + //searching for the newly cloned AudioNode + audioTrack.audio = findAudio(spatial); + if (audioTrack.audio == null) { + logger.log(Level.WARNING, "{0} was not found in {1} or is not bound to this track", new Object[]{audio.getName(), spatial.getName()}); + audioTrack.audio = audio; + } + + //setting user data on the new AudioNode and marking it with a reference to the cloned Track. + setUserData(audioTrack); + + return audioTrack; + } + + /** + * recursive function responsible for finding the newly cloned AudioNode + * + * @param spat + * @return + */ + private AudioNode findAudio(Spatial spat) { + if (spat instanceof AudioNode) { + //spat is an AudioNode + AudioNode em = (AudioNode) spat; + //getting the UserData TrackInfo so check if it should be attached to this Track + TrackInfo t = (TrackInfo) em.getUserData("TrackInfo"); + if (t != null && t.getTracks().contains(this)) { + return em; + } + return null; + + } else if (spat instanceof Node) { + for (Spatial child : ((Node) spat).getChildren()) { + AudioNode em = findAudio(child); + if (em != null) { + return em; + } + } + } + return null; + } + + private void setUserData(AudioTrack audioTrack) { + //fetching the UserData TrackInfo. + TrackInfo data = (TrackInfo) audioTrack.audio.getUserData("TrackInfo"); + + //if it does not exist, we create it and attach it to the AudioNode. + if (data == null) { + data = new TrackInfo(); + audioTrack.audio.setUserData("TrackInfo", data); + } + + //adding the given Track to the TrackInfo. + data.addTrack(audioTrack); + } + + public void cleanUp() { + TrackInfo t = (TrackInfo) audio.getUserData("TrackInfo"); + t.getTracks().remove(this); + if (!t.getTracks().isEmpty()) { + audio.setUserData("TrackInfo", null); + } + } + + /** + * + * @return the audio node used by this track + */ + public AudioNode getAudio() { + return audio; + } + + /** + * sets the audio node to be used for this track + * + * @param audio + */ + public void setAudio(AudioNode audio) { + if (this.audio != null) { + TrackInfo data = (TrackInfo) audio.getUserData("TrackInfo"); + data.getTracks().remove(this); + } + this.audio = audio; + setUserData(this); + } + + /** + * + * @return the start offset of the track + */ + public float getStartOffset() { + return startOffset; + } + + /** + * set the start offset of the track + * + * @param startOffset + */ + public void setStartOffset(float startOffset) { + this.startOffset = startOffset; + } + + /** + * Internal use only serialization + * + * @param ex exporter + * @throws IOException exception + */ + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + out.write(audio, "audio", null); + out.write(length, "length", 0); + out.write(startOffset, "startOffset", 0); + } + + /** + * Internal use only serialization + * + * @param im importer + * @throws IOException Exception + */ + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + audio = (AudioNode) in.readSavable("audio", null); + length = in.readFloat("length", length); + startOffset = in.readFloat("startOffset", 0); + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/Bone.java b/jme3-core/src/main/java/com/jme3/animation/Bone.java new file mode 100644 index 000000000..f6958c04b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/Bone.java @@ -0,0 +1,691 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.*; +import com.jme3.math.*; +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; + /** + * The inverse world bind transform. + * BONE SPACE -> MODEL SPACE + */ + private Vector3f worldBindInversePos; + private Quaternion worldBindInverseRot; + private Vector3f worldBindInverseScale; + /** + * The local animated transform combined with the local bind transform and parent world transform + */ + private Vector3f localPos = new Vector3f(); + private Quaternion localRot = new Quaternion(); + private Vector3f localScale = new Vector3f(1.0f, 1.0f, 1.0f); + /** + * MODEL SPACE -> BONE SPACE (in animated state) + */ + private Vector3f worldPos = new Vector3f(); + private Quaternion worldRot = new Quaternion(); + private Vector3f worldScale = new Vector3f(); + + // Used for getCombinedTransform + private Transform tmpTransform = new Transform(); + + /** + * Used to handle blending from one animation to another. + * See {@link #blendAnimTransforms(com.jme3.math.Vector3f, com.jme3.math.Quaternion, com.jme3.math.Vector3f, float)} + * on how this variable is used. + */ + private transient float currentWeightSum = -1; + + /** + * Creates a new bone with the given name. + * + * @param name Name to give to this bone + */ + public Bone(String name) { + if (name == null) + throw new IllegalArgumentException("Name cannot be null"); + + this.name = name; + + initialPos = new Vector3f(); + initialRot = new Quaternion(); + initialScale = new Vector3f(1, 1, 1); + + worldBindInversePos = new Vector3f(); + worldBindInverseRot = new Quaternion(); + worldBindInverseScale = new Vector3f(); + } + + /** + * Special-purpose copy constructor. + *

    + * Only copies the name and bind pose from the original. + *

    + * WARNING: Local bind pose and world inverse bind pose transforms shallow + * copied. Modifying that data on the original bone will cause it to + * be recomputed on any cloned bones. + *

    + * The rest of the data is NOT copied, as it will be + * generated automatically when the bone is animated. + * + * @param source The bone from which to copy the data. + */ + Bone(Bone source) { + this.name = source.name; + + userControl = source.userControl; + + initialPos = source.initialPos; + initialRot = source.initialRot; + initialScale = source.initialScale; + + worldBindInversePos = source.worldBindInversePos; + worldBindInverseRot = source.worldBindInverseRot; + worldBindInverseScale = source.worldBindInverseScale; + + // parent and children will be assigned manually.. + } + + /** + * Serialization only. Do not use. + */ + public Bone() { + } + + /** + * Returns the name of the bone, set in the constructor. + * + * @return The name of the bone, set in the constructor. + */ + public String getName() { + return name; + } + + /** + * Returns parent bone of this bone, or null if it is a root bone. + * @return The parent bone of this bone, or null if it is a root bone. + */ + public Bone getParent() { + return parent; + } + + /** + * Returns all the children bones of this bone. + * + * @return All the children bones of this bone. + */ + public ArrayList getChildren() { + return children; + } + + /** + * Returns the local position of the bone, relative to the parent bone. + * + * @return The local position of the bone, relative to the parent bone. + */ + public Vector3f getLocalPosition() { + return localPos; + } + + /** + * Returns the local rotation of the bone, relative to the parent bone. + * + * @return The local rotation of the bone, relative to the parent bone. + */ + public Quaternion getLocalRotation() { + return localRot; + } + + /** + * Returns the local scale of the bone, relative to the parent bone. + * + * @return The local scale of the bone, relative to the parent bone. + */ + public Vector3f getLocalScale() { + return localScale; + } + + /** + * Returns the position of the bone in model space. + * + * @return The position of the bone in model space. + */ + public Vector3f getModelSpacePosition() { + return worldPos; + } + + /** + * Returns the rotation of the bone in model space. + * + * @return The rotation of the bone in model space. + */ + public Quaternion getModelSpaceRotation() { + return worldRot; + } + + /** + * Returns the scale of the bone in model space. + * + * @return The scale of the bone in model space. + */ + public Vector3f getModelSpaceScale() { + return worldScale; + } + + /** + * Returns the inverse world bind pose position. + *

    + * The bind pose transform of the bone is its "default" + * transform with no animation applied. + * + * @return the inverse world bind pose position. + */ + public Vector3f getWorldBindInversePosition() { + return worldBindInversePos; + } + + /** + * Returns the inverse world bind pose rotation. + *

    + * The bind pose transform of the bone is its "default" + * transform with no animation applied. + * + * @return the inverse world bind pose rotation. + */ + public Quaternion getWorldBindInverseRotation() { + return worldBindInverseRot; + } + + /** + * Returns the inverse world bind pose scale. + *

    + * The bind pose transform of the bone is its "default" + * transform with no animation applied. + * + * @return the inverse world bind pose scale. + */ + public Vector3f getWorldBindInverseScale() { + return worldBindInverseScale; + } + + /** + * Returns the world bind pose position. + *

    + * The bind pose transform of the bone is its "default" + * transform with no animation applied. + * + * @return the world bind pose position. + */ + public Vector3f getWorldBindPosition() { + return initialPos; + } + + /** + * Returns the world bind pose rotation. + *

    + * The bind pose transform of the bone is its "default" + * transform with no animation applied. + * + * @return the world bind pose rotation. + */ + public Quaternion getWorldBindRotation() { + return initialRot; + } + + /** + * Returns the world bind pose scale. + *

    + * The bind pose transform of the bone is its "default" + * transform with no animation applied. + * + * @return the world bind pose scale. + */ + public Vector3f getWorldBindScale() { + return initialScale; + } + + /** + * If enabled, user can control bone transform with setUserTransforms. + * Animation transforms are not applied to this bone when enabled. + */ + public void setUserControl(boolean enable) { + userControl = enable; + } + + /** + * Add a new child to this bone. Shouldn't be used by user code. + * Can corrupt skeleton. + * + * @param bone The bone to add + */ + public void addChild(Bone bone) { + children.add(bone); + bone.parent = this; + } + + /** + * Updates the world transforms for this bone, and, possibly the attach node + * if not null. + *

    + * The world transform of this bone is computed by combining the parent's + * world transform with this bones' local transform. + */ + public final void updateWorldVectors() { + if (currentWeightSum == 1f) { + currentWeightSum = -1; + } else if (currentWeightSum != -1f) { + // Apply the weight to the local transform + if (currentWeightSum == 0) { + localRot.set(initialRot); + localPos.set(initialPos); + localScale.set(initialScale); + } else { + float invWeightSum = 1f - currentWeightSum; + localRot.nlerp(initialRot, invWeightSum); + localPos.interpolateLocal(initialPos, invWeightSum); + localScale.interpolateLocal(initialScale, invWeightSum); + } + + // Future invocations of transform blend will start over. + currentWeightSum = -1; + } + + if (parent != null) { + //rotation + parent.worldRot.mult(localRot, worldRot); + + //scale + //For scale parent scale is not taken into account! + // worldScale.set(localScale); + parent.worldScale.mult(localScale, worldScale); + + //translation + //scale and rotation of parent affect bone position + parent.worldRot.mult(localPos, worldPos); + worldPos.multLocal(parent.worldScale); + worldPos.addLocal(parent.worldPos); + } else { + worldRot.set(localRot); + worldPos.set(localPos); + worldScale.set(localScale); + } + + if (attachNode != null) { + attachNode.setLocalTranslation(worldPos); + attachNode.setLocalRotation(worldRot); + attachNode.setLocalScale(worldScale); + } + } + + /** + * Updates world transforms for this bone and it's children. + */ + final void update() { + this.updateWorldVectors(); + + for (int i = children.size() - 1; i >= 0; i--) { + children.get(i).update(); + } + } + + /** + * Saves the current bone state as its binding pose, including its children. + */ + void setBindingPose() { + initialPos.set(localPos); + initialRot.set(localRot); + initialScale.set(localScale); + + if (worldBindInversePos == null) { + worldBindInversePos = new Vector3f(); + worldBindInverseRot = new Quaternion(); + worldBindInverseScale = new Vector3f(); + } + + // Save inverse derived position/scale/orientation, used for calculate offset transform later + worldBindInversePos.set(worldPos); + worldBindInversePos.negateLocal(); + + worldBindInverseRot.set(worldRot); + worldBindInverseRot.inverseLocal(); + + worldBindInverseScale.set(Vector3f.UNIT_XYZ); + worldBindInverseScale.divideLocal(worldScale); + + for (Bone b : children) { + b.setBindingPose(); + } + } + + /** + * Reset the bone and it's children to bind pose. + */ + final void reset() { + if (!userControl) { + localPos.set(initialPos); + localRot.set(initialRot); + localScale.set(initialScale); + } + + for (int i = children.size() - 1; i >= 0; i--) { + children.get(i).reset(); + } + } + + /** + * Stores the skinning transform in the specified Matrix4f. + * The skinning transform applies the animation of the bone to a vertex. + * + * This assumes that the world transforms for the entire bone hierarchy + * have already been computed, otherwise this method will return undefined + * results. + * + * @param outTransform + */ + void getOffsetTransform(Matrix4f outTransform, Quaternion tmp1, Vector3f tmp2, Vector3f tmp3, Matrix3f tmp4) { + // Computing scale + Vector3f scale = worldScale.mult(worldBindInverseScale, tmp3); + + // Computing rotation + Quaternion rotate = worldRot.mult(worldBindInverseRot, tmp1); + + // Computing translation + // Translation depend on rotation and scale + Vector3f translate = worldPos.add(rotate.mult(scale.mult(worldBindInversePos, tmp2), tmp2), tmp2); + + // Populating the matrix + outTransform.loadIdentity(); + outTransform.setTransform(translate, scale, rotate.toRotationMatrix(tmp4)); + } + + /** + * Sets user transform. + */ + public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) { + if (!userControl) { + throw new IllegalStateException("User control must be on bone to allow user transforms"); + } + + localPos.set(initialPos); + localRot.set(initialRot); + localScale.set(initialScale); + + localPos.addLocal(translation); + localRot = localRot.mult(rotation); + localScale.multLocal(scale); + } + + /** + * Must update all bones in skeleton for this to work. + * @param translation + * @param rotation + */ + public void setUserTransformsWorld(Vector3f translation, Quaternion rotation) { + if (!userControl) { + throw new IllegalStateException("User control must be on bone to allow user transforms"); + } + + // TODO: add scale here ??? + worldPos.set(translation); + worldRot.set(rotation); + + //if there is an attached Node we need to set it's local transforms too. + if(attachNode != null){ + attachNode.setLocalTranslation(translation); + attachNode.setLocalRotation(rotation); + } + } + + /** + * Returns the local transform of this bone combined with the given position and rotation + * @param position a position + * @param rotation a rotation + */ + public Transform getCombinedTransform(Vector3f position, Quaternion rotation) { + rotation.mult(localPos, tmpTransform.getTranslation()).addLocal(position); + tmpTransform.setRotation(rotation).getRotation().multLocal(localRot); + return tmpTransform; + } + + /** + * Returns the attachment node. + * Attach models and effects to this node to make + * them follow this bone's motions. + */ + Node getAttachmentsNode() { + if (attachNode == null) { + attachNode = new Node(name + "_attachnode"); + attachNode.setUserData("AttachedBone", this); + } + return attachNode; + } + + /** + * Used internally after model cloning. + * @param attachNode + */ + void setAttachmentsNode(Node attachNode) { + this.attachNode = attachNode; + } + + /** + * Sets the local animation transform of this bone. + * Bone is assumed to be in bind pose when this is called. + */ + void setAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) { + if (userControl) { + return; + } + +// localPos.addLocal(translation); +// localRot.multLocal(rotation); + //localRot = localRot.mult(rotation); + + localPos.set(initialPos).addLocal(translation); + localRot.set(initialRot).multLocal(rotation); + + if (scale != null) { + localScale.set(initialScale).multLocal(scale); + } + } + + /** + * Blends the given animation transform onto the bone's local transform. + *

    + * Subsequent calls of this method stack up, with the final transformation + * of the bone computed at {@link #updateWorldVectors() } which resets + * the stack. + *

    + * E.g. a single transform blend with weight = 0.5 followed by an + * updateWorldVectors() call will result in final transform = transform * 0.5. + * Two transform blends with weight = 0.5 each will result in the two + * transforms blended together (nlerp) with blend = 0.5. + * + * @param translation The translation to blend in + * @param rotation The rotation to blend in + * @param scale The scale to blend in + * @param weight The weight of the transform to apply. Set to 1.0 to prevent + * any other transform from being applied until updateWorldVectors(). + */ + void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale, float weight) { + if (userControl) { + return; + } + + if (weight == 0) { + // Do not apply this transform at all. + return; + } + + if (currentWeightSum == 1){ + return; // More than 2 transforms are being blended + } else if (currentWeightSum == -1 || currentWeightSum == 0) { + // Set the transform fully + localPos.set(initialPos).addLocal(translation); + localRot.set(initialRot).multLocal(rotation); + if (scale != null) { + localScale.set(initialScale).multLocal(scale); + } + // Set the weight. It will be applied in updateWorldVectors(). + currentWeightSum = weight; + } else { + // The weight is already set. + // Blend in the new transform. + TempVars vars = TempVars.get(); + + Vector3f tmpV = vars.vect1; + Vector3f tmpV2 = vars.vect2; + Quaternion tmpQ = vars.quat1; + + tmpV.set(initialPos).addLocal(translation); + localPos.interpolateLocal(tmpV, weight); + + tmpQ.set(initialRot).multLocal(rotation); + localRot.nlerp(tmpQ, weight); + + if (scale != null) { + tmpV2.set(initialScale).multLocal(scale); + localScale.interpolateLocal(tmpV2, weight); + } + + // Ensures no new weights will be blended in the future. + currentWeightSum = 1; + + vars.release(); + } + } + + /** + * Sets local bind transform for bone. + * Call setBindingPose() after all of the skeleton bones' bind transforms are set to save them. + */ + public void setBindTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) { + initialPos.set(translation); + initialRot.set(rotation); + //ogre.xml can have null scale values breaking this if the check is removed + if (scale != null) { + initialScale.set(scale); + } + + localPos.set(translation); + localRot.set(rotation); + if (scale != null) { + localScale.set(scale); + } + } + + private String toString(int depth) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < depth; i++) { + sb.append('-'); + } + + sb.append(name).append(" bone\n"); + for (Bone child : children) { + sb.append(child.toString(depth + 1)); + } + return sb.toString(); + } + + @Override + public String toString() { + return this.toString(0); + } + + @Override + @SuppressWarnings("unchecked") + public void read(JmeImporter im) throws IOException { + InputCapsule input = im.getCapsule(this); + + name = input.readString("name", null); + initialPos = (Vector3f) input.readSavable("initialPos", null); + initialRot = (Quaternion) input.readSavable("initialRot", null); + initialScale = (Vector3f) input.readSavable("initialScale", new Vector3f(1.0f, 1.0f, 1.0f)); + attachNode = (Node) input.readSavable("attachNode", null); + + localPos.set(initialPos); + localRot.set(initialRot); + + ArrayList childList = input.readSavableArrayList("children", null); + for (int i = childList.size() - 1; i >= 0; i--) { + this.addChild(childList.get(i)); + } + + // NOTE: Parent skeleton will call update() then setBindingPose() + // after Skeleton has been de-serialized. + // Therefore, worldBindInversePos and worldBindInverseRot + // will be reconstructed based on that information. + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule output = ex.getCapsule(this); + + output.write(name, "name", null); + output.write(attachNode, "attachNode", null); + output.write(initialPos, "initialPos", null); + output.write(initialRot, "initialRot", null); + output.write(initialScale, "initialScale", new Vector3f(1.0f, 1.0f, 1.0f)); + output.writeSavableArrayList(children, "children", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/BoneAnimation.java b/jme3-core/src/main/java/com/jme3/animation/BoneAnimation.java new file mode 100644 index 000000000..735ee05a5 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/BoneAnimation.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +/** + * @deprecated use Animation instead with tracks of selected type (ie. BoneTrack, SpatialTrack, MeshTrack) + */ +@Deprecated +public final class BoneAnimation extends Animation { + + @Deprecated + public BoneAnimation(String name, float length) { + super(name, length); + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/BoneTrack.java b/jme3-core/src/main/java/com/jme3/animation/BoneTrack.java new file mode 100644 index 000000000..bf3c2da73 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/BoneTrack.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.*; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.BitSet; + +/** + * Contains a list of transforms and times for each keyframe. + * + * @author Kirill Vainer + */ +public final class BoneTrack implements Track { + + /** + * Bone index in the skeleton which this track effects. + */ + private int targetBoneIndex; + + /** + * Transforms and times for track. + */ + private CompactVector3Array translations; + private CompactQuaternionArray rotations; + private CompactVector3Array scales; + private float[] times; + + /** + * Serialization-only. Do not use. + */ + public BoneTrack() { + } + + /** + * Creates a bone track for the given bone index + * @param targetBoneIndex the bone index + * @param times a float array with the time of each frame + * @param translations the translation of the bone for each frame + * @param rotations the rotation of the bone for each frame + */ + public BoneTrack(int targetBoneIndex, float[] times, Vector3f[] translations, Quaternion[] rotations) { + this.targetBoneIndex = targetBoneIndex; + this.setKeyframes(times, translations, rotations); + } + + /** + * Creates a bone track for the given bone index + * @param targetBoneIndex the bone index + * @param times a float array with the time of each frame + * @param translations the translation of the bone for each frame + * @param rotations the rotation of the bone for each frame + * @param scales the scale of the bone for each frame + */ + public BoneTrack(int targetBoneIndex, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) { + this.targetBoneIndex = targetBoneIndex; + this.setKeyframes(times, translations, rotations, scales); + } + + /** + * Creates a bone track for the given bone index + * @param targetBoneIndex the bone's index + */ + public BoneTrack(int targetBoneIndex) { + this.targetBoneIndex = targetBoneIndex; + } + + /** + * @return the bone index of this bone track. + */ + public int getTargetBoneIndex() { + return targetBoneIndex; + } + + /** + * return the array of rotations of this track + * @return + */ + public Quaternion[] getRotations() { + return rotations.toObjectArray(); + } + + /** + * returns the array of scales for this track + * @return + */ + public Vector3f[] getScales() { + return scales == null ? null : scales.toObjectArray(); + } + + /** + * returns the arrays of time for this track + * @return + */ + public float[] getTimes() { + return times; + } + + /** + * returns the array of translations of this track + * @return + */ + public Vector3f[] getTranslations() { + return translations.toObjectArray(); + } + + /** + * Set the translations and rotations for this bone track + * @param times a float array with the time of each frame + * @param translations the translation of the bone for each frame + * @param rotations the rotation of the bone for each frame + */ + public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations) { + if (times.length == 0) { + throw new RuntimeException("BoneTrack with no keyframes!"); + } + + assert times.length == translations.length && times.length == rotations.length; + + this.times = times; + this.translations = new CompactVector3Array(); + this.translations.add(translations); + this.translations.freeze(); + this.rotations = new CompactQuaternionArray(); + this.rotations.add(rotations); + this.rotations.freeze(); + } + + /** + * Set the translations, rotations and scales for this bone track + * @param times a float array with the time of each frame + * @param translations the translation of the bone for each frame + * @param rotations the rotation of the bone for each frame + * @param scales the scale of the bone for each frame + */ + public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) { + this.setKeyframes(times, translations, rotations); + assert times.length == scales.length; + if (scales != null) { + this.scales = new CompactVector3Array(); + this.scales.add(scales); + this.scales.freeze(); + } + } + + /** + * + * Modify the bone which this track modifies in the skeleton to contain + * the correct animation transforms for a given time. + * The transforms can be interpolated in some method from the keyframes. + * + * @param time the current time of the animation + * @param weight the weight of the animation + * @param control + * @param channel + * @param vars + */ + public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { + BitSet affectedBones = channel.getAffectedBones(); + if (affectedBones != null && !affectedBones.get(targetBoneIndex)) { + return; + } + + Bone target = control.getSkeleton().getBone(targetBoneIndex); + + Vector3f tempV = vars.vect1; + Vector3f tempS = vars.vect2; + Quaternion tempQ = vars.quat1; + Vector3f tempV2 = vars.vect3; + Vector3f tempS2 = vars.vect4; + Quaternion tempQ2 = vars.quat2; + + int lastFrame = times.length - 1; + if (time < 0 || lastFrame == 0) { + rotations.get(0, tempQ); + translations.get(0, tempV); + if (scales != null) { + scales.get(0, tempS); + } + } else if (time >= times[lastFrame]) { + rotations.get(lastFrame, tempQ); + translations.get(lastFrame, tempV); + if (scales != null) { + scales.get(lastFrame, tempS); + } + } else { + int startFrame = 0; + int endFrame = 1; + // use lastFrame so we never overflow the array + int i; + for (i = 0; i < lastFrame && times[i] < time; i++) { + startFrame = i; + endFrame = i + 1; + } + + float blend = (time - times[startFrame]) + / (times[endFrame] - times[startFrame]); + + rotations.get(startFrame, tempQ); + translations.get(startFrame, tempV); + if (scales != null) { + scales.get(startFrame, tempS); + } + rotations.get(endFrame, tempQ2); + translations.get(endFrame, tempV2); + if (scales != null) { + scales.get(endFrame, tempS2); + } + tempQ.nlerp(tempQ2, blend); + tempV.interpolateLocal(tempV2, blend); + tempS.interpolateLocal(tempS2, blend); + } + +// if (weight != 1f) { + target.blendAnimTransforms(tempV, tempQ, scales != null ? tempS : null, weight); +// } else { +// target.setAnimTransforms(tempV, tempQ, scales != null ? tempS : null); +// } + } + + /** + * @return the length of the track + */ + public float getLength() { + return times == null ? 0 : times[times.length - 1] - times[0]; + } + + /** + * This method creates a clone of the current object. + * @return a clone of the current object + */ + @Override + public BoneTrack clone() { + int tablesLength = times.length; + + float[] times = this.times.clone(); + Vector3f[] sourceTranslations = this.getTranslations(); + Quaternion[] sourceRotations = this.getRotations(); + Vector3f[] sourceScales = this.getScales(); + + Vector3f[] translations = new Vector3f[tablesLength]; + Quaternion[] rotations = new Quaternion[tablesLength]; + Vector3f[] scales = new Vector3f[tablesLength]; + for (int i = 0; i < tablesLength; ++i) { + translations[i] = sourceTranslations[i].clone(); + rotations[i] = sourceRotations[i].clone(); + scales[i] = sourceScales != null ? sourceScales[i].clone() : new Vector3f(1.0f, 1.0f, 1.0f); + } + + // Need to use the constructor here because of the final fields used in this class + return new BoneTrack(targetBoneIndex, times, translations, rotations, scales); + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(targetBoneIndex, "boneIndex", 0); + oc.write(translations, "translations", null); + oc.write(rotations, "rotations", null); + oc.write(times, "times", null); + oc.write(scales, "scales", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + targetBoneIndex = ic.readInt("boneIndex", 0); + + translations = (CompactVector3Array) ic.readSavable("translations", null); + rotations = (CompactQuaternionArray) ic.readSavable("rotations", null); + times = ic.readFloatArray("times", null); + scales = (CompactVector3Array) ic.readSavable("scales", null); + + //Backward compatibility for old j3o files generated before revision 6807 + if (im.getFormatVersion() == 0){ + if (translations == null) { + Savable[] sav = ic.readSavableArray("translations", null); + if (sav != null) { + translations = new CompactVector3Array(); + Vector3f[] transCopy = new Vector3f[sav.length]; + System.arraycopy(sav, 0, transCopy, 0, sav.length); + translations.add(transCopy); + translations.freeze(); + } + } + if (rotations == null) { + Savable[] sav = ic.readSavableArray("rotations", null); + if (sav != null) { + rotations = new CompactQuaternionArray(); + Quaternion[] rotCopy = new Quaternion[sav.length]; + System.arraycopy(sav, 0, rotCopy, 0, sav.length); + rotations.add(rotCopy); + rotations.freeze(); + } + } + } + } + + public void setTime(float time, float weight, AnimControl control, AnimChannel channel) { + throw new UnsupportedOperationException("Not supported yet."); + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java b/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java new file mode 100644 index 000000000..ae7ae6703 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.scene.Spatial; + +/** + * An interface that allow to clone a Track for a given Spatial. + * The spatial fed to the method is the Spatial holding the AnimControl controling the Animation using this track. + * + * Implement this interface only if you make your own Savable Track and that the track has a direct reference to a Spatial in the scene graph. + * This Spatial is assumed to be a child of the spatial holding the AnimControl. + * + * + * @author Nehon + */ +public interface ClonableTrack extends Track { + + /** + * Allows to clone the track for a given Spatial. + * The spatial fed to the method is the Spatial holding the AnimControl controling the Animation using this track. + * This method will be called during the loading process of a j3o model by the assetManager. + * The assetManager keeps the original model in cache and returns a clone of the model. + * + * This method prupose is to find the cloned reference of the original spatial which it refers to in the cloned model. + * + * See EffectTrack for a proper implementation. + * + * @param spatial the spatial holding the AnimControl + * @return the cloned Track + */ + public Track cloneForSpatial(Spatial spatial); + + /** + * Method responsible of cleaning UserData on referenced Spatials when the Track is deleted + */ + public void cleanUp(); +} diff --git a/jme3-core/src/main/java/com/jme3/animation/CompactArray.java b/jme3-core/src/main/java/com/jme3/animation/CompactArray.java new file mode 100644 index 000000000..03983a10a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/CompactArray.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import java.lang.reflect.Array; +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; + + /** + * Creates a compact array + */ + public CompactArray() { + } + + /** + * create array using serialized data + * @param compressedArray + * @param index + */ + public CompactArray(float[] compressedArray, int[] index) { + this.array = compressedArray; + this.index = index; + } + + /** + * Add objects. + * They are serialized automatically when get() method is called. + * @param objArray + */ + public void add(T... objArray) { + if (objArray == null || objArray.length == 0) { + return; + } + invalid = true; + int base = 0; + if (index == null) { + index = new int[objArray.length]; + } else { + if (indexPool.isEmpty()) { + throw new RuntimeException("Internal is already fixed"); + } + base = index.length; + + int[] tmp = new int[base + objArray.length]; + System.arraycopy(index, 0, tmp, 0, index.length); + index = tmp; + //index = Arrays.copyOf(index, base+objArray.length); + } + for (int j = 0; j < objArray.length; j++) { + T obj = objArray[j]; + if (obj == null) { + index[base + j] = -1; + } else { + Integer i = indexPool.get(obj); + if (i == null) { + i = indexPool.size(); + indexPool.put(obj, i); + } + index[base + j] = i; + } + } + } + + /** + * release objects. + * add() method call is not allowed anymore. + */ + public void freeze() { + serialize(); + indexPool.clear(); + } + + /** + * @param index + * @param value + */ + public final void set(int index, T value) { + int j = getCompactIndex(index); + serialize(j, value); + } + + /** + * returns the object for the given index + * @param index the index + * @param store an object to store the result + * @return + */ + public final T get(int index, T store) { + serialize(); + int j = getCompactIndex(index); + return deserialize(j, store); + } + + /** + * return a float array of serialized data + * @return + */ + public final float[] getSerializedData() { + serialize(); + return array; + } + + /** + * serialize this compact array + */ + public final void serialize() { + if (invalid) { + int newSize = indexPool.size() * getTupleSize(); + if (array == null || Array.getLength(array) < newSize) { + array = ensureCapacity(array, newSize); + for (Map.Entry entry : indexPool.entrySet()) { + int i = entry.getValue(); + T obj = entry.getKey(); + serialize(i, obj); + } + } + invalid = false; + } + } + + /** + * @return compacted array's primitive size + */ + protected final int getSerializedSize() { + return Array.getLength(getSerializedData()); + } + + /** + * Ensure the capacity for the given array and the given size + * @param arr the array + * @param size the size + * @return + */ + protected float[] ensureCapacity(float[] arr, int size) { + if (arr == null) { + return new float[size]; + } else if (arr.length >= size) { + return arr; + } else { + float[] tmp = new float[size]; + System.arraycopy(arr, 0, tmp, 0, arr.length); + return tmp; + //return Arrays.copyOf(arr, size); + } + } + + /** + * retrun an array of indices for the given objects + * @param objArray + * @return + */ + public final int[] getIndex(T... objArray) { + int[] index = new int[objArray.length]; + for (int i = 0; i < index.length; i++) { + T obj = objArray[i]; + index[i] = obj != null ? indexPool.get(obj) : -1; + } + return index; + } + + /** + * returns the corresponding index in the compact array + * @param objIndex + * @return object index in the compacted object array + */ + public int getCompactIndex(int objIndex) { + return index != null ? index[objIndex] : objIndex; + } + + /** + * @return uncompressed object size + */ + public final int getTotalObjectSize() { + assert getSerializedSize() % getTupleSize() == 0; + return index != null ? index.length : getSerializedSize() / getTupleSize(); + } + + /** + * @return compressed object size + */ + public final int getCompactObjectSize() { + assert getSerializedSize() % getTupleSize() == 0; + return getSerializedSize() / getTupleSize(); + } + + /** + * decompress and return object array + * @return decompress and return object array + */ + public final T[] toObjectArray() { + try { + T[] compactArr = (T[]) Array.newInstance(getElementClass(), getSerializedSize() / getTupleSize()); + for (int i = 0; i < compactArr.length; i++) { + compactArr[i] = getElementClass().newInstance(); + deserialize(i, compactArr[i]); + } + + T[] objArr = (T[]) Array.newInstance(getElementClass(), getTotalObjectSize()); + for (int i = 0; i < objArr.length; i++) { + int compactIndex = getCompactIndex(i); + objArr[i] = compactArr[compactIndex]; + } + return objArr; + } catch (Exception e) { + return null; + } + } + + /** + * serialize object + * @param compactIndex compacted object index + * @param store + */ + protected abstract void serialize(int compactIndex, T store); + + /** + * deserialize object + * @param compactIndex compacted object index + * @param store + */ + protected abstract T deserialize(int compactIndex, T store); + + /** + * serialized size of one object element + */ + protected abstract int getTupleSize(); + + protected abstract Class getElementClass(); +} diff --git a/jme3-core/src/main/java/com/jme3/animation/CompactQuaternionArray.java b/jme3-core/src/main/java/com/jme3/animation/CompactQuaternionArray.java new file mode 100644 index 000000000..21b1bd9ff --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/CompactQuaternionArray.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.*; +import com.jme3.math.Quaternion; +import java.io.IOException; + +/** + * Serialize and compress {@link Quaternion}[] by indexing same values + * It is converted to float[] + * @author Lim, YongHoon + */ +public class CompactQuaternionArray extends CompactArray implements Savable { + + /** + * creates a compact Quaternion array + */ + public CompactQuaternionArray() { + } + + /** + * creates a compact Quaternion array + * @param dataArray the data array + * @param index the indices array + */ + public CompactQuaternionArray(float[] dataArray, int[] index) { + super(dataArray, index); + } + + @Override + protected final int getTupleSize() { + return 4; + } + + @Override + protected final Class 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/jme3-core/src/main/java/com/jme3/animation/CompactVector3Array.java b/jme3-core/src/main/java/com/jme3/animation/CompactVector3Array.java new file mode 100644 index 000000000..289f81ec8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/CompactVector3Array.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.*; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * Serialize and compress Vector3f[] by indexing same values + * @author Lim, YongHoon + */ +public class CompactVector3Array extends CompactArray implements Savable { + + /** + * Creates a compact vector array + */ + public CompactVector3Array() { + } + + /** + * creates a compact vector array + * @param dataArray the data array + * @param index the indices + */ + public CompactVector3Array(float[] dataArray, int[] index) { + super(dataArray, index); + } + + @Override + protected final int getTupleSize() { + return 3; + } + + @Override + protected final Class 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/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java b/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java new file mode 100644 index 000000000..9d6b9b4d2 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.effect.ParticleEmitter; +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.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.control.Control; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * EffectTrack is a track to add to an existing animation, to emmit particles + * during animations for example : exhausts, dust raised by foot steps, shock + * waves, lightnings etc... + * + * usage is + *

    + * AnimControl control model.getControl(AnimControl.class);
    + * EffectTrack track = new EffectTrack(existingEmmitter, control.getAnim("TheAnim").getLength());
    + * control.getAnim("TheAnim").addTrack(track);
    + * 
    + * + * if the emitter has emmits 0 particles per seconds emmitAllPArticles will be + * called on it at time 0 + startOffset. if it he it has more it will start + * emmit normally at time 0 + startOffset. + * + * + * @author Nehon + */ +public class EffectTrack implements ClonableTrack { + + private static final Logger logger = Logger.getLogger(EffectTrack.class.getName()); + private ParticleEmitter emitter; + private float startOffset = 0; + private float particlesPerSeconds = 0; + private float length = 0; + private boolean emitted = false; + private boolean initialized = false; + //control responsible for disable and cull the emitter once all particles are gone + private KillParticleControl killParticles = new KillParticleControl(); + + public static class KillParticleControl extends AbstractControl { + + ParticleEmitter emitter; + boolean stopRequested = false; + boolean remove = false; + + public KillParticleControl() { + } + + @Override + public void setSpatial(Spatial spatial) { + super.setSpatial(spatial); + if (spatial != null) { + if (spatial instanceof ParticleEmitter) { + emitter = (ParticleEmitter) spatial; + } else { + throw new IllegalArgumentException("KillParticleEmitter can only ba attached to ParticleEmitter"); + } + } + + + } + + @Override + protected void controlUpdate(float tpf) { + if (remove) { + emitter.removeControl(this); + return; + } + if (emitter.getNumVisibleParticles() == 0) { + emitter.setCullHint(CullHint.Always); + emitter.setEnabled(false); + emitter.removeControl(this); + stopRequested = false; + } + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } + + @Override + public Control cloneForSpatial(Spatial spatial) { + + KillParticleControl c = new KillParticleControl(); + //this control should be removed as it shouldn't have been persisted in the first place + //In the quest to find the less hackish solution to achieve this, + //making it remove itself from the spatial in the first update loop when loaded was the less bad. + c.remove = true; + c.setSpatial(spatial); + return c; + + } + }; + + //Anim listener that stops the Emmitter when the animation is finished or changed. + private class OnEndListener implements AnimEventListener { + + public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { + stop(); + } + + public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { + } + } + + /** + * default constructor only for serialization + */ + public EffectTrack() { + } + + /** + * Creates and EffectTrack + * + * @param emitter the emmitter of the track + * @param length the length of the track (usually the length of the + * animation you want to add the track to) + */ + public EffectTrack(ParticleEmitter emitter, float length) { + this.emitter = emitter; + //saving particles per second value + this.particlesPerSeconds = emitter.getParticlesPerSec(); + //setting the emmitter to not emmit. + this.emitter.setParticlesPerSec(0); + this.length = length; + //Marking the emitter with a reference to this track for further use in deserialization. + setUserData(this); + + } + + /** + * Creates and EffectTrack + * + * @param emitter the emmitter of the track + * @param length the length of the track (usually the length of the + * animation you want to add the track to) + * @param startOffset the time in second when the emitter will be triggerd + * after the animation starts (default is 0) + */ + public EffectTrack(ParticleEmitter emitter, float length, float startOffset) { + this(emitter, length); + this.startOffset = startOffset; + } + + /** + * Internal use only + * + * @see Track#setTime(float, float, com.jme3.animation.AnimControl, + * com.jme3.animation.AnimChannel, com.jme3.util.TempVars) + */ + public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { + + if (time >= length) { + return; + } + //first time adding the Animation listener to stop the track at the end of the animation + if (!initialized) { + control.addListener(new OnEndListener()); + initialized = true; + } + //checking fo time to trigger the effect + if (!emitted && time >= startOffset) { + emitted = true; + emitter.setCullHint(CullHint.Dynamic); + emitter.setEnabled(true); + //if the emitter has 0 particles per seconds emmit all particles in one shot + if (particlesPerSeconds == 0) { + emitter.emitAllParticles(); + if (!killParticles.stopRequested) { + emitter.addControl(killParticles); + killParticles.stopRequested = true; + } + } else { + //else reset its former particlePerSec value to let it emmit. + emitter.setParticlesPerSec(particlesPerSeconds); + } + } + } + + //stops the emmiter to emit. + private void stop() { + emitter.setParticlesPerSec(0); + emitted = false; + if (!killParticles.stopRequested) { + emitter.addControl(killParticles); + killParticles.stopRequested = true; + } + + } + + /** + * Retruns the length of the track + * + * @return length of the track + */ + public float getLength() { + return length; + } + + /** + * Clone this track + * + * @return + */ + @Override + public Track clone() { + return new EffectTrack(emitter, length, startOffset); + } + + /** + * This method clone the Track and search for the cloned counterpart of the + * original emmitter in the given cloned spatial. The spatial is assumed to + * be the Spatial holding the AnimControl controling the animation using + * this Track. + * + * @param spatial the Spatial holding the AnimControl + * @return the cloned Track with proper reference + */ + public Track cloneForSpatial(Spatial spatial) { + EffectTrack effectTrack = new EffectTrack(); + effectTrack.particlesPerSeconds = this.particlesPerSeconds; + effectTrack.length = this.length; + effectTrack.startOffset = this.startOffset; + + //searching for the newly cloned ParticleEmitter + effectTrack.emitter = findEmitter(spatial); + if (effectTrack.emitter == null) { + logger.log(Level.WARNING, "{0} was not found in {1} or is not bound to this track", new Object[]{emitter.getName(), spatial.getName()}); + effectTrack.emitter = emitter; + } + + removeUserData(this); + //setting user data on the new emmitter and marking it with a reference to the cloned Track. + setUserData(effectTrack); + effectTrack.emitter.setParticlesPerSec(0); + return effectTrack; + } + + /** + * recursive function responsible for finding the newly cloned Emitter + * + * @param spat + * @return + */ + private ParticleEmitter findEmitter(Spatial spat) { + if (spat instanceof ParticleEmitter) { + //spat is a PArticleEmitter + ParticleEmitter em = (ParticleEmitter) spat; + //getting the UserData TrackInfo so check if it should be attached to this Track + TrackInfo t = (TrackInfo) em.getUserData("TrackInfo"); + if (t != null && t.getTracks().contains(this)) { + return em; + } + return null; + + } else if (spat instanceof Node) { + for (Spatial child : ((Node) spat).getChildren()) { + ParticleEmitter em = findEmitter(child); + if (em != null) { + return em; + } + } + } + return null; + } + + public void cleanUp() { + TrackInfo t = (TrackInfo) emitter.getUserData("TrackInfo"); + t.getTracks().remove(this); + if (t.getTracks().isEmpty()) { + emitter.setUserData("TrackInfo", null); + } + } + + /** + * + * @return the emitter used by this track + */ + public ParticleEmitter getEmitter() { + return emitter; + } + + /** + * Sets the Emitter to use in this track + * + * @param emitter + */ + public void setEmitter(ParticleEmitter emitter) { + if (this.emitter != null) { + TrackInfo data = (TrackInfo) emitter.getUserData("TrackInfo"); + data.getTracks().remove(this); + } + this.emitter = emitter; + //saving particles per second value + this.particlesPerSeconds = emitter.getParticlesPerSec(); + //setting the emmitter to not emmit. + this.emitter.setParticlesPerSec(0); + setUserData(this); + } + + /** + * + * @return the start offset of the track + */ + public float getStartOffset() { + return startOffset; + } + + /** + * set the start offset of the track + * + * @param startOffset + */ + public void setStartOffset(float startOffset) { + this.startOffset = startOffset; + } + + private void setUserData(EffectTrack effectTrack) { + //fetching the UserData TrackInfo. + TrackInfo data = (TrackInfo) effectTrack.emitter.getUserData("TrackInfo"); + + //if it does not exist, we create it and attach it to the emitter. + if (data == null) { + data = new TrackInfo(); + effectTrack.emitter.setUserData("TrackInfo", data); + } + + //adding the given Track to the TrackInfo. + data.addTrack(effectTrack); + + + } + + private void removeUserData(EffectTrack effectTrack) { + //fetching the UserData TrackInfo. + TrackInfo data = (TrackInfo) effectTrack.emitter.getUserData("TrackInfo"); + + //if it does not exist, we create it and attach it to the emitter. + if (data == null) { + return; + } + + //removing the given Track to the TrackInfo. + data.getTracks().remove(effectTrack); + + + } + + /** + * Internal use only serialization + * + * @param ex exporter + * @throws IOException exception + */ + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + //reseting the particle emission rate on the emitter before saving. + emitter.setParticlesPerSec(particlesPerSeconds); + out.write(emitter, "emitter", null); + out.write(particlesPerSeconds, "particlesPerSeconds", 0); + out.write(length, "length", 0); + out.write(startOffset, "startOffset", 0); + //Setting emission rate to 0 so that this track can go on being used. + emitter.setParticlesPerSec(0); + } + + /** + * Internal use only serialization + * + * @param im importer + * @throws IOException Exception + */ + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + this.particlesPerSeconds = in.readFloat("particlesPerSeconds", 0); + //reading the emitter even if the track will then reference its cloned counter part if it's loaded with the assetManager. + //This also avoid null pointer exception if the model is not loaded via the AssetManager. + emitter = (ParticleEmitter) in.readSavable("emitter", null); + emitter.setParticlesPerSec(0); + //if the emitter was saved with a KillParticleControl we remove it. +// Control c = emitter.getControl(KillParticleControl.class); +// if(c!=null){ +// emitter.removeControl(c); +// } + //emitter.removeControl(KillParticleControl.class); + length = in.readFloat("length", length); + startOffset = in.readFloat("startOffset", 0); + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/LoopMode.java b/jme3-core/src/main/java/com/jme3/animation/LoopMode.java new file mode 100644 index 000000000..ca7b1eb1c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/LoopMode.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +/** + * 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/jme3-core/src/main/java/com/jme3/animation/Pose.java b/jme3-core/src/main/java/com/jme3/animation/Pose.java new file mode 100644 index 000000000..fc06d0d07 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/Pose.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.*; +import com.jme3.math.Vector3f; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.nio.FloatBuffer; + +/** + * A pose is a list of offsets that say where a mesh vertices should be for this pose. + */ +public final class Pose implements Savable, Cloneable { + + private String name; + private int targetMeshIndex; + + private Vector3f[] offsets; + private int[] indices; + + private transient final Vector3f tempVec = new Vector3f(); + private transient final Vector3f tempVec2 = new Vector3f(); + + public Pose(String name, int targetMeshIndex, Vector3f[] offsets, int[] indices){ + this.name = name; + this.targetMeshIndex = targetMeshIndex; + this.offsets = offsets; + this.indices = indices; + } + + /** + * Serialization-only. Do not use. + */ + public Pose() + { + } + + public int getTargetMeshIndex(){ + return targetMeshIndex; + } + + + /** + * Applies the offsets of this pose to the vertex buffer given by the blend factor. + * + * @param blend Blend factor, 0 = no change to vertex buffer, 1 = apply full offsets + * @param vertbuf Vertex buffer to apply this pose to + */ + public void apply(float blend, FloatBuffer vertbuf){ + for (int i = 0; i < indices.length; i++){ + Vector3f offset = offsets[i]; + int vertIndex = indices[i]; + + tempVec.set(offset).multLocal(blend); + + // acquire vertex + BufferUtils.populateFromBuffer(tempVec2, vertbuf, vertIndex); + + // add offset multiplied by factor + tempVec2.addLocal(tempVec); + + // write modified vertex + BufferUtils.setInBuffer(tempVec2, vertbuf, vertIndex); + } + } + + /** + * This method creates a clone of the current object. + * @return a clone of the current object + */ + @Override + public Pose clone() { + try { + Pose result = (Pose) super.clone(); + result.indices = this.indices.clone(); + if (this.offsets != null) { + result.offsets = new Vector3f[this.offsets.length]; + for (int i = 0; i < this.offsets.length; ++i) { + result.offsets[i] = this.offsets[i].clone(); + } + } + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + public void write(JmeExporter e) throws IOException { + OutputCapsule out = e.getCapsule(this); + out.write(name, "name", ""); + out.write(targetMeshIndex, "meshIndex", -1); + out.write(offsets, "offsets", null); + out.write(indices, "indices", null); + } + + public void read(JmeImporter i) throws IOException { + InputCapsule in = i.getCapsule(this); + name = in.readString("name", ""); + targetMeshIndex = in.readInt("meshIndex", -1); + indices = in.readIntArray("indices", null); + + Savable[] readSavableArray = in.readSavableArray("offsets", null); + if (readSavableArray != null) { + offsets = new Vector3f[readSavableArray.length]; + System.arraycopy(readSavableArray, 0, offsets, 0, readSavableArray.length); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/PoseTrack.java b/jme3-core/src/main/java/com/jme3/animation/PoseTrack.java new file mode 100644 index 000000000..92c24ce01 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/PoseTrack.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.*; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.FloatBuffer; + +/** + * A single track of pose animation associated with a certain mesh. + */ +@Deprecated +public final class PoseTrack implements Track { + + private int targetMeshIndex; + private PoseFrame[] frames; + private float[] times; + + public static class PoseFrame implements Savable, Cloneable { + + Pose[] poses; + float[] weights; + + public PoseFrame(Pose[] poses, float[] weights) { + this.poses = poses; + this.weights = weights; + } + + /** + * Serialization-only. Do not use. + */ + public PoseFrame() + { + } + + /** + * This method creates a clone of the current object. + * @return a clone of the current object + */ + @Override + public PoseFrame clone() { + try { + PoseFrame result = (PoseFrame) super.clone(); + result.weights = this.weights.clone(); + if (this.poses != null) { + result.poses = new Pose[this.poses.length]; + for (int i = 0; i < this.poses.length; ++i) { + result.poses[i] = this.poses[i].clone(); + } + } + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + public void write(JmeExporter e) throws IOException { + OutputCapsule out = e.getCapsule(this); + out.write(poses, "poses", null); + out.write(weights, "weights", null); + } + + public void read(JmeImporter i) throws IOException { + InputCapsule in = i.getCapsule(this); + weights = in.readFloatArray("weights", null); + + Savable[] readSavableArray = in.readSavableArray("poses", null); + if (readSavableArray != null) { + poses = new Pose[readSavableArray.length]; + System.arraycopy(readSavableArray, 0, poses, 0, readSavableArray.length); + } + } + } + + public PoseTrack(int targetMeshIndex, float[] times, PoseFrame[] frames){ + this.targetMeshIndex = targetMeshIndex; + this.times = times; + this.frames = frames; + } + + /** + * Serialization-only. Do not use. + */ + public PoseTrack() + { + } + + private void applyFrame(Mesh target, int frameIndex, float weight){ + PoseFrame frame = frames[frameIndex]; + VertexBuffer pb = target.getBuffer(Type.Position); + for (int i = 0; i < frame.poses.length; i++){ + Pose pose = frame.poses[i]; + float poseWeight = frame.weights[i] * weight; + + pose.apply(poseWeight, (FloatBuffer) pb.getData()); + } + + // force to re-upload data to gpu + pb.updateData(pb.getData()); + } + + public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { + // TODO: When MeshControl is created, it will gather targets + // list automatically which is then retrieved here. + + /* + Mesh target = targets[targetMeshIndex]; + if (time < times[0]) { + applyFrame(target, 0, weight); + } else if (time > times[times.length - 1]) { + applyFrame(target, times.length - 1, weight); + } else { + int startFrame = 0; + for (int i = 0; i < times.length; i++) { + if (times[i] < time) { + startFrame = i; + } + } + + int endFrame = startFrame + 1; + float blend = (time - times[startFrame]) / (times[endFrame] - times[startFrame]); + applyFrame(target, startFrame, blend * weight); + applyFrame(target, endFrame, (1f - blend) * weight); + } + */ + } + + /** + * @return the length of the track + */ + public float getLength() { + return times == null ? 0 : times[times.length - 1] - times[0]; + } + + /** + * This method creates a clone of the current object. + * @return a clone of the current object + */ + @Override + public PoseTrack clone() { + try { + PoseTrack result = (PoseTrack) super.clone(); + result.times = this.times.clone(); + if (this.frames != null) { + result.frames = new PoseFrame[this.frames.length]; + for (int i = 0; i < this.frames.length; ++i) { + result.frames[i] = this.frames[i].clone(); + } + } + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + @Override + public void write(JmeExporter e) throws IOException { + OutputCapsule out = e.getCapsule(this); + out.write(targetMeshIndex, "meshIndex", 0); + out.write(frames, "frames", null); + out.write(times, "times", null); + } + + @Override + public void read(JmeImporter i) throws IOException { + InputCapsule in = i.getCapsule(this); + targetMeshIndex = in.readInt("meshIndex", 0); + times = in.readFloatArray("times", null); + + Savable[] readSavableArray = in.readSavableArray("frames", null); + if (readSavableArray != null) { + frames = new PoseFrame[readSavableArray.length]; + System.arraycopy(readSavableArray, 0, frames, 0, readSavableArray.length); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/Skeleton.java b/jme3-core/src/main/java/com/jme3/animation/Skeleton.java new file mode 100644 index 000000000..b0d3419d8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/Skeleton.java @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.*; +import com.jme3.math.Matrix4f; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Skeleton is a convenience class for managing a bone hierarchy. + * Skeleton updates the world transforms to reflect the current local + * animated matrixes. + * + * @author Kirill Vainer + */ +public final class Skeleton implements Savable { + + private Bone[] rootBones; + private Bone[] boneList; + + /** + * Contains the skinning matrices, multiplying it by a vertex effected by a bone + * will cause it to go to the animated position. + */ + private transient Matrix4f[] skinningMatrixes; + + /** + * Creates a skeleton from a bone list. + * The root bones are found automatically. + *

    + * Note that using this constructor will cause the bones in the list + * to have their bind pose recomputed based on their local transforms. + * + * @param boneList The list of bones to manage by this Skeleton + */ + public Skeleton(Bone[] boneList) { + this.boneList = boneList; + + List 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(); + } + } + + /** + * Special-purpose copy constructor. + *

    + * Shallow copies bind pose data from the source skeleton, does not + * copy any other data. + * + * @param source The source Skeleton to copy from + */ + public Skeleton(Skeleton source) { + Bone[] sourceList = source.boneList; + boneList = new Bone[sourceList.length]; + for (int i = 0; i < sourceList.length; i++) { + boneList[i] = new Bone(sourceList[i]); + } + + rootBones = new Bone[source.rootBones.length]; + for (int i = 0; i < rootBones.length; i++) { + rootBones[i] = recreateBoneStructure(source.rootBones[i]); + } + createSkinningMatrices(); + + for (int i = rootBones.length - 1; i >= 0; i--) { + rootBones[i].update(); + } + } + + /** + * Serialization only. Do not use. + */ + public Skeleton() { + } + + private void createSkinningMatrices() { + skinningMatrixes = new Matrix4f[boneList.length]; + for (int i = 0; i < skinningMatrixes.length; i++) { + skinningMatrixes[i] = new Matrix4f(); + } + } + + private Bone recreateBoneStructure(Bone sourceRoot) { + Bone targetRoot = getBone(sourceRoot.getName()); + List children = sourceRoot.getChildren(); + for (int i = 0; i < children.size(); i++) { + Bone sourceChild = children.get(i); + // find my version of the child + Bone targetChild = getBone(sourceChild.getName()); + targetRoot.addChild(targetChild); + recreateBoneStructure(sourceChild); + } + + return targetRoot; + } + + /** + * Updates world transforms for all bones in this skeleton. + * Typically called after setting local animation transforms. + */ + public void updateWorldVectors() { + for (int i = rootBones.length - 1; i >= 0; i--) { + rootBones[i].update(); + } + } + + /** + * Saves the current skeleton state as it's binding pose. + */ + public void setBindingPose() { + for (int i = rootBones.length - 1; i >= 0; i--) { + rootBones[i].setBindingPose(); + } + } + + /** + * Reset the skeleton to bind pose. + */ + public final void reset() { + for (int i = rootBones.length - 1; i >= 0; i--) { + rootBones[i].reset(); + } + } + + /** + * Reset the skeleton to bind pose and updates the bones + */ + public final void resetAndUpdate() { + for (int i = rootBones.length - 1; i >= 0; i--) { + Bone rootBone = rootBones[i]; + rootBone.reset(); + rootBone.update(); + } + } + + /** + * returns the array of all root bones of this skeleton + * @return + */ + public Bone[] getRoots() { + return rootBones; + } + + /** + * return a bone for the given index + * @param index + * @return + */ + public Bone getBone(int index) { + return boneList[index]; + } + + /** + * returns the bone with the given name + * @param name + * @return + */ + public Bone getBone(String name) { + for (int i = 0; i < boneList.length; i++) { + if (boneList[i].getName().equals(name)) { + return boneList[i]; + } + } + return null; + } + + /** + * returns the bone index of the given bone + * @param bone + * @return + */ + public int getBoneIndex(Bone bone) { + for (int i = 0; i < boneList.length; i++) { + if (boneList[i] == bone) { + return i; + } + } + + return -1; + } + + /** + * returns the bone index of the bone that has the given name + * @param name + * @return + */ + public int getBoneIndex(String name) { + for (int i = 0; i < boneList.length; i++) { + if (boneList[i].getName().equals(name)) { + return i; + } + } + + return -1; + } + + /** + * Compute the skining matrices for each bone of the skeleton that would be used to transform vertices of associated meshes + * @return + */ + public Matrix4f[] computeSkinningMatrices() { + TempVars vars = TempVars.get(); + for (int i = 0; i < boneList.length; i++) { + boneList[i].getOffsetTransform(skinningMatrixes[i], vars.quat1, vars.vect1, vars.vect2, vars.tempMat3); + } + vars.release(); + return skinningMatrixes; + } + + /** + * returns the number of bones of this skeleton + * @return + */ + public int getBoneCount() { + return boneList.length; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Skeleton - ").append(boneList.length).append(" bones, ").append(rootBones.length).append(" roots\n"); + for (Bone rootBone : rootBones) { + sb.append(rootBone.toString()); + } + return sb.toString(); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule input = im.getCapsule(this); + + Savable[] boneRootsAsSav = input.readSavableArray("rootBones", null); + rootBones = new Bone[boneRootsAsSav.length]; + System.arraycopy(boneRootsAsSav, 0, rootBones, 0, boneRootsAsSav.length); + + Savable[] boneListAsSavable = input.readSavableArray("boneList", null); + boneList = new Bone[boneListAsSavable.length]; + System.arraycopy(boneListAsSavable, 0, boneList, 0, boneListAsSavable.length); + + createSkinningMatrices(); + + for (Bone rootBone : rootBones) { + rootBone.update(); + rootBone.setBindingPose(); + } + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule output = ex.getCapsule(this); + output.write(rootBones, "rootBones", null); + output.write(boneList, "boneList", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java new file mode 100644 index 000000000..468330827 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java @@ -0,0 +1,734 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.*; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.RendererException; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.*; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.control.Control; +import com.jme3.shader.VarType; +import com.jme3.util.SafeArrayList; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The Skeleton control deforms a model according to a skeleton, It handles the + * computation of the deformation matrices and performs the transformations on + * the mesh + * + * @author Rémy Bouquet Based on AnimControl by Kirill Vainer + */ +public class SkeletonControl extends AbstractControl implements Cloneable { + + /** + * The skeleton of the model. + */ + private Skeleton skeleton; + /** + * List of targets which this controller effects. + */ + private SafeArrayList targets = new SafeArrayList(Mesh.class); + /** + * Used to track when a mesh was updated. Meshes are only updated if they + * are visible in at least one camera. + */ + private boolean wasMeshUpdated = false; + + /** + * User wishes to use hardware skinning if available. + */ + private transient boolean hwSkinningDesired = false; + + /** + * Hardware skinning is currently being used. + */ + private transient boolean hwSkinningEnabled = false; + + /** + * Hardware skinning was tested on this GPU, results + * are stored in {@link #hwSkinningSupported} variable. + */ + private transient boolean hwSkinningTested = false; + + /** + * If hardware skinning was {@link #hwSkinningTested tested}, then + * this variable will be set to true if supported, and false if otherwise. + */ + private transient boolean hwSkinningSupported = false; + + /** + * Bone offset matrices, recreated each frame + */ + private transient Matrix4f[] offsetMatrices; + /** + * Material references used for hardware skinning + */ + private Set materials = new HashSet(); + + /** + * Serialization only. Do not use. + */ + public SkeletonControl() { + } + + private void switchToHardware() { + // Next full 10 bones (e.g. 30 on 24 bones) + int numBones = ((skeleton.getBoneCount() / 10) + 1) * 10; + for (Material m : materials) { + m.setInt("NumberOfBones", numBones); + } + for (Mesh mesh : targets) { + if (mesh.isAnimated()) { + mesh.prepareForAnim(false); + } + } + } + + private void switchToSoftware() { + for (Material m : materials) { + if (m.getParam("NumberOfBones") != null) { + m.clearParam("NumberOfBones"); + } + } + for (Mesh mesh : targets) { + if (mesh.isAnimated()) { + mesh.prepareForAnim(true); + } + } + } + + private boolean testHardwareSupported(RenderManager rm) { + for (Material m : materials) { + // Some of the animated mesh(es) do not support hardware skinning, + // so it is not supported by the model. + if (m.getMaterialDef().getMaterialParam("NumberOfBones") == null) { + Logger.getLogger(SkeletonControl.class.getName()).log(Level.WARNING, + "Not using hardware skinning for {0}, " + + "because material {1} doesn''t support it.", + new Object[]{spatial, m.getMaterialDef().getName()}); + + return false; + } + } + + switchToHardware(); + + try { + rm.preloadScene(spatial); + return true; + } catch (RendererException e) { + Logger.getLogger(SkeletonControl.class.getName()).log(Level.WARNING, "Could not enable HW skinning due to shader compile error:", e); + return false; + } + } + + /** + * Specifies if hardware skinning is preferred. If it is preferred and + * supported by GPU, it shall be enabled, if its not preferred, or not + * supported by GPU, then it shall be disabled. + * + * @see #isHardwareSkinningUsed() + */ + public void setHardwareSkinningPreferred(boolean preferred) { + hwSkinningDesired = preferred; + } + + /** + * @return True if hardware skinning is preferable to software skinning. + * Set to false by default. + * + * @see #setHardwareSkinningPreferred(boolean) + */ + public boolean isHardwareSkinningPreferred() { + return hwSkinningDesired; + } + + /** + * @return True is hardware skinning is activated and is currently used, false otherwise. + */ + public boolean isHardwareSkinningUsed() { + return hwSkinningEnabled; + } + + /** + * Creates a skeleton control. The list of targets will be acquired + * automatically when the control is attached to a node. + * + * @param skeleton the skeleton + */ + public SkeletonControl(Skeleton skeleton) { + this.skeleton = skeleton; + } + + /** + * Creates a skeleton control. + * + * @param targets the meshes controlled by the skeleton + * @param skeleton the skeleton + */ + @Deprecated + SkeletonControl(Mesh[] targets, Skeleton skeleton) { + this.skeleton = skeleton; + this.targets = new SafeArrayList(Mesh.class, Arrays.asList(targets)); + } + + + private void findTargets(Node node) { + Mesh sharedMesh = null; + + for (Spatial child : node.getChildren()) { + if (child instanceof Geometry) { + Geometry geom = (Geometry) child; + + // is this geometry using a shared mesh? + Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH); + + if (childSharedMesh != null) { + // Don’t bother with non-animated shared meshes + if (childSharedMesh.isAnimated()) { + // child is using shared mesh, + // so animate the shared mesh but ignore child + if (sharedMesh == null) { + sharedMesh = childSharedMesh; + } else if (sharedMesh != childSharedMesh) { + throw new IllegalStateException("Two conflicting shared meshes for " + node); + } + materials.add(geom.getMaterial()); + } + } else { + Mesh mesh = geom.getMesh(); + if (mesh.isAnimated()) { + targets.add(mesh); + materials.add(geom.getMaterial()); + } + } + } else if (child instanceof Node) { + findTargets((Node) child); + } + } + + if (sharedMesh != null) { + targets.add(sharedMesh); + } + + } + + @Override + public void setSpatial(Spatial spatial) { + super.setSpatial(spatial); + updateTargetsAndMaterials(spatial); + } + + private void controlRenderSoftware() { + resetToBind(); // reset morph meshes to bind pose + + offsetMatrices = skeleton.computeSkinningMatrices(); + + for (Mesh mesh : targets) { + // NOTE: This assumes that code higher up + // Already ensured those targets are animated + // otherwise a crash will happen in skin update + softwareSkinUpdate(mesh, offsetMatrices); + } + } + + private void controlRenderHardware() { + offsetMatrices = skeleton.computeSkinningMatrices(); + for (Material m : materials) { + m.setParam("BoneMatrices", VarType.Matrix4Array, offsetMatrices); + } + } + + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + if (!wasMeshUpdated) { + updateTargetsAndMaterials(spatial); + + // Prevent illegal cases. These should never happen. + assert hwSkinningTested || (!hwSkinningTested && !hwSkinningSupported && !hwSkinningEnabled); + assert !hwSkinningEnabled || (hwSkinningEnabled && hwSkinningTested && hwSkinningSupported); + + if (hwSkinningDesired && !hwSkinningTested) { + hwSkinningTested = true; + hwSkinningSupported = testHardwareSupported(rm); + + if (hwSkinningSupported) { + hwSkinningEnabled = true; + + Logger.getLogger(SkeletonControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for " + spatial); + } else { + switchToSoftware(); + } + } else if (hwSkinningDesired && hwSkinningSupported && !hwSkinningEnabled) { + switchToHardware(); + hwSkinningEnabled = true; + } else if (!hwSkinningDesired && hwSkinningEnabled) { + switchToSoftware(); + hwSkinningEnabled = false; + } + + if (hwSkinningEnabled) { + controlRenderHardware(); + } else { + controlRenderSoftware(); + } + + wasMeshUpdated = true; + } + } + + @Override + protected void controlUpdate(float tpf) { + wasMeshUpdated = false; + } + + //only do this for software updates + void resetToBind() { + for (Mesh mesh : targets) { + if (mesh.isAnimated()) { + Buffer bwBuff = mesh.getBuffer(Type.BoneWeight).getData(); + Buffer biBuff = mesh.getBuffer(Type.BoneIndex).getData(); + if (!biBuff.hasArray() || !bwBuff.hasArray()) { + mesh.prepareForAnim(true); // prepare for software animation + } + VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition); + VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal); + VertexBuffer pos = mesh.getBuffer(Type.Position); + VertexBuffer norm = mesh.getBuffer(Type.Normal); + FloatBuffer pb = (FloatBuffer) pos.getData(); + FloatBuffer nb = (FloatBuffer) norm.getData(); + FloatBuffer bpb = (FloatBuffer) bindPos.getData(); + FloatBuffer bnb = (FloatBuffer) bindNorm.getData(); + pb.clear(); + nb.clear(); + bpb.clear(); + bnb.clear(); + + //reseting bind tangents if there is a bind tangent buffer + VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent); + if (bindTangents != null) { + VertexBuffer tangents = mesh.getBuffer(Type.Tangent); + FloatBuffer tb = (FloatBuffer) tangents.getData(); + FloatBuffer btb = (FloatBuffer) bindTangents.getData(); + tb.clear(); + btb.clear(); + tb.put(btb).clear(); + } + + + pb.put(bpb).clear(); + nb.put(bnb).clear(); + } + } + } + + public Control cloneForSpatial(Spatial spatial) { + Node clonedNode = (Node) spatial; + AnimControl ctrl = spatial.getControl(AnimControl.class); + SkeletonControl clone = new SkeletonControl(); + + clone.skeleton = ctrl.getSkeleton(); + + clone.setSpatial(clonedNode); + + // Fix attachments for the cloned node + for (int i = 0; i < clonedNode.getQuantity(); i++) { + // go through attachment nodes, apply them to correct bone + Spatial child = clonedNode.getChild(i); + if (child instanceof Node) { + Node clonedAttachNode = (Node) child; + Bone originalBone = (Bone) clonedAttachNode.getUserData("AttachedBone"); + + if (originalBone != null) { + Bone clonedBone = clone.skeleton.getBone(originalBone.getName()); + + clonedAttachNode.setUserData("AttachedBone", clonedBone); + clonedBone.setAttachmentsNode(clonedAttachNode); + } + } + } + + return clone; + } + + /** + * + * @param boneName the name of the bone + * @return the node attached to this bone + */ + public Node getAttachmentsNode(String boneName) { + Bone b = skeleton.getBone(boneName); + if (b == null) { + throw new IllegalArgumentException("Given bone name does not exist " + + "in the skeleton."); + } + + Node n = b.getAttachmentsNode(); + Node model = (Node) spatial; + model.attachChild(n); + return n; + } + + /** + * returns the skeleton of this control + * + * @return + */ + public Skeleton getSkeleton() { + return skeleton; + } + + /** + * returns a copy of array of the targets meshes of this control + * + * @return + */ + public Mesh[] getTargets() { + return targets.toArray(new Mesh[targets.size()]); + } + + /** + * Update the mesh according to the given transformation matrices + * + * @param mesh then mesh + * @param offsetMatrices the transformation matrices to apply + */ + private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) { + + VertexBuffer tb = mesh.getBuffer(Type.Tangent); + if (tb == null) { + //if there are no tangents use the classic skinning + applySkinning(mesh, offsetMatrices); + } else { + //if there are tangents use the skinning with tangents + applySkinningTangents(mesh, offsetMatrices, tb); + } + + + } + + /** + * Method to apply skinning transforms to a mesh's buffers + * + * @param mesh the mesh + * @param offsetMatrices the offset matices to apply + */ + private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) { + int maxWeightsPerVert = mesh.getMaxNumWeights(); + if (maxWeightsPerVert <= 0) { + throw new IllegalStateException("Max weights per vert is incorrectly set!"); + } + + int fourMinusMaxWeights = 4 - maxWeightsPerVert; + + // NOTE: This code assumes the vertex buffer is in bind pose + // resetToBind() has been called this frame + VertexBuffer vb = mesh.getBuffer(Type.Position); + FloatBuffer fvb = (FloatBuffer) vb.getData(); + fvb.rewind(); + + VertexBuffer nb = mesh.getBuffer(Type.Normal); + FloatBuffer fnb = (FloatBuffer) nb.getData(); + fnb.rewind(); + + // get boneIndexes and weights for mesh + ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData(); + FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); + + ib.rewind(); + wb.rewind(); + + float[] weights = wb.array(); + byte[] indices = ib.array(); + int idxWeights = 0; + + TempVars vars = TempVars.get(); + + float[] posBuf = vars.skinPositions; + float[] normBuf = vars.skinNormals; + + int iterations = (int) FastMath.ceil(fvb.limit() / ((float) posBuf.length)); + int bufLength = posBuf.length; + for (int i = iterations - 1; i >= 0; i--) { + // read next set of positions and normals from native buffer + bufLength = Math.min(posBuf.length, fvb.remaining()); + fvb.get(posBuf, 0, bufLength); + fnb.get(normBuf, 0, bufLength); + int verts = bufLength / 3; + int idxPositions = 0; + + // iterate vertices and apply skinning transform for each effecting bone + for (int vert = verts - 1; vert >= 0; vert--) { + // Skip this vertex if the first weight is zero. + if (weights[idxWeights] == 0) { + idxPositions += 3; + idxWeights += 4; + continue; + } + + 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++] & 0xff]; + + rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight; + ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight; + rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight; + + rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight; + rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight; + rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight; + } + + idxWeights += fourMinusMaxWeights; + + idxPositions -= 3; + normBuf[idxPositions] = rnx; + posBuf[idxPositions++] = rx; + normBuf[idxPositions] = rny; + posBuf[idxPositions++] = ry; + normBuf[idxPositions] = rnz; + posBuf[idxPositions++] = rz; + } + + fvb.position(fvb.position() - bufLength); + fvb.put(posBuf, 0, bufLength); + fnb.position(fnb.position() - bufLength); + fnb.put(normBuf, 0, bufLength); + } + + vars.release(); + + vb.updateData(fvb); + nb.updateData(fnb); + + } + + /** + * Specific method for skinning with tangents to avoid cluttering the + * classic skinning calculation with null checks that would slow down the + * process even if tangents don't have to be computed. Also the iteration + * has additional indexes since tangent has 4 components instead of 3 for + * pos and norm + * + * @param maxWeightsPerVert maximum number of weights per vertex + * @param mesh the mesh + * @param offsetMatrices the offsetMaytrices to apply + * @param tb the tangent vertexBuffer + */ + private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) { + int maxWeightsPerVert = mesh.getMaxNumWeights(); + + if (maxWeightsPerVert <= 0) { + throw new IllegalStateException("Max weights per vert is incorrectly set!"); + } + + int fourMinusMaxWeights = 4 - maxWeightsPerVert; + + // NOTE: This code assumes the vertex buffer is in bind pose + // resetToBind() has been called this frame + VertexBuffer vb = mesh.getBuffer(Type.Position); + FloatBuffer fvb = (FloatBuffer) vb.getData(); + fvb.rewind(); + + VertexBuffer nb = mesh.getBuffer(Type.Normal); + + FloatBuffer fnb = (FloatBuffer) nb.getData(); + fnb.rewind(); + + + FloatBuffer ftb = (FloatBuffer) tb.getData(); + ftb.rewind(); + + + // get boneIndexes and weights for mesh + ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData(); + FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); + + ib.rewind(); + wb.rewind(); + + float[] weights = wb.array(); + byte[] indices = ib.array(); + int idxWeights = 0; + + TempVars vars = TempVars.get(); + + + float[] posBuf = vars.skinPositions; + float[] normBuf = vars.skinNormals; + float[] tanBuf = vars.skinTangents; + + int iterations = (int) FastMath.ceil(fvb.limit() / ((float) posBuf.length)); + int bufLength = 0; + int tanLength = 0; + for (int i = iterations - 1; i >= 0; i--) { + // read next set of positions and normals from native buffer + bufLength = Math.min(posBuf.length, fvb.remaining()); + tanLength = Math.min(tanBuf.length, ftb.remaining()); + fvb.get(posBuf, 0, bufLength); + fnb.get(normBuf, 0, bufLength); + ftb.get(tanBuf, 0, tanLength); + int verts = bufLength / 3; + int idxPositions = 0; + //tangents has their own index because of the 4 components + int idxTangents = 0; + + // iterate vertices and apply skinning transform for each effecting bone + for (int vert = verts - 1; vert >= 0; vert--) { + // Skip this vertex if the first weight is zero. + if (weights[idxWeights] == 0) { + idxTangents += 4; + idxPositions += 3; + idxWeights += 4; + continue; + } + + float nmx = normBuf[idxPositions]; + float vtx = posBuf[idxPositions++]; + float nmy = normBuf[idxPositions]; + float vty = posBuf[idxPositions++]; + float nmz = normBuf[idxPositions]; + float vtz = posBuf[idxPositions++]; + + float tnx = tanBuf[idxTangents++]; + float tny = tanBuf[idxTangents++]; + float tnz = tanBuf[idxTangents++]; + + // skipping the 4th component of the tangent since it doesn't have to be transformed + idxTangents++; + + float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0; + + for (int w = maxWeightsPerVert - 1; w >= 0; w--) { + float weight = weights[idxWeights]; + Matrix4f mat = offsetMatrices[indices[idxWeights++]]; + + rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight; + ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight; + rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight; + + rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight; + rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight; + rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight; + + rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight; + rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight; + rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight; + } + + idxWeights += fourMinusMaxWeights; + + idxPositions -= 3; + + normBuf[idxPositions] = rnx; + posBuf[idxPositions++] = rx; + normBuf[idxPositions] = rny; + posBuf[idxPositions++] = ry; + normBuf[idxPositions] = rnz; + posBuf[idxPositions++] = rz; + + idxTangents -= 4; + + tanBuf[idxTangents++] = rtx; + tanBuf[idxTangents++] = rty; + tanBuf[idxTangents++] = rtz; + + //once again skipping the 4th component of the tangent + idxTangents++; + } + + fvb.position(fvb.position() - bufLength); + fvb.put(posBuf, 0, bufLength); + fnb.position(fnb.position() - bufLength); + fnb.put(normBuf, 0, bufLength); + ftb.position(ftb.position() - tanLength); + ftb.put(tanBuf, 0, tanLength); + } + + vars.release(); + + vb.updateData(fvb); + nb.updateData(fnb); + tb.updateData(ftb); + + + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(skeleton, "skeleton", null); + //Targets and materials don't need to be saved, they'll be gathered on each frame + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + skeleton = (Skeleton) in.readSavable("skeleton", null); + } + + private void updateTargetsAndMaterials(Spatial spatial) { + targets.clear(); + materials.clear(); + if (spatial != null && spatial instanceof Node) { + Node node = (Node) spatial; + findTargets(node); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/SpatialAnimation.java b/jme3-core/src/main/java/com/jme3/animation/SpatialAnimation.java new file mode 100644 index 000000000..4a552bba6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/SpatialAnimation.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +/** + * @deprecated use Animation instead with tracks of selected type (ie. BoneTrack, SpatialTrack, MeshTrack) + */ +@Deprecated +public class SpatialAnimation extends Animation { + public SpatialAnimation(String name, float length) { + super(name, length); + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java b/jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java new file mode 100644 index 000000000..ba715b6f8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.Arrays; + +/** + * This class represents the track for spatial animation. + * + * @author Marcin Roguski (Kaelthas) + */ +public class SpatialTrack implements Track { + + /** + * Translations of the track. + */ + private CompactVector3Array translations; + + /** + * Rotations of the track. + */ + private CompactQuaternionArray rotations; + + /** + * Scales of the track. + */ + private CompactVector3Array scales; + + /** + * The times of the animations frames. + */ + private float[] times; + + public SpatialTrack() { + } + + /** + * Creates a spatial track for the given track data. + * + * @param times + * a float array with the time of each frame + * @param translations + * the translation of the bone for each frame + * @param rotations + * the rotation of the bone for each frame + * @param scales + * the scale of the bone for each frame + */ + public SpatialTrack(float[] times, Vector3f[] translations, + Quaternion[] rotations, Vector3f[] scales) { + setKeyframes(times, translations, rotations, scales); + } + + /** + * + * Modify the spatial which this track modifies. + * + * @param time + * the current time of the animation + */ + public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { + Spatial spatial = control.getSpatial(); + + Vector3f tempV = vars.vect1; + Vector3f tempS = vars.vect2; + Quaternion tempQ = vars.quat1; + Vector3f tempV2 = vars.vect3; + Vector3f tempS2 = vars.vect4; + Quaternion tempQ2 = vars.quat2; + + int lastFrame = times.length - 1; + if (time < 0 || lastFrame == 0) { + if (rotations != null) + rotations.get(0, tempQ); + if (translations != null) + translations.get(0, tempV); + if (scales != null) { + scales.get(0, tempS); + } + } else if (time >= times[lastFrame]) { + if (rotations != null) + rotations.get(lastFrame, tempQ); + if (translations != null) + translations.get(lastFrame, tempV); + if (scales != null) { + scales.get(lastFrame, tempS); + } + } else { + int startFrame = 0; + int endFrame = 1; + // use lastFrame so we never overflow the array + for (int i = 0; i < lastFrame && times[i] < time; ++i) { + startFrame = i; + endFrame = i + 1; + } + + float blend = (time - times[startFrame]) / (times[endFrame] - times[startFrame]); + + if (rotations != null) + rotations.get(startFrame, tempQ); + if (translations != null) + translations.get(startFrame, tempV); + if (scales != null) { + scales.get(startFrame, tempS); + } + if (rotations != null) + rotations.get(endFrame, tempQ2); + if (translations != null) + translations.get(endFrame, tempV2); + if (scales != null) { + scales.get(endFrame, tempS2); + } + tempQ.nlerp(tempQ2, blend); + tempV.interpolateLocal(tempV2, blend); + tempS.interpolateLocal(tempS2, blend); + } + + if (translations != null) + spatial.setLocalTranslation(tempV); + if (rotations != null) + spatial.setLocalRotation(tempQ); + if (scales != null) { + spatial.setLocalScale(tempS); + } + } + + /** + * Set the translations, rotations and scales for this track. + * + * @param times + * a float array with the time of each frame + * @param translations + * the translation of the bone for each frame + * @param rotations + * the rotation of the bone for each frame + * @param scales + * the scale of the bone for each frame + */ + public void setKeyframes(float[] times, Vector3f[] translations, + Quaternion[] rotations, Vector3f[] scales) { + if (times.length == 0) { + throw new RuntimeException("BoneTrack with no keyframes!"); + } + + this.times = times; + if (translations != null) { + assert times.length == translations.length; + this.translations = new CompactVector3Array(); + this.translations.add(translations); + this.translations.freeze(); + } + if (rotations != null) { + assert times.length == rotations.length; + this.rotations = new CompactQuaternionArray(); + this.rotations.add(rotations); + this.rotations.freeze(); + } + if (scales != null) { + assert times.length == scales.length; + this.scales = new CompactVector3Array(); + this.scales.add(scales); + this.scales.freeze(); + } + } + + /** + * @return the array of rotations of this track + */ + public Quaternion[] getRotations() { + return rotations == null ? null : rotations.toObjectArray(); + } + + /** + * @return the array of scales for this track + */ + public Vector3f[] getScales() { + return scales == null ? null : scales.toObjectArray(); + } + + /** + * @return the arrays of time for this track + */ + public float[] getTimes() { + return times; + } + + /** + * @return the array of translations of this track + */ + public Vector3f[] getTranslations() { + return translations == null ? null : translations.toObjectArray(); + } + + /** + * @return the length of the track + */ + public float getLength() { + return times == null ? 0 : times[times.length - 1] - times[0]; + } + + /** + * This method creates a clone of the current object. + * @return a clone of the current object + */ + @Override + public SpatialTrack clone() { + int tablesLength = times.length; + + float[] timesCopy = this.times.clone(); + Vector3f[] translationsCopy = this.getTranslations() == null ? null : Arrays.copyOf(this.getTranslations(), tablesLength); + Quaternion[] rotationsCopy = this.getRotations() == null ? null : Arrays.copyOf(this.getRotations(), tablesLength); + Vector3f[] scalesCopy = this.getScales() == null ? null : Arrays.copyOf(this.getScales(), tablesLength); + + //need to use the constructor here because of the final fields used in this class + return new SpatialTrack(timesCopy, translationsCopy, rotationsCopy, scalesCopy); + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(translations, "translations", null); + oc.write(rotations, "rotations", null); + oc.write(times, "times", null); + oc.write(scales, "scales", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + translations = (CompactVector3Array) ic.readSavable("translations", null); + rotations = (CompactQuaternionArray) ic.readSavable("rotations", null); + times = ic.readFloatArray("times", null); + scales = (CompactVector3Array) ic.readSavable("scales", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/Track.java b/jme3-core/src/main/java/com/jme3/animation/Track.java new file mode 100644 index 000000000..e77d67f14 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/Track.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.Savable; +import com.jme3.util.TempVars; + +public interface Track extends Savable, Cloneable { + + /** + * Sets the time of the animation. + * + * Internally, the track will retrieve objects from the control + * and modify them according to the properties of the channel and the + * given parameters. + * + * @param time The time in the animation + * @param weight The weight from 0 to 1 on how much to apply the track + * @param control The control which the track should effect + * @param channel The channel which the track should effect + */ + public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars); + + /** + * @return the length of the track + */ + public float getLength(); + + /** + * This method creates a clone of the current object. + * @return a clone of the current object + */ + public Track clone(); +} diff --git a/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java b/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java new file mode 100644 index 000000000..b6bff101a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.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; + +/** + * This class is intended as a UserData added to a Spatial that is referenced by a Track. + * (ParticleEmitter for EffectTrack and AudioNode for AudioTrack) + * It holds the list of tracks that are directly referencing the Spatial. + * + * This is used when loading a Track to find the cloned reference of a Spatial in the cloned model returned by the assetManager. + * + * @author Nehon + */ +public class TrackInfo implements Savable { + + ArrayList tracks = new ArrayList(); + + public TrackInfo() { + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule c = ex.getCapsule(this); + c.writeSavableArrayList(tracks, "tracks", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule c = im.getCapsule(this); + tracks = c.readSavableArrayList("tracks", null); + } + + public ArrayList getTracks() { + return tracks; + } + + public void addTrack(Track track) { + tracks.add(track); + } +} diff --git a/jme3-core/src/main/java/com/jme3/animation/package.html b/jme3-core/src/main/java/com/jme3/animation/package.html new file mode 100644 index 000000000..a69904f3b --- /dev/null +++ b/jme3-core/src/main/java/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, com.jme3.math.Vector3f) }. +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/jme3-core/src/main/java/com/jme3/app/AppTask.java b/jme3-core/src/main/java/com/jme3/app/AppTask.java new file mode 100644 index 000000000..183088581 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/AppTask.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import java.util.concurrent.*; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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(); + + /** + * Create an AppTask that will execute the given + * {@link Callable}. + * + * @param callable The callable to be executed + */ + public AppTask(Callable callable) { + this.callable = callable; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + stateLock.lock(); + try { + if (result != null) { + return false; + } + cancelled = true; + + finishedCondition.signalAll(); + + return true; + } finally { + stateLock.unlock(); + } + } + + public V get() throws InterruptedException, ExecutionException { + stateLock.lock(); + try { + while (!isDone()) { + finishedCondition.await(); + } + if (exception != null) { + throw exception; + } + return result; + } finally { + stateLock.unlock(); + } + } + + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + stateLock.lock(); + try { + if (!isDone()) { + finishedCondition.await(timeout, unit); + } + if (exception != null) { + throw exception; + } + if (result == null) { + throw new TimeoutException("Object not returned in time allocated."); + } + return result; + } finally { + stateLock.unlock(); + } + } + + public boolean isCancelled() { + stateLock.lock(); + try { + return cancelled; + } finally { + stateLock.unlock(); + } + } + + public boolean isDone() { + stateLock.lock(); + try { + return finished || cancelled || (exception != null); + } finally { + stateLock.unlock(); + } + } + + public Callable 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/jme3-core/src/main/java/com/jme3/app/Application.java b/jme3-core/src/main/java/com/jme3/app/Application.java new file mode 100644 index 000000000..e34cc0ba0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/Application.java @@ -0,0 +1,656 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetManager; +import com.jme3.audio.AudioContext; +import com.jme3.audio.AudioRenderer; +import com.jme3.audio.Listener; +import com.jme3.input.*; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.system.*; +import com.jme3.system.JmeContext.Type; +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 NOT EXTEND* this class but extend {@link com.jme3.app.SimpleApplication} instead. + * + */ +public class Application implements SystemListener { + + private static final Logger logger = Logger.getLogger(Application.class.getName()); + + protected AssetManager assetManager; + + protected AudioRenderer audioRenderer; + protected Renderer renderer; + protected RenderManager renderManager; + protected ViewPort viewPort; + protected ViewPort guiViewPort; + + protected JmeContext context; + protected AppSettings settings; + protected Timer timer = new NanoTimer(); + protected Camera cam; + protected Listener listener; + + protected boolean inputEnabled = true; + protected boolean pauseOnFocus = true; + protected float speed = 1f; + protected boolean paused = false; + protected MouseInput mouseInput; + protected KeyInput keyInput; + protected JoyInput joyInput; + protected TouchInput touchInput; + protected InputManager inputManager; + protected AppStateManager stateManager; + + private final ConcurrentLinkedQueue> taskQueue = new ConcurrentLinkedQueue>(); + + /** + * Create a new instance of Application. + */ + public Application(){ + initStateManager(); + } + + /** + * Returns true if pause on lost focus is enabled, false otherwise. + * + * @return true if pause on lost focus is enabled + * + * @see #setPauseOnLostFocus(boolean) + */ + public boolean isPauseOnLostFocus() { + return pauseOnFocus; + } + + /** + * Enable or disable pause on lost focus. + *

    + * By default, pause on lost focus is enabled. + * If enabled, the application will stop updating + * when it loses focus or becomes inactive (e.g. alt-tab). + * For online or real-time applications, this might not be preferable, + * so this feature should be set to disabled. For other applications, + * it is best to keep it on so that CPU usage is not used when + * not necessary. + * + * @param pauseOnLostFocus True to enable pause on lost focus, false + * otherwise. + */ + public void setPauseOnLostFocus(boolean pauseOnLostFocus) { + this.pauseOnFocus = pauseOnLostFocus; + } + + @Deprecated + public void setAssetManager(AssetManager assetManager){ + if (this.assetManager != null) + throw new IllegalStateException("Can only set asset manager" + + " before initialization."); + + this.assetManager = assetManager; + } + + private void initAssetManager(){ + if (settings != null){ + String assetCfg = settings.getString("AssetConfigURL"); + if (assetCfg != null){ + URL url = null; + try { + url = new URL(assetCfg); + } catch (MalformedURLException ex) { + } + if (url == null) { + url = Application.class.getClassLoader().getResource(assetCfg); + if (url == null) { + logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg); + return; + } + } + assetManager = JmeSystem.newAssetManager(url); + } + } + if (assetManager == null){ + assetManager = JmeSystem.newAssetManager( + Thread.currentThread().getContextClassLoader() + .getResource("com/jme3/asset/Desktop.cfg")); + } + } + + /** + * Set the display settings to define the display created. + *

    + * Examples of display parameters include display pixel width and height, + * color bit depth, z-buffer bits, anti-aliasing samples, and update frequency. + * If this method is called while the application is already running, then + * {@link #restart() } must be called to apply the settings to the display. + * + * @param settings The settings to set. + */ + public void setSettings(AppSettings settings){ + this.settings = settings; + if (context != null && settings.useInput() != inputEnabled){ + // may need to create or destroy input based + // on settings change + inputEnabled = !inputEnabled; + if (inputEnabled){ + initInput(); + }else{ + destroyInput(); + } + }else{ + inputEnabled = settings.useInput(); + } + } + + /** + * Sets the Timer implementation that will be used for calculating + * frame times. By default, Application will use the Timer as returned + * by the current JmeContext implementation. + */ + public void setTimer(Timer timer){ + this.timer = timer; + + if (timer != null) { + timer.reset(); + } + + if (renderManager != null) { + renderManager.setTimer(timer); + } + } + + public Timer getTimer(){ + return timer; + } + + private void initDisplay(){ + // aquire important objects + // from the context + settings = context.getSettings(); + + // Only reset the timer if a user has not already provided one + if (timer == null) { + timer = context.getTimer(); + } + + renderer = context.getRenderer(); + } + + private void initAudio(){ + if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){ + audioRenderer = JmeSystem.newAudioRenderer(settings); + audioRenderer.initialize(); + AudioContext.setAudioRenderer(audioRenderer); + + listener = new Listener(); + audioRenderer.setListener(listener); + } + } + + /** + * Creates the camera to use for rendering. Default values are perspective + * projection with 45° field of view, with near and far values 1 and 1000 + * units respectively. + */ + private void initCamera(){ + cam = new Camera(settings.getWidth(), settings.getHeight()); + + cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f); + cam.setLocation(new Vector3f(0f, 0f, 10f)); + cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y); + + renderManager = new RenderManager(renderer); + //Remy - 09/14/2010 setted the timer in the renderManager + renderManager.setTimer(timer); + viewPort = renderManager.createMainView("Default", cam); + viewPort.setClearFlags(true, true, true); + + // Create a new cam for the gui + Camera guiCam = new Camera(settings.getWidth(), settings.getHeight()); + guiViewPort = renderManager.createPostView("Gui Default", guiCam); + guiViewPort.setClearFlags(false, false, false); + } + + /** + * Initializes mouse and keyboard input. Also + * initializes joystick input if joysticks are enabled in the + * AppSettings. + */ + private void initInput(){ + mouseInput = context.getMouseInput(); + if (mouseInput != null) + mouseInput.initialize(); + + keyInput = context.getKeyInput(); + if (keyInput != null) + keyInput.initialize(); + + touchInput = context.getTouchInput(); + if (touchInput != null) + touchInput.initialize(); + + if (!settings.getBoolean("DisableJoysticks")){ + joyInput = context.getJoyInput(); + if (joyInput != null) + joyInput.initialize(); + } + + inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput); + } + + private void initStateManager(){ + stateManager = new AppStateManager(this); + + // Always register a ResetStatsState to make sure + // that the stats are cleared every frame + stateManager.attach(new ResetStatsState()); + } + + /** + * @return The {@link AssetManager asset manager} for this application. + */ + public AssetManager getAssetManager(){ + return assetManager; + } + + /** + * @return the {@link InputManager input manager}. + */ + public InputManager getInputManager(){ + return inputManager; + } + + /** + * @return the {@link AppStateManager app state manager} + */ + public AppStateManager getStateManager() { + return stateManager; + } + + /** + * @return the {@link RenderManager render manager} + */ + public RenderManager getRenderManager() { + return renderManager; + } + + /** + * @return The {@link Renderer renderer} for the application + */ + public Renderer getRenderer(){ + return renderer; + } + + /** + * @return The {@link AudioRenderer audio renderer} for the application + */ + public AudioRenderer getAudioRenderer() { + return audioRenderer; + } + + /** + * @return The {@link Listener listener} object for audio + */ + public Listener getListener() { + return listener; + } + + /** + * @return The {@link JmeContext display context} for the application + */ + public JmeContext getContext(){ + return context; + } + + /** + * @return The {@link Camera camera} for the application + */ + public Camera getCamera(){ + return cam; + } + + /** + * Starts the application in {@link Type#Display display} mode. + * + * @see #start(com.jme3.system.JmeContext.Type) + */ + public void start(){ + start(JmeContext.Type.Display); + } + + /** + * Starts the application. + * Creating a rendering context and executing + * the main loop in a separate thread. + */ + public void start(JmeContext.Type contextType){ + if (context != null && context.isCreated()){ + logger.warning("start() called when application already created!"); + return; + } + + if (settings == null){ + settings = new AppSettings(true); + } + + logger.log(Level.FINE, "Starting application: {0}", getClass().getName()); + context = JmeSystem.newContext(settings, contextType); + context.setSystemListener(this); + context.create(false); + } + + /** + * Initializes the application's canvas for use. + *

    + * After calling this method, cast the {@link #getContext() context} to + * {@link JmeCanvasContext}, + * then acquire the canvas with {@link JmeCanvasContext#getCanvas() } + * and attach it to an AWT/Swing Frame. + * The rendering thread will start when the canvas becomes visible on + * screen, however if you wish to start the context immediately you + * may call {@link #startCanvas() } to force the rendering thread + * to start. + * + * @see JmeCanvasContext + * @see Type#Canvas + */ + public void createCanvas(){ + if (context != null && context.isCreated()){ + logger.warning("createCanvas() called when application already created!"); + return; + } + + if (settings == null){ + settings = new AppSettings(true); + } + + logger.log(Level.FINE, "Starting application: {0}", getClass().getName()); + context = JmeSystem.newContext(settings, JmeContext.Type.Canvas); + context.setSystemListener(this); + } + + /** + * Starts the rendering thread after createCanvas() has been called. + *

    + * Same as calling startCanvas(false) + * + * @see #startCanvas(boolean) + */ + public void startCanvas(){ + startCanvas(false); + } + + /** + * Starts the rendering thread after createCanvas() has been called. + *

    + * Calling this method is optional, the canvas will start automatically + * when it becomes visible. + * + * @param waitFor If true, the current thread will block until the + * rendering thread is running + */ + public void startCanvas(boolean waitFor){ + context.create(waitFor); + } + + /** + * Internal use only. + */ + public void reshape(int w, int h){ + renderManager.notifyReshape(w, h); + } + + /** + * Restarts the context, applying any changed settings. + *

    + * Changes to the {@link AppSettings} of this Application are not + * applied immediately; calling this method forces the context + * to restart, applying the new settings. + */ + public void restart(){ + context.setSettings(settings); + context.restart(); + } + + /** + * Requests the context to close, shutting down the main loop + * and making necessary cleanup operations. + * + * Same as calling stop(false) + * + * @see #stop(boolean) + */ + public void stop(){ + stop(false); + } + + /** + * Requests the context to close, shutting down the main loop + * and making necessary cleanup operations. + * After the application has stopped, it cannot be used anymore. + */ + public void stop(boolean waitFor){ + logger.log(Level.FINE, "Closing application: {0}", getClass().getName()); + context.destroy(waitFor); + } + + /** + * Do not call manually. + * Callback from ContextListener. + *

    + * 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(); + + // update timer so that the next delta is not too large +// timer.update(); + timer.reset(); + + // user code here.. + } + + /** + * Internal use only. + */ + public void handleError(String errMsg, Throwable t){ + // Print error to log. + logger.log(Level.SEVERE, errMsg, t); + // Display error message on screen + if (t != null) { + JmeSystem.showErrorDialog(errMsg + "\n" + t.getClass().getSimpleName() + + (t.getMessage() != null ? ": " + t.getMessage() : "")); + } else { + JmeSystem.showErrorDialog(errMsg); + } + + stop(); // stop the application + } + + /** + * Internal use only. + */ + public void gainFocus(){ + if (pauseOnFocus) { + paused = false; + context.setAutoFlushFrames(true); + if (inputManager != null) { + inputManager.reset(); + } + } + } + + /** + * Internal use only. + */ + public void loseFocus(){ + if (pauseOnFocus){ + paused = true; + context.setAutoFlushFrames(false); + } + } + + /** + * Internal use only. + */ + public void requestClose(boolean esc){ + context.destroy(false); + } + + /** + * Enqueues a task/callable object to execute in the jME3 + * rendering thread. + *

    + * Callables are executed right at the beginning of the main loop. + * They are executed even if the application is currently paused + * or out of focus. + */ + public Future enqueue(Callable callable) { + AppTask task = new AppTask(callable); + taskQueue.add(task); + return task; + } + + /** + * Runs tasks enqueued via {@link #enqueue(Callable)} + */ + protected void runQueuedTasks() { + AppTask task; + while( (task = taskQueue.poll()) != null ) { + if (!task.isCancelled()) { + task.invoke(); + } + } + } + + /** + * Do not call manually. + * Callback from ContextListener. + */ + public void update(){ + // Make sure the audio renderer is available to callables + AudioContext.setAudioRenderer(audioRenderer); + + runQueuedTasks(); + + if (speed == 0 || paused) + return; + + timer.update(); + + if (inputEnabled){ + inputManager.update(timer.getTimePerFrame()); + } + + if (audioRenderer != null){ + audioRenderer.update(timer.getTimePerFrame()); + } + + // user code here.. + } + + protected void destroyInput(){ + if (mouseInput != null) + mouseInput.destroy(); + + if (keyInput != null) + keyInput.destroy(); + + if (joyInput != null) + joyInput.destroy(); + + if (touchInput != null) + touchInput.destroy(); + + inputManager = null; + } + + /** + * Do not call manually. + * Callback from ContextListener. + */ + public void destroy(){ + stateManager.cleanup(); + + destroyInput(); + if (audioRenderer != null) + audioRenderer.cleanup(); + + timer.reset(); + } + + /** + * @return The GUI viewport. Which is used for the on screen + * statistics and FPS. + */ + public ViewPort getGuiViewPort() { + return guiViewPort; + } + + public ViewPort getViewPort() { + return viewPort; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/app/DebugKeysAppState.java b/jme3-core/src/main/java/com/jme3/app/DebugKeysAppState.java new file mode 100644 index 000000000..8e7e3f943 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/DebugKeysAppState.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.input.InputManager; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.util.BufferUtils; + + +/** + * Registers a few keys that will dump debug information + * to the console. + * + * @author Paul Speed + */ +public class DebugKeysAppState extends AbstractAppState { + + public static final String INPUT_MAPPING_CAMERA_POS = "SIMPLEAPP_CameraPos"; + public static final String INPUT_MAPPING_MEMORY = "SIMPLEAPP_Memory"; + + private Application app; + private DebugKeyListener keyListener = new DebugKeyListener(); + private InputManager inputManager; + + public DebugKeysAppState() { + } + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + + this.app = app; + this.inputManager = app.getInputManager(); + + if (app.getInputManager() != null) { + + inputManager.addMapping(INPUT_MAPPING_CAMERA_POS, new KeyTrigger(KeyInput.KEY_C)); + inputManager.addMapping(INPUT_MAPPING_MEMORY, new KeyTrigger(KeyInput.KEY_M)); + + inputManager.addListener(keyListener, + INPUT_MAPPING_CAMERA_POS, + INPUT_MAPPING_MEMORY); + } + } + + @Override + public void cleanup() { + super.cleanup(); + + if (inputManager.hasMapping(INPUT_MAPPING_CAMERA_POS)) + inputManager.deleteMapping(INPUT_MAPPING_CAMERA_POS); + if (inputManager.hasMapping(INPUT_MAPPING_MEMORY)) + inputManager.deleteMapping(INPUT_MAPPING_MEMORY); + + inputManager.removeListener(keyListener); + } + + + private class DebugKeyListener implements ActionListener { + + public void onAction(String name, boolean value, float tpf) { + if (!value) { + return; + } + + if (name.equals(INPUT_MAPPING_CAMERA_POS)) { + Camera cam = app.getCamera(); + if (cam != null) { + Vector3f loc = cam.getLocation(); + Quaternion rot = cam.getRotation(); + System.out.println("Camera Position: (" + + loc.x + ", " + loc.y + ", " + loc.z + ")"); + System.out.println("Camera Rotation: " + rot); + System.out.println("Camera Direction: " + cam.getDirection()); + System.out.println("cam.setLocation(new Vector3f(" + + loc.x + "f, " + loc.y + "f, " + loc.z + "f));"); + System.out.println("cam.setRotation(new Quaternion(" + rot.getX() + "f, " +rot.getY()+ "f, " + rot.getZ() + "f, " + rot.getW() + "f));"); + + } + } else if (name.equals(INPUT_MAPPING_MEMORY)) { + BufferUtils.printCurrentDirectMemory(null); + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/app/FlyCamAppState.java b/jme3-core/src/main/java/com/jme3/app/FlyCamAppState.java new file mode 100644 index 000000000..11d3e3fa2 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/FlyCamAppState.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.input.FlyByCamera; + + +/** + * Manages a FlyByCamera. + * + * @author Paul Speed + */ +public class FlyCamAppState extends AbstractAppState { + + private Application app; + private FlyByCamera flyCam; + + public FlyCamAppState() { + } + + /** + * This is called by SimpleApplication during initialize(). + */ + void setCamera( FlyByCamera cam ) { + this.flyCam = cam; + } + + public FlyByCamera getCamera() { + return flyCam; + } + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + + this.app = app; + + if (app.getInputManager() != null) { + + if (flyCam == null) { + flyCam = new FlyByCamera(app.getCamera()); + } + + flyCam.registerWithInput(app.getInputManager()); + } + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + + flyCam.setEnabled(enabled); + } + + @Override + public void cleanup() { + super.cleanup(); + + if (app.getInputManager() != null) { + flyCam.unregisterInput(); + } + } + + +} diff --git a/jme3-core/src/main/java/com/jme3/app/ResetStatsState.java b/jme3-core/src/main/java/com/jme3/app/ResetStatsState.java new file mode 100644 index 000000000..03f301326 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/ResetStatsState.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.app.state.AbstractAppState; +import com.jme3.renderer.RenderManager; + + +/** + * Resets (clearFrame()) the render's stats object every frame + * during AppState.render(). This state is registered once + * with Application to ensure that the stats are cleared once + * a frame. Using this makes sure that any Appliction based + * application that properly runs its state manager will have + * stats reset no matter how many views it has or if it even + * has views. + * + * @author Paul Speed + */ +public class ResetStatsState extends AbstractAppState { + + public ResetStatsState() { + } + + @Override + public void render(RenderManager rm) { + super.render(rm); + rm.getRenderer().getStatistics().clearFrame(); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java b/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java new file mode 100644 index 000000000..3a746b32c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.app.state.AppState; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.input.FlyByCamera; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.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; + +/** + * SimpleApplication is the base class for all jME3 Applications. + * SimpleApplication will display a statistics view + * using the {@link com.jme3.app.StatsAppState} AppState. 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.
    + * + * A {@link com.jme3.app.FlyCamAppState} is by default attached as well and can + * be removed by calling stateManager.detach( stateManager.getState(FlyCamAppState.class) ); + */ +public abstract class SimpleApplication extends Application { + + public static final String INPUT_MAPPING_EXIT = "SIMPLEAPP_Exit"; + public static final String INPUT_MAPPING_CAMERA_POS = DebugKeysAppState.INPUT_MAPPING_CAMERA_POS; + public static final String INPUT_MAPPING_MEMORY = DebugKeysAppState.INPUT_MAPPING_MEMORY; + public static final String INPUT_MAPPING_HIDE_STATS = "SIMPLEAPP_HideStats"; + + protected Node rootNode = new Node("Root Node"); + protected Node guiNode = new Node("Gui Node"); + protected BitmapText fpsText; + protected BitmapFont guiFont; + protected FlyByCamera flyCam; + protected boolean showSettings = true; + private AppActionListener actionListener = new AppActionListener(); + + private class AppActionListener implements ActionListener { + + public void onAction(String name, boolean value, float tpf) { + if (!value) { + return; + } + + if (name.equals(INPUT_MAPPING_EXIT)) { + stop(); + }else if (name.equals(INPUT_MAPPING_HIDE_STATS)){ + if (stateManager.getState(StatsAppState.class) != null) { + stateManager.getState(StatsAppState.class).toggleStats(); + } + } + } + } + + public SimpleApplication() { + this( new StatsAppState(), new FlyCamAppState(), new DebugKeysAppState() ); + } + + public SimpleApplication( AppState... initialStates ) { + super(); + + if (initialStates != null) { + for (AppState a : initialStates) { + if (a != null) { + stateManager.attach(a); + } + } + } + } + + @Override + public void start() { + // set some default settings in-case + // settings dialog is not shown + boolean loadSettings = false; + if (settings == null) { + setSettings(new AppSettings(true)); + loadSettings = true; + } + + // show settings dialog + if (showSettings) { + if (!JmeSystem.showSettingsDialog(settings, loadSettings)) { + return; + } + } + //re-setting settings they can have been merged from the registry. + setSettings(settings); + super.start(); + } + + /** + * Retrieves flyCam + * @return flyCam Camera object + * + */ + public FlyByCamera getFlyByCamera() { + return flyCam; + } + + /** + * Retrieves guiNode + * @return guiNode Node object + * + */ + public Node getGuiNode() { + return guiNode; + } + + /** + * Retrieves rootNode + * @return rootNode Node object + * + */ + public Node getRootNode() { + return rootNode; + } + + public boolean isShowSettings() { + return showSettings; + } + + /** + * Toggles settings window to display at start-up + * @param showSettings Sets true/false + * + */ + public void setShowSettings(boolean showSettings) { + this.showSettings = showSettings; + } + + /** + * Creates the font that will be set to the guiFont field + * and subsequently set as the font for the stats text. + */ + protected BitmapFont loadGuiFont() { + return assetManager.loadFont("Interface/Fonts/Default.fnt"); + } + + @Override + public void initialize() { + super.initialize(); + + // Several things rely on having this + guiFont = loadGuiFont(); + + guiNode.setQueueBucket(Bucket.Gui); + guiNode.setCullHint(CullHint.Never); + viewPort.attachScene(rootNode); + guiViewPort.attachScene(guiNode); + + if (inputManager != null) { + + // We have to special-case the FlyCamAppState because too + // many SimpleApplication subclasses expect it to exist in + // simpleInit(). But at least it only gets initialized if + // the app state is added. + if (stateManager.getState(FlyCamAppState.class) != null) { + flyCam = new FlyByCamera(cam); + flyCam.setMoveSpeed(1f); // odd to set this here but it did it before + stateManager.getState(FlyCamAppState.class).setCamera( flyCam ); + } + + if (context.getType() == Type.Display) { + inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE)); + } + + if (stateManager.getState(StatsAppState.class) != null) { + inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5)); + inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS); + } + + inputManager.addListener(actionListener, INPUT_MAPPING_EXIT); + } + + if (stateManager.getState(StatsAppState.class) != null) { + // Some of the tests rely on having access to fpsText + // for quick display. Maybe a different way would be better. + stateManager.getState(StatsAppState.class).setFont(guiFont); + fpsText = stateManager.getState(StatsAppState.class).getFpsText(); + } + + // call user code + simpleInitApp(); + } + + @Override + public void update() { + super.update(); // makes sure to execute AppTasks + if (speed == 0 || paused) { + return; + } + + float tpf = timer.getTimePerFrame() * speed; + + // update states + stateManager.update(tpf); + + // simple update and root node + simpleUpdate(tpf); + + rootNode.updateLogicalState(tpf); + guiNode.updateLogicalState(tpf); + + rootNode.updateGeometricState(); + guiNode.updateGeometricState(); + + // render states + stateManager.render(renderManager); + renderManager.render(tpf, context.isRenderable()); + simpleRender(renderManager); + stateManager.postRender(); + } + + public void setDisplayFps(boolean show) { + if (stateManager.getState(StatsAppState.class) != null) { + stateManager.getState(StatsAppState.class).setDisplayFps(show); + } + } + + public void setDisplayStatView(boolean show) { + if (stateManager.getState(StatsAppState.class) != null) { + stateManager.getState(StatsAppState.class).setDisplayStatView(show); + } + } + + public abstract void simpleInitApp(); + + public void simpleUpdate(float tpf) { + } + + public void simpleRender(RenderManager rm) { + } +} diff --git a/jme3-core/src/main/java/com/jme3/app/StatsAppState.java b/jme3-core/src/main/java/com/jme3/app/StatsAppState.java new file mode 100644 index 000000000..3225c5798 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/StatsAppState.java @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.material.Material; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.scene.shape.Quad; + + +/** + * Displays stats in SimpleApplication's GUI node or + * using the node and font parameters provided. + * + * @author Paul Speed + */ +public class StatsAppState extends AbstractAppState { + + private Application app; + protected StatsView statsView; + protected boolean showSettings = true; + private boolean showFps = true; + private boolean showStats = true; + private boolean darkenBehind = true; + + protected Node guiNode; + protected float secondCounter = 0.0f; + protected int frameCounter = 0; + protected BitmapText fpsText; + protected BitmapFont guiFont; + protected Geometry darkenFps; + protected Geometry darkenStats; + + public StatsAppState() { + } + + public StatsAppState( Node guiNode, BitmapFont guiFont ) { + this.guiNode = guiNode; + this.guiFont = guiFont; + } + + /** + * Called by SimpleApplication to provide an early font + * so that the fpsText can be created before init. This + * is because several applications expect to directly access + * fpsText... unfortunately. + */ + void setFont( BitmapFont guiFont ) { + this.guiFont = guiFont; + this.fpsText = new BitmapText(guiFont, false); + } + + public BitmapText getFpsText() { + return fpsText; + } + + public StatsView getStatsView() { + return statsView; + } + + public float getSecondCounter() { + return secondCounter; + } + + public void toggleStats() { + setDisplayFps( !showFps ); + setDisplayStatView( !showStats ); + } + + public void setDisplayFps(boolean show) { + showFps = show; + if (fpsText != null) { + fpsText.setCullHint(show ? CullHint.Never : CullHint.Always); + if (darkenFps != null) { + darkenFps.setCullHint(showFps && darkenBehind ? CullHint.Never : CullHint.Always); + } + + } + } + + public void setDisplayStatView(boolean show) { + showStats = show; + if (statsView != null ) { + statsView.setEnabled(show); + statsView.setCullHint(show ? CullHint.Never : CullHint.Always); + if (darkenStats != null) { + darkenStats.setCullHint(showStats && darkenBehind ? CullHint.Never : CullHint.Always); + } + } + } + + public void setDarkenBehind(boolean darkenBehind) { + this.darkenBehind = darkenBehind; + setEnabled(isEnabled()); + } + + public boolean isDarkenBehind() { + return darkenBehind; + } + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + this.app = app; + + if (app instanceof SimpleApplication) { + SimpleApplication simpleApp = (SimpleApplication)app; + if (guiNode == null) { + guiNode = simpleApp.guiNode; + } + if (guiFont == null ) { + guiFont = simpleApp.guiFont; + } + } + + if (guiNode == null) { + throw new RuntimeException( "No guiNode specific and cannot be automatically determined." ); + } + + if (guiFont == null) { + guiFont = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt"); + } + + loadFpsText(); + loadStatsView(); + loadDarken(); + } + + /** + * Attaches FPS statistics to guiNode and displays it on the screen. + * + */ + public void loadFpsText() { + if (fpsText == null) { + fpsText = new BitmapText(guiFont, false); + } + + fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0); + fpsText.setText("Frames per second"); + fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always); + guiNode.attachChild(fpsText); + + } + + /** + * Attaches Statistics View to guiNode and displays it on the screen + * above FPS statistics line. + * + */ + public void loadStatsView() { + statsView = new StatsView("Statistics View", + app.getAssetManager(), + app.getRenderer().getStatistics()); + // move it up so it appears above fps text + statsView.setLocalTranslation(0, fpsText.getLineHeight(), 0); + statsView.setEnabled(showStats); + statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always); + guiNode.attachChild(statsView); + } + + public void loadDarken() { + Material mat = new Material(app.assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", new ColorRGBA(0,0,0,0.5f)); + mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + + darkenFps = new Geometry("StatsDarken", new Quad(200, fpsText.getLineHeight())); + darkenFps.setMaterial(mat); + darkenFps.setLocalTranslation(0, 0, -1); + darkenFps.setCullHint(showFps && darkenBehind ? CullHint.Never : CullHint.Always); + guiNode.attachChild(darkenFps); + + darkenStats = new Geometry("StatsDarken", new Quad(200, statsView.getHeight())); + darkenStats.setMaterial(mat); + darkenStats.setLocalTranslation(0, fpsText.getHeight(), -1); + darkenStats.setCullHint(showStats && darkenBehind ? CullHint.Never : CullHint.Always); + guiNode.attachChild(darkenStats); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + + if (enabled) { + fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always); + darkenFps.setCullHint(showFps && darkenBehind ? CullHint.Never : CullHint.Always); + statsView.setEnabled(showStats); + statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always); + darkenStats.setCullHint(showStats && darkenBehind ? CullHint.Never : CullHint.Always); + } else { + fpsText.setCullHint(CullHint.Always); + darkenFps.setCullHint(CullHint.Always); + statsView.setEnabled(false); + statsView.setCullHint(CullHint.Always); + darkenStats.setCullHint(CullHint.Always); + } + } + + @Override + public void update(float tpf) { + if (showFps) { + secondCounter += app.getTimer().getTimePerFrame(); + frameCounter ++; + if (secondCounter >= 1.0f) { + int fps = (int) (frameCounter / secondCounter); + fpsText.setText("Frames per second: " + fps); + secondCounter = 0.0f; + frameCounter = 0; + } + } + } + + @Override + public void cleanup() { + super.cleanup(); + + guiNode.detachChild(statsView); + guiNode.detachChild(fpsText); + guiNode.detachChild(darkenFps); + guiNode.detachChild(darkenStats); + } + + +} diff --git a/jme3-core/src/main/java/com/jme3/app/StatsView.java b/jme3-core/src/main/java/com/jme3/app/StatsView.java new file mode 100644 index 000000000..9f748fdf4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/StatsView.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.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 boolean enabled = true; + + private final StringBuilder stringBuilder = new StringBuilder(); + + public StatsView(String name, AssetManager manager, Statistics stats){ + super(name); + + setQueueBucket(Bucket.Gui); + setCullHint(CullHint.Never); + + statistics = stats; + statistics.setEnabled(enabled); + + 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 float getHeight() { + return labels[0].getLineHeight() * statLabels.length; + } + + public void update(float tpf) { + + if (!isEnabled()) + return; + + statistics.getData(statData); + for (int i = 0; i < labels.length; i++) { + stringBuilder.setLength(0); + stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]); + labels[i].setText(stringBuilder); + } + + // Moved to ResetStatsState to make sure it is + // done even if there is no StatsView or the StatsView + // is disable. + //statistics.clearFrame(); + } + + public Control cloneForSpatial(Spatial spatial) { + return (Control) spatial; + } + + public void setSpatial(Spatial spatial) { + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + statistics.setEnabled(enabled); + } + + public boolean isEnabled() { + return enabled; + } + + public void render(RenderManager rm, ViewPort vp) { + } + +} diff --git a/jme3-core/src/main/java/com/jme3/app/package.html b/jme3-core/src/main/java/com/jme3/app/package.html new file mode 100644 index 000000000..ec6bb9a89 --- /dev/null +++ b/jme3-core/src/main/java/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/jme3-core/src/main/java/com/jme3/app/state/AbstractAppState.java b/jme3-core/src/main/java/com/jme3/app/state/AbstractAppState.java new file mode 100644 index 000000000..fe3cf22b6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/state/AbstractAppState.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app.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 enabled = true; + + public void initialize(AppStateManager stateManager, Application app) { + initialized = true; + } + + public boolean isInitialized() { + return initialized; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public void stateAttached(AppStateManager stateManager) { + } + + public void stateDetached(AppStateManager stateManager) { + } + + public void update(float tpf) { + } + + public void render(RenderManager rm) { + } + + public void postRender(){ + } + + public void cleanup() { + initialized = false; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/app/state/AppState.java b/jme3-core/src/main/java/com/jme3/app/state/AppState.java new file mode 100644 index 000000000..cd6e02e51 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/state/AppState.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app.state; + +import com.jme3.app.Application; +import com.jme3.renderer.RenderManager; + +/** + * AppState represents 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.
    + * + *
      + *
    • If a detached AppState is attached then initialize() will be called + * on the following render pass. + *
    • + *
    • If an attached AppState is detached then cleanup() will be called + * on the following render pass. + *
    • + *
    • If you attach an already-attached AppState then the second attach + * is a no-op and will return false. + *
    • + *
    • If you both attach and detach an AppState within one frame then + * neither initialize() or cleanup() will be called, + * although if either is called both will be. + *
    • + *
    • If you both detach and then re-attach an AppState within one frame + * then on the next update pass its cleanup() and initialize() + * methods will be called in that order. + *
    • + *
    + * @author Kirill Vainer + */ +public interface AppState { + + /** + * Called by {@link AppStateManager} when transitioning this {@code AppState} + * from initializing to running.
    + * This will happen on the next iteration through the update loop after + * {@link AppStateManager#attach()} was called. + *

    + * AppStateManager will call this only from the update loop + * inside the rendering thread. This means is it safe to modify the scene + * graph from this method. + * + * @param stateManager The state manager + * @param app The application + */ + public void initialize(AppStateManager stateManager, Application app); + + /** + * @return True if initialize() was called on the state, + * false otherwise. + */ + public boolean isInitialized(); + + /** + * Enable or disable the functionality of the AppState. + * The effect of this call depends on implementation. An + * AppState starts as being enabled by default. + * A disabled AppStates does not get calls to + * {@link #update(float)}, {@link #render(RenderManager)}, or + * {@link #postRender()} from its {@link AppStateManager}. + * + * @param active activate the AppState or not. + */ + public void setEnabled(boolean active); + + /** + * @return True if the AppState is enabled, false otherwise. + * + * @see AppState#setEnabled(boolean) + */ + public boolean isEnabled(); + + /** + * Called by {@link AppStateManager#attach()} when transitioning this + * AppState from detached to initializing. + *

    + * There is no assumption about the thread from which this function is + * called, therefore it is unsafe to modify the scene graph + * from this method. Please use + * {@link #initialize(com.jme3.app.state.AppStateManager, com.jme3.app.Application) } + * instead. + * + * @param stateManager State manager to which the state was attached to. + */ + public void stateAttached(AppStateManager stateManager); + + /** + * Called by {@link AppStateManager#detach()} when transitioning this + * AppState from running to terminating. + *

    + * There is no assumption about the thread from which this function is + * called, therefore it is unsafe to modify the scene graph + * from this method. Please use + * {@link #cleanup() } + * instead. + * + * @param stateManager The state manager from which the state was detached from. + */ + public void stateDetached(AppStateManager stateManager); + + /** + * Called to update the AppState. This method will be called + * every render pass if the AppState is both attached and enabled. + * + * @param tpf Time since the last call to update(), in seconds. + */ + public void update(float tpf); + + /** + * Render the state. This method will be called + * every render pass if the AppState is both attached and enabled. + * + * @param rm RenderManager + */ + public void render(RenderManager rm); + + /** + * Called after all rendering commands are flushed. This method will be called + * every render pass if the AppState is both attached and enabled. + */ + public void postRender(); + + /** + * Called by {@link AppStateManager} when transitioning this + * AppState from terminating to detached. This + * method is called the following render pass after the AppState has + * been detached and is always called once and only once for each time + * initialize() is called. Either when the AppState + * is detached or when the application terminates (if it terminates normally). + */ + public void cleanup(); + +} diff --git a/jme3-core/src/main/java/com/jme3/app/state/AppStateManager.java b/jme3-core/src/main/java/com/jme3/app/state/AppStateManager.java new file mode 100644 index 000000000..177fcb22f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/state/AppStateManager.java @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app.state; + +import com.jme3.app.Application; +import com.jme3.renderer.RenderManager; +import com.jme3.util.SafeArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * The 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. + * + *

    The lifecycle for an attached AppState is as follows:

    + *
      + *
    • stateAttached() : called when the state is attached on the thread on which + * the state was attached. + *
    • initialize() : called ONCE on the render thread at the beginning of the next + * AppStateManager.update(). + *
    • stateDetached() : called when the state is detached on the thread on which + * the state was detached. This is not necessarily on the + * render thread and it is not necessarily safe to modify + * the scene graph, etc.. + *
    • cleanup() : called ONCE on the render thread at the beginning of the next update + * after the state has been detached or when the application is + * terminating. + *
    + * + * @author Kirill Vainer, Paul Speed + */ +public class AppStateManager { + + /** + * List holding the attached app states that are pending + * initialization. Once initialized they will be added to + * the running app states. + */ + private final SafeArrayList initializing = new SafeArrayList(AppState.class); + + /** + * Holds the active states once they are initialized. + */ + private final SafeArrayList states = new SafeArrayList(AppState.class); + + /** + * List holding the detached app states that are pending + * cleanup. + */ + private final SafeArrayList terminating = new SafeArrayList(AppState.class); + + // All of the above lists need to be thread safe but access will be + // synchronized separately.... but always on the states list. This + // is to avoid deadlocking that may occur and the most common use case + // is that they are all modified from the same thread anyway. + + private final Application app; + private AppState[] stateArray; + + public AppStateManager(Application app){ + this.app = app; + } + + /** + * Returns the Application to which this AppStateManager belongs. + */ + public Application getApplication() { + return app; + } + + protected AppState[] getInitializing() { + synchronized (states){ + return initializing.getArray(); + } + } + + protected AppState[] getTerminating() { + synchronized (states){ + return terminating.getArray(); + } + } + + protected AppState[] getStates(){ + synchronized (states){ + return states.getArray(); + } + } + + /** + * Attach a state to the AppStateManager, the same state cannot be attached + * twice. + * + * @param state The state to attach + * @return True if the state was successfully attached, false if the state + * was already attached. + */ + public boolean attach(AppState state){ + synchronized (states){ + if (!states.contains(state) && !initializing.contains(state)){ + state.stateAttached(this); + initializing.add(state); + return true; + }else{ + return false; + } + } + } + + /** + * Attaches many state to the AppStateManager in a way that is guaranteed + * that they will all get initialized before any of their updates are run. + * The same state cannot be attached twice and will be ignored. + * + * @param states The states to attach + */ + public void attachAll(AppState... states){ + attachAll(Arrays.asList(states)); + } + + /** + * Attaches many state to the AppStateManager in a way that is guaranteed + * that they will all get initialized before any of their updates are run. + * The same state cannot be attached twice and will be ignored. + * + * @param states The states to attach + */ + public void attachAll(Iterable states){ + synchronized (this.states){ + for( AppState state : states ) { + attach(state); + } + } + } + + /** + * Detaches the state from the AppStateManager. + * + * @param state The state to detach + * @return True if the state was detached successfully, false + * if the state was not attached in the first place. + */ + public boolean detach(AppState state){ + synchronized (states){ + if (states.contains(state)){ + state.stateDetached(this); + states.remove(state); + terminating.add(state); + return true; + } else if(initializing.contains(state)){ + state.stateDetached(this); + initializing.remove(state); + return true; + }else{ + return false; + } + } + } + + /** + * Check if a state is attached or not. + * + * @param state The state to check + * @return True if the state is currently attached to this AppStateManager. + * + * @see AppStateManager#attach(com.jme3.app.state.AppState) + */ + public boolean hasState(AppState state){ + synchronized (states){ + return states.contains(state) || initializing.contains(state); + } + } + + /** + * Returns the first state that is an instance of subclass of the specified class. + * @param + * @param stateClass + * @return First attached state that is an instance of stateClass + */ + public T getState(Class stateClass){ + synchronized (states){ + AppState[] array = getStates(); + for (AppState state : array) { + if (stateClass.isAssignableFrom(state.getClass())){ + return (T) state; + } + } + + // This may be more trouble than its worth but I think + // it's necessary for proper decoupling of states and provides + // similar behavior to before where a state could be looked + // up even if it wasn't initialized. -pspeed + array = getInitializing(); + for (AppState state : array) { + if (stateClass.isAssignableFrom(state.getClass())){ + return (T) state; + } + } + } + return null; + } + + protected void initializePending(){ + AppState[] array = getInitializing(); + if (array.length == 0) + return; + + synchronized( states ) { + // Move the states that will be initialized + // into the active array. In all but one case the + // order doesn't matter but if we do this here then + // a state can detach itself in initialize(). If we + // did it after then it couldn't. + List transfer = Arrays.asList(array); + states.addAll(transfer); + initializing.removeAll(transfer); + } + for (AppState state : array) { + state.initialize(this, app); + } + } + + protected void terminatePending(){ + AppState[] array = getTerminating(); + if (array.length == 0) + return; + + for (AppState state : array) { + state.cleanup(); + } + synchronized( states ) { + // Remove just the states that were terminated... + // which might now be a subset of the total terminating + // list. + terminating.removeAll(Arrays.asList(array)); + } + } + + /** + * Calls update for attached states, do not call directly. + * @param tpf Time per frame. + */ + public void update(float tpf){ + + // Cleanup any states pending + terminatePending(); + + // Initialize any states pending + initializePending(); + + // Update enabled states + AppState[] array = getStates(); + for (AppState state : array){ + if (state.isEnabled()) { + state.update(tpf); + } + } + } + + /** + * Calls render for all attached and initialized states, do not call directly. + * @param rm The RenderManager + */ + public void render(RenderManager rm){ + AppState[] array = getStates(); + for (AppState state : array){ + if (state.isEnabled()) { + state.render(rm); + } + } + } + + /** + * Calls render for all attached and initialized states, do not call directly. + */ + public void postRender(){ + AppState[] array = getStates(); + for (AppState state : array){ + if (state.isEnabled()) { + state.postRender(); + } + } + } + + /** + * Calls cleanup on attached states, do not call directly. + */ + public void cleanup(){ + AppState[] array = getStates(); + for (AppState state : array){ + state.cleanup(); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java b/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java new file mode 100644 index 000000000..10afa47da --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app.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.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.system.JmeSystem; +import com.jme3.texture.FrameBuffer; +import com.jme3.util.BufferUtils; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ScreenshotAppState extends AbstractAppState implements ActionListener, SceneProcessor { + + private static final Logger logger = Logger.getLogger(ScreenshotAppState.class.getName()); + private String filePath = null; + private boolean capture = false; + private boolean numbered = true; + private Renderer renderer; + private RenderManager rm; + private ByteBuffer outBuf; + private String shotName; + private long shotIndex = 0; + private int width, height; + + /** + * Using this constructor, the screenshot files will be written sequentially to the system + * default storage folder. + */ + public ScreenshotAppState() { + this(null); + } + + /** + * This constructor allows you to specify the output file path of the screenshot. + * Include the seperator at the end of the path. + * Use an emptry string to use the application folder. Use NULL to use the system + * default storage folder. + * @param filePath The screenshot file path to use. Include the seperator at the end of the path. + */ + public ScreenshotAppState(String filePath) { + this.filePath = filePath; + } + + /** + * This constructor allows you to specify the output file path of the screenshot. + * Include the seperator at the end of the path. + * Use an emptry string to use the application folder. Use NULL to use the system + * default storage folder. + * @param filePath The screenshot file path to use. Include the seperator at the end of the path. + * @param fileName The name of the file to save the screeshot as. + */ + public ScreenshotAppState(String filePath, String fileName) { + this.filePath = filePath; + this.shotName = fileName; + } + + /** + * This constructor allows you to specify the output file path of the screenshot and + * a base index for the shot index. + * Include the seperator at the end of the path. + * Use an emptry string to use the application folder. Use NULL to use the system + * default storage folder. + * @param filePath The screenshot file path to use. Include the seperator at the end of the path. + * @param shotIndex The base index for screen shots. The first screen shot will have + * shotIndex + 1 appended, the next shotIndex + 2, and so on. + */ + public ScreenshotAppState(String filePath, long shotIndex) { + this.filePath = filePath; + this.shotIndex = shotIndex; + } + + /** + * This constructor allows you to specify the output file path of the screenshot and + * a base index for the shot index. + * Include the seperator at the end of the path. + * Use an emptry string to use the application folder. Use NULL to use the system + * default storage folder. + * @param filePath The screenshot file path to use. Include the seperator at the end of the path. + * @param fileName The name of the file to save the screeshot as. + * @param shotIndex The base index for screen shots. The first screen shot will have + * shotIndex + 1 appended, the next shotIndex + 2, and so on. + */ + public ScreenshotAppState(String filePath, String fileName, long shotIndex) { + this.filePath = filePath; + this.shotName = fileName; + this.shotIndex = shotIndex; + } + + /** + * Set the file path to store the screenshot. + * Include the seperator at the end of the path. + * Use an emptry string to use the application folder. Use NULL to use the system + * default storage folder. + * @param filePath File path to use to store the screenshot. Include the seperator at the end of the path. + */ + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + /** + * Set the file name of the screenshot. + * @param fileName File name to save the screenshot as. + */ + public void setFileName(String fileName) { + this.shotName = fileName; + } + + /** + * Sets the base index that will used for subsequent screen shots. + */ + public void setShotIndex(long index) { + this.shotIndex = index; + } + + /** + * Sets if the filename should be appended with a number representing the + * current sequence. + * @param numberedWanted If numbering is wanted. + */ + public void setIsNumbered(boolean numberedWanted) { + this.numbered = numberedWanted; + } + + @Override + public void initialize(AppStateManager stateManager, Application app) { + if (!super.isInitialized()){ + InputManager inputManager = app.getInputManager(); + inputManager.addMapping("ScreenShot", new KeyTrigger(KeyInput.KEY_SYSRQ)); + inputManager.addListener(this, "ScreenShot"); + + List vps = app.getRenderManager().getPostViews(); + ViewPort last = vps.get(vps.size()-1); + last.addProcessor(this); + + if (shotName == null) { + shotName = app.getClass().getSimpleName(); + } + } + + super.initialize(stateManager, app); + } + + public void onAction(String name, boolean value, float tpf) { + if (value){ + capture = true; + } + } + + public void takeScreenshot() { + capture = true; + } + + public void initialize(RenderManager rm, ViewPort vp) { + renderer = rm.getRenderer(); + this.rm = rm; + 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); + width = w; + height = h; + } + + public void preFrame(float tpf) { + } + + public void postQueue(RenderQueue rq) { + } + + public void postFrame(FrameBuffer out) { + if (capture){ + capture = false; + + Camera curCamera = rm.getCurrentCamera(); + int viewX = (int) (curCamera.getViewPortLeft() * curCamera.getWidth()); + int viewY = (int) (curCamera.getViewPortBottom() * curCamera.getHeight()); + int viewWidth = (int) ((curCamera.getViewPortRight() - curCamera.getViewPortLeft()) * curCamera.getWidth()); + int viewHeight = (int) ((curCamera.getViewPortTop() - curCamera.getViewPortBottom()) * curCamera.getHeight()); + + renderer.setViewPort(0, 0, width, height); + renderer.readFrameBuffer(out, outBuf); + renderer.setViewPort(viewX, viewY, viewWidth, viewHeight); + + File file; + String filename; + if (numbered) { + shotIndex++; + filename = shotName + shotIndex; + } else { + filename = shotName; + } + + if (filePath == null) { + file = new File(JmeSystem.getStorageFolder() + File.separator + filename + ".png").getAbsoluteFile(); + } else { + file = new File(filePath + filename + ".png").getAbsoluteFile(); + } + logger.log(Level.FINE, "Saving ScreenShot to: {0}", file.getAbsolutePath()); + + OutputStream outStream = null; + try { + outStream = new FileOutputStream(file); + JmeSystem.writeImageFile(outStream, "png", outBuf, width, height); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error while saving screenshot", ex); + } finally { + if (outStream != null){ + try { + outStream.close(); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error while saving screenshot", ex); + } + } + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/app/state/package.html b/jme3-core/src/main/java/com/jme3/app/state/package.html new file mode 100644 index 000000000..0e93b3880 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/state/package.html @@ -0,0 +1,15 @@ + + + + + + + + + +The com.jme3.app.state package provides +{@link com.jme3.app.state.AppState app states}, +an abstract way of handling application logic. + + + diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java b/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java new file mode 100644 index 000000000..735a57be8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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(); + } + Class clazz = acquireClass(loaderClass); + if (clazz != null) { + manager.registerLoader(clazz, extensions); + } else { + Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot find loader {0}", loaderClass); + } + } else if (cmd.equals("LOCATOR")) { + String rootPath = scan.next(); + String locatorClass = scan.nextLine().trim(); + Class clazz = acquireClass(locatorClass); + if (clazz != null) { + manager.registerLocator(rootPath, clazz); + } else { + Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot find locator {0}", locatorClass); + } + } else { + throw new IOException("Expected command, got '" + cmd + "'"); + } + } + } + + private Class acquireClass(String name) { + try { + Class clazz = Class.forName(name); + return clazz; + } catch (ClassNotFoundException ex) { + return null; + } + } + + /* + 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/jme3-core/src/main/java/com/jme3/asset/AssetEventListener.java b/jme3-core/src/main/java/com/jme3/asset/AssetEventListener.java new file mode 100644 index 000000000..c58133534 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/AssetEventListener.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +/** + * 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); + + /** + * Called when an asset dependency cannot be found for an asset. + * When an asset is loaded, each of its dependent assets that + * have failed to load due to a {@link AssetNotFoundException}, will cause + * an invocation of this callback. + * + * @param parentKey The key of the parent asset that is being loaded + * from within the user application. + * @param dependentAssetKey The asset key of the dependent asset that has + * failed to load. + */ + public void assetDependencyNotFound(AssetKey parentKey, AssetKey dependentAssetKey); + +} diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetInfo.java b/jme3-core/src/main/java/com/jme3/asset/AssetInfo.java new file mode 100644 index 000000000..34cd8c5ab --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/AssetInfo.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import java.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}. + *

    + * Each invocation of this method should return a new stream to the + * asset data, starting at the beginning of the file. + * + * @return The asset data. + */ + public abstract InputStream openStream(); + +} diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetKey.java b/jme3-core/src/main/java/com/jme3/asset/AssetKey.java new file mode 100644 index 000000000..a266bd74c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/AssetKey.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.asset.cache.AssetCache; +import com.jme3.asset.cache.SimpleAssetCache; +import com.jme3.export.*; +import java.io.IOException; +import java.util.LinkedList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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, Cloneable { + + protected String name; + protected transient String folder; + protected transient String extension; + + public AssetKey(String name){ + this.name = reducePath(name); + this.extension = getExtension(this.name); + } + + public AssetKey(){ + } + + @Override + public AssetKey clone() { + try { + return (AssetKey) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + protected static String getExtension(String name) { + int idx = name.lastIndexOf('.'); + //workaround for filenames ending with xml and another dot ending before that (my.mesh.xml) + if (name.toLowerCase().endsWith(".xml")) { + idx = name.substring(0, idx).lastIndexOf('.'); + if (idx == -1) { + idx = name.lastIndexOf('.'); + } + } + if (idx <= 0 || idx == name.length() - 1) { + return ""; + } else { + return name.substring(idx + 1).toLowerCase(); + } + } + + protected static String getFolder(String name) { + int idx = name.lastIndexOf('/'); + if (idx <= 0 || idx == name.length() - 1) { + return ""; + } else { + return name.substring(0, idx + 1); + } + } + + /** + * @return The asset path + */ + 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; + } + + /** + * @return The folder in which the asset is located in. + * E.g. if the {@link #getName() name} is "Models/MyModel/MyModel.j3o" + * then "Models/MyModel/" is returned. + */ + public String getFolder(){ + if (folder == null) + folder = getFolder(name); + + return folder; + } + + /** + * @return The preferred cache class for this asset type. Specify "null" + * if caching is to be disabled. By default the + * {@link SimpleAssetCache} is returned. + */ + public Class getCacheType(){ + return SimpleAssetCache.class; + } + + /** + * @return The preferred processor type for this asset type. Specify "null" + * if no processing is required. + */ + public Class getProcessorType(){ + return null; + } + + /** + * Removes all relative elements of a path (A/B/../C.png and A/./C.png). + * @param path The path containing relative elements + * @return A path without relative elements + */ + public static String reducePath(String path) { + if (path == null || path.indexOf("./") == -1) { + return path; + } + String[] parts = path.split("/"); + LinkedList list = new LinkedList(); + for (int i = 0; i < parts.length; i++) { + String string = parts[i]; + if (string.length() == 0 || string.equals(".")) { + //do nothing + } else if (string.equals("..")) { + if (list.size() > 0 && !list.getLast().equals("..")) { + list.removeLast(); + } else { + list.add(".."); + Logger.getLogger(AssetKey.class.getName()).log(Level.SEVERE, "Asset path is outside assetmanager root"); + } + } else { + list.add(string); + } + } + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < list.size(); i++) { + String string = list.get(i); + if (i != 0) { + builder.append("/"); + } + builder.append(string); + } + return builder.toString(); + } + + @Override + public boolean equals(Object other){ + if (!(other instanceof AssetKey)){ + return false; + } + return name.equals(((AssetKey)other).name); + } + + @Override + public int hashCode(){ + return name.hashCode(); + } + + @Override + public String toString(){ + return name; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(name, "name", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + name = reducePath(ic.readString("name", null)); + extension = getExtension(name); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetLoadException.java b/jme3-core/src/main/java/com/jme3/asset/AssetLoadException.java new file mode 100644 index 000000000..770640e04 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/AssetLoadException.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +/** + * AssetLoadException is thrown when the {@link AssetManager} + * is able to find the requested asset, but there was a problem while loading + * it. + * + * @author Kirill Vainer + */ +public class AssetLoadException extends RuntimeException { + public AssetLoadException(String message){ + super(message); + } + public AssetLoadException(String message, Throwable cause){ + super(message, cause); + } +} diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetLoader.java b/jme3-core/src/main/java/com/jme3/asset/AssetLoader.java new file mode 100644 index 000000000..dde7476c2 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/AssetLoader.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import java.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/jme3-core/src/main/java/com/jme3/asset/AssetLocator.java b/jme3-core/src/main/java/com/jme3/asset/AssetLocator.java new file mode 100644 index 000000000..caa28524b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/AssetLocator.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +/** + * 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 The {@link AssetInfo} that was located, or null if not found. + */ + public AssetInfo locate(AssetManager manager, AssetKey key); +} diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java new file mode 100644 index 000000000..16fa9e0f5 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.asset.plugins.FileLocator; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioKey; +import com.jme3.font.BitmapFont; +import com.jme3.material.Material; +import com.jme3.post.FilterPostProcessor; +import com.jme3.renderer.Caps; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.OBJLoader; +import com.jme3.shader.Shader; +import com.jme3.shader.ShaderGenerator; +import com.jme3.shader.ShaderKey; +import com.jme3.texture.Texture; +import com.jme3.texture.plugins.TGALoader; +import java.util.EnumSet; +import java.util.List; + +/** + * AssetManager provides an interface for managing the data assets + * of a jME3 application. + *

    + * The asset manager provides a means to register {@link AssetLocator}s, + * which are used to find asset data on disk, network, or other file system. + * The asset locators are invoked in order of addition to find the asset data. + * Use the {@link #registerLocator(java.lang.String, java.lang.Class) } method + * to add new {@link AssetLocator}s. + * Some examples of locators: + *

      + *
    • {@link FileLocator} - Used to find assets on the local file system.
    • + *
    • {@link ClasspathLocator} - Used to find assets in the Java classpath
    • + *
    + *

    + * The asset data is represented by the {@link AssetInfo} class, this + * data is passed into the registered {@link AssetLoader}s in order to + * convert the data into a usable object. Use the + * {@link #registerLoader(java.lang.Class, java.lang.String[]) } method + * to add loaders. + * Some examples of loaders: + *

      + *
    • {@link OBJLoader} - Used to load Wavefront .OBJ model files
    • + *
    • {@link TGALoader} - Used to load Targa image files
    • + *
    + *

    + * Once the asset has been loaded, + */ +public interface AssetManager { + + /** + * Adds a {@link ClassLoader} that is used to load {@link Class classes} + * that are needed for finding and loading Assets. + * This does not allow loading assets from that classpath, + * use registerLocator for that. + * + * @param loader A ClassLoader that Classes in asset files can be loaded from. + */ + public void addClassLoader(ClassLoader loader); + + /** + * Remove a {@link ClassLoader} from the list of registered ClassLoaders + */ + public void removeClassLoader(ClassLoader loader); + + /** + * Retrieve the list of registered ClassLoaders that are used for loading + * {@link Class classes} from asset files. + */ + public List getClassLoaders(); + + /** + * Registers a loader for the given extensions. + * + * @param loaderClassName + * @param extensions + * + * @deprecated Please use {@link #registerLoader(java.lang.Class, java.lang.String[]) } + * together with {@link Class#forName(java.lang.String) } to find a class + * and then register it. + * + * @deprecated Please use {@link #registerLoader(java.lang.Class, java.lang.String[]) } + * with {@link Class#forName(java.lang.String) } instead. + */ + @Deprecated + public void registerLoader(String loaderClassName, String ... extensions); + + /** + * Registers an {@link AssetLocator} by using a class name. + * 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, this + * depends on the implementation of the asset locator. + * A URL based locator will expect a url folder such as "http://www.example.com/" + * while a File based locator will expect a file path (OS dependent). + * @param locatorClassName The full class name of the {@link AssetLocator} + * implementation. + * + * @deprecated Please use {@link #registerLocator(java.lang.String, java.lang.Class) } + * together with {@link Class#forName(java.lang.String) } to find a class + * and then register it. + */ + @Deprecated + public void registerLocator(String rootPath, String locatorClassName); + + /** + * Register an {@link AssetLoader} by using a class object. + * + * @param loaderClass + * @param extensions + */ + public void registerLoader(Class loaderClass, String ... extensions); + + /** + * Unregister a {@link AssetLoader} from loading its assigned extensions. + * This undoes the effect of calling + * {@link #registerLoader(java.lang.Class, java.lang.String[]) }. + * + * @param loaderClass The loader class to unregister. + * @see #registerLoader(java.lang.Class, java.lang.String[]) + */ + public void unregisterLoader(Class loaderClass); + + /** + * 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. + * Once a locator is registered, it can be removed via + * {@link #unregisterLocator(java.lang.String, java.lang.Class) }. + * + * @param rootPath Specifies the root path from which to locate assets + * for the given {@link AssetLocator}. The purpose of this parameter + * depends on the type of the {@link AssetLocator}. + * @param locatorClass The class type of the {@link AssetLocator} to register. + * + * @see AssetLocator#setRootPath(java.lang.String) + * @see AssetLocator#locate(com.jme3.asset.AssetManager, com.jme3.asset.AssetKey) + * @see #unregisterLocator(java.lang.String, java.lang.Class) + */ + public void registerLocator(String rootPath, Class locatorClass); + + /** + * Unregisters the given locator class. This essentially undoes the operation + * done by {@link #registerLocator(java.lang.String, java.lang.Class) }. + * + * @param rootPath Should be the same as the root path specified in {@link + * #registerLocator(java.lang.String, java.lang.Class) }. + * @param locatorClass The locator class to unregister + * + * @see #registerLocator(java.lang.String, java.lang.Class) + */ + public void unregisterLocator(String rootPath, Class locatorClass); + + /** + * Add an {@link AssetEventListener} to receive events from this + * AssetManager. + * @param listener The asset event listener to add + */ + public void addAssetEventListener(AssetEventListener listener); + + /** + * Remove an {@link AssetEventListener} from receiving events from this + * AssetManager + * @param listener The asset event listener to remove + */ + public void removeAssetEventListener(AssetEventListener listener); + + /** + * Removes all asset event listeners. + * + * @see #addAssetEventListener(com.jme3.asset.AssetEventListener) + */ + public void clearAssetEventListeners(); + + /** + * Set an {@link AssetEventListener} to receive events from this + * AssetManager. Any currently added listeners are + * cleared and then the given listener is added. + * + * @param listener The listener to set + * @deprecated Please use {@link #addAssetEventListener(com.jme3.asset.AssetEventListener) } + * to listen for asset events. + */ + @Deprecated + 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 an 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 The texture that was loaded + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public Texture loadTexture(String name); + + /** + * Load audio file, supported types are WAV or OGG. + * @param key Asset key of the audio file to load + * @return The audio data loaded + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public AudioData loadAudio(AudioKey key); + + /** + * Load audio file, supported types are WAV or OGG. + * The file is loaded without stream-mode. + * @param name Asset name of the audio file to load + * @return The audio data loaded + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public AudioData loadAudio(String name); + + /** + * Loads a 3D model with a ModelKey. + * Models can be jME3 object files (J3O) or OgreXML/OBJ files. + * @param key Asset key of the model to load + * @return The model that was loaded + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public Spatial loadModel(ModelKey key); + + /** + * Loads a 3D model. Models can be jME3 object files (J3O) or + * OgreXML/OBJ files. + * @param name Asset name of the model to load + * @return The model that was loaded + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public Spatial loadModel(String name); + + /** + * Load a material instance (J3M) file. + * @param name Asset name of the material to load + * @return The material that was loaded + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public Material loadMaterial(String name); + + /** + * Loads shader file(s), shouldn't be used by end-user in most cases. + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public Shader loadShader(ShaderKey key); + + /** + * Load a font file. Font files are in AngelCode text format, + * and are with the extension "fnt". + * + * @param name Asset name of the font to load + * @return The font loaded + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public BitmapFont loadFont(String name); + + /** + * Loads a filter *.j3f file with a FilterKey. + * @param key Asset key of the filter file to load + * @return The filter that was loaded + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public FilterPostProcessor loadFilter(FilterKey key); + + /** + * Loads a filter *.j3f file with a FilterKey. + * @param name Asset name of the filter file to load + * @return The filter that was loaded + * + * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) + */ + public FilterPostProcessor loadFilter(String name); + + /** + * Sets the shaderGenerator to generate shaders based on shaderNodes. + * @param generator the shaderGenerator + */ + public void setShaderGenerator(ShaderGenerator generator); + + /** + * Returns the shaderGenerator responsible for generating the shaders + * @return the shaderGenerator + */ + public ShaderGenerator getShaderGenerator(EnumSet caps); + +} diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetNotFoundException.java b/jme3-core/src/main/java/com/jme3/asset/AssetNotFoundException.java new file mode 100644 index 000000000..acee6db72 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/AssetNotFoundException.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +/** + * AssetNotFoundException is thrown when the {@link AssetManager} + * is unable to locate the requested asset using any of the registered + * {@link AssetLocator}s. + * + * @author Kirill Vainer + */ +public class AssetNotFoundException extends RuntimeException { + public AssetNotFoundException(String message){ + super(message); + } + public AssetNotFoundException(String message, Exception ex){ + super(message, ex); + } +} diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetProcessor.java b/jme3-core/src/main/java/com/jme3/asset/AssetProcessor.java new file mode 100644 index 000000000..e8e3728b3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/AssetProcessor.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.material.Material; +import com.jme3.shader.Shader; + +/** + * AssetProcessor is used to apply processing to assets + * after they have been loaded. They are assigned to a particular + * asset type (which is represented by a {@link Class} and any assets + * loaded that are of that class will be processed by the assigned + * processor. + * + * @author Kirill Vainer + */ +public interface AssetProcessor { + /** + * Applies post processing to an asset. + * The method may return an object that is not the same + * instance as the parameter object, and it could be from a different class. + * + * @param obj The asset that was loaded from an {@link AssetLoader}. + * @return Either the same object with processing applied, or an instance + * of a new object. + */ + public Object postProcess(AssetKey key, Object obj); + + /** + * Creates a clone of the given asset. + * If no clone is desired, then the same instance can be returned, + * otherwise, a clone should be created. + * For example, a clone of a {@link Material} should have its own set + * of unique parameters that can be changed just for that instance, + * but it may share certain other data if it sees fit (like the {@link Shader}). + * + * @param obj The asset to clone + * @return The cloned asset, or the same as the given argument if no + * clone is needed. + */ + public Object createClone(Object obj); +} diff --git a/jme3-core/src/main/java/com/jme3/asset/CloneableAssetProcessor.java b/jme3-core/src/main/java/com/jme3/asset/CloneableAssetProcessor.java new file mode 100644 index 000000000..bc6529b6b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/CloneableAssetProcessor.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +/** + * CloneableAssetProcessor simply calls {@link Object#clone() } + * on assets to clone them. No processing is applied. + * + * @author Kirill Vainer + */ +public class CloneableAssetProcessor implements AssetProcessor { + + public Object postProcess(AssetKey key, Object obj) { + return obj; + } + + public Object createClone(Object obj) { + CloneableSmartAsset asset = (CloneableSmartAsset) obj; + return asset.clone(); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/asset/CloneableSmartAsset.java b/jme3-core/src/main/java/com/jme3/asset/CloneableSmartAsset.java new file mode 100644 index 000000000..784085613 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/CloneableSmartAsset.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.asset.cache.WeakRefCloneAssetCache; + +/** + * Implementing the CloneableSmartAsset interface allows use + * of cloneable smart asset management. + *

    + * Smart asset management requires cooperation from the {@link AssetKey}. + * In particular, the AssetKey should return {@link WeakRefCloneAssetCache} in its + * {@link AssetKey#getCacheType()} method. Also smart assets MUST + * create a clone of the asset and cannot return the same reference, + * e.g. {@link AssetProcessor#createClone(java.lang.Object) createClone(someAsset)} != someAsset. + *

    + * If the {@link AssetManager#loadAsset(com.jme3.asset.AssetKey) } method + * is called twice with the same asset key (equals() wise, not necessarily reference wise) + * then both assets will have the same asset key set (reference wise) via + * {@link AssetKey#AssetKey() }, then this asset key + * is used to track all instances of that asset. Once all clones of the asset + * are garbage collected, the shared asset key becomes unreachable and at that + * point it is removed from the smart asset cache. + */ +public interface CloneableSmartAsset extends Cloneable { + + /** + * Creates a clone of the asset. + * + * Please see {@link Object#clone() } for more info on how this method + * should be implemented. + * + * @return A clone of this asset. + * The cloned asset cannot reference equal this asset. + */ + public Object clone(); + + /** + * Set by the {@link AssetManager} to track this asset. + * + * Only clones of the asset has this set, the original copy that + * was loaded has this key set to null so that only the clones are tracked + * for garbage collection. + * + * @param key The AssetKey to set + */ + public void setKey(AssetKey key); + + /** + * Returns the asset key that is used to track this asset for garbage + * collection. + * + * @return the asset key that is used to track this asset for garbage + * collection. + */ + public AssetKey getKey(); +} diff --git a/jme3-core/src/main/java/com/jme3/asset/Desktop.cfg b/jme3-core/src/main/java/com/jme3/asset/Desktop.cfg new file mode 100644 index 000000000..18c82f5bc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/Desktop.cfg @@ -0,0 +1,24 @@ +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.cursors.plugins.CursorLoader : ani, cur, ico +LOADER com.jme3.material.plugins.J3MLoader : j3m +LOADER com.jme3.material.plugins.J3MLoader : j3md +LOADER com.jme3.material.plugins.ShaderNodeDefinitionLoader : j3sn +LOADER com.jme3.font.plugins.BitmapFontLoader : fnt +LOADER com.jme3.texture.plugins.DDSLoader : dds +LOADER com.jme3.texture.plugins.PFMLoader : pfm +LOADER com.jme3.texture.plugins.HDRLoader : hdr +LOADER com.jme3.texture.plugins.TGALoader : tga +LOADER com.jme3.export.binary.BinaryImporter : j3o +LOADER com.jme3.export.binary.BinaryImporter : j3f +LOADER com.jme3.scene.plugins.OBJLoader : obj +LOADER com.jme3.scene.plugins.MTLLoader : mtl +LOADER com.jme3.scene.plugins.ogre.MeshLoader : meshxml, mesh.xml +LOADER com.jme3.scene.plugins.ogre.SkeletonLoader : skeletonxml, skeleton.xml +LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material +LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene +LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend +LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib diff --git a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java new file mode 100644 index 000000000..083f85e25 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java @@ -0,0 +1,448 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.asset.cache.AssetCache; +import com.jme3.asset.cache.SimpleAssetCache; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioKey; +import com.jme3.font.BitmapFont; +import com.jme3.material.Material; +import com.jme3.post.FilterPostProcessor; +import com.jme3.renderer.Caps; +import com.jme3.scene.Spatial; +import com.jme3.shader.Glsl100ShaderGenerator; +import com.jme3.shader.Glsl150ShaderGenerator; +import com.jme3.shader.Shader; +import com.jme3.shader.ShaderGenerator; +import com.jme3.shader.ShaderKey; +import com.jme3.texture.Texture; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +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 ShaderGenerator shaderGenerator; + + private final ImplHandler handler = new ImplHandler(this); + + private CopyOnWriteArrayList eventListeners = + new CopyOnWriteArrayList(); + + private List classLoaders = + Collections.synchronizedList(new ArrayList()); + + 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){ + loadConfigFile(configFile); + } + logger.fine("DesktopAssetManager created."); + } + + private void loadConfigFile(URL configFile){ + 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){ + } + } + } + + public void addClassLoader(ClassLoader loader) { + classLoaders.add(loader); + } + + public void removeClassLoader(ClassLoader loader) { + classLoaders.remove(loader); + } + + public List getClassLoaders(){ + return Collections.unmodifiableList(classLoaders); + } + + public void addAssetEventListener(AssetEventListener listener) { + eventListeners.add(listener); + } + + public void removeAssetEventListener(AssetEventListener listener) { + eventListeners.remove(listener); + } + + public void clearAssetEventListeners() { + eventListeners.clear(); + } + + public void setAssetEventListener(AssetEventListener listener){ + eventListeners.clear(); + eventListeners.add(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 unregisterLoader(Class loaderClass) { + handler.removeLoader(loaderClass); + if (logger.isLoggable(Level.FINER)){ + logger.log(Level.FINER, "Unregistered loader: {0}", + loaderClass.getSimpleName()); + } + } + + 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 AssetInfo locateAsset(AssetKey key){ + AssetInfo info = handler.tryLocate(key); + if (info == null){ + logger.log(Level.WARNING, "Cannot locate resource: {0}", key); + } + return info; + } + + public T getFromCache(AssetKey key) { + AssetCache cache = handler.getCache(key.getCacheType()); + if (cache != null) { + T asset = cache.getFromCache(key); + if (asset != null) { + // Since getFromCache fills the load stack, it has to be popped + cache.notifyNoAssetClone(); + } + return asset; + } else { + throw new IllegalArgumentException("Key " + key + " specifies no cache."); + } + } + + public void addToCache(AssetKey key, T asset) { + AssetCache cache = handler.getCache(key.getCacheType()); + if (cache != null) { + cache.addToCache(key, asset); + cache.notifyNoAssetClone(); + } else { + throw new IllegalArgumentException("Key " + key + " specifies no cache."); + } + } + + public boolean deleteFromCache(AssetKey key) { + AssetCache cache = handler.getCache(key.getCacheType()); + if (cache != null) { + return cache.deleteFromCache(key); + } else { + throw new IllegalArgumentException("Key " + key + " specifies no cache."); + } + } + + public void clearCache(){ + handler.clearCache(); + if (logger.isLoggable(Level.FINER)){ + logger.log(Level.FINER, "All asset caches cleared."); + } + } + + /** + * Thread-safe. + * + * @param + * @param key + * @return the loaded asset + */ + public T loadAsset(AssetKey key){ + if (key == null) + throw new IllegalArgumentException("key cannot be null"); + + for (AssetEventListener listener : eventListeners){ + listener.assetRequested(key); + } + + AssetCache cache = handler.getCache(key.getCacheType()); + AssetProcessor proc = handler.getProcessor(key.getProcessorType()); + + Object obj = cache != null ? cache.getFromCache(key) : null; + if (obj == null){ + // Asset not in cache, load it from file system. + AssetLoader loader = handler.aquireLoader(key); + AssetInfo info = handler.tryLocate(key); + if (info == null){ + if (handler.getParentKey() != null){ + // Inform event listener that an asset has failed to load. + // If the parent AssetLoader chooses not to propagate + // the exception, this is the only means of finding + // that something went wrong. + for (AssetEventListener listener : eventListeners){ + listener.assetDependencyNotFound(handler.getParentKey(), key); + } + } + throw new AssetNotFoundException(key.toString()); + } + + try { + handler.establishParentKey(key); + obj = loader.load(info); + } catch (IOException ex) { + throw new AssetLoadException("An exception has occured while loading asset: " + key, ex); + } finally { + handler.releaseParentKey(key); + } + if (obj == null){ + throw new AssetLoadException("Error occured while loading asset \"" + key + "\" using " + loader.getClass().getSimpleName()); + }else{ + if (logger.isLoggable(Level.FINER)){ + logger.log(Level.FINER, "Loaded {0} with {1}", + new Object[]{key, loader.getClass().getSimpleName()}); + } + + if (proc != null){ + // do processing on asset before caching + obj = proc.postProcess(key, obj); + } + + if (cache != null){ + // At this point, obj should be of type T + cache.addToCache(key, (T) obj); + } + + for (AssetEventListener listener : eventListeners){ + listener.assetLoaded(key); + } + } + } + + // object obj is the original asset + // create an instance for user + T clone = (T) obj; + if (clone instanceof CloneableSmartAsset){ + if (proc == null){ + throw new IllegalStateException("Asset implements " + + "CloneableSmartAsset but doesn't " + + "have processor to handle cloning"); + }else{ + clone = (T) proc.createClone(obj); + if (cache != null && clone != obj){ + cache.registerAssetClone(key, clone); + } else{ + throw new IllegalStateException("Asset implements " + + "CloneableSmartAsset but doesn't have cache or " + + "was not cloned"); + } + } + } + + return clone; + } + + public Object loadAsset(String name){ + return loadAsset(new AssetKey(name)); + } + + public Texture loadTexture(TextureKey key){ + return (Texture) loadAsset(key); + } + + public Material loadMaterial(String name){ + return (Material) loadAsset(new MaterialKey(name)); + } + + public Texture loadTexture(String name){ + TextureKey key = new TextureKey(name, true); + key.setGenerateMips(true); + Texture tex = loadTexture(key); + logger.log(Level.FINE, "{0} - {1}", new Object[]{tex, tex.getMinFilter()}); + return tex; + } + + public AudioData loadAudio(AudioKey key){ + return (AudioData) loadAsset(key); + } + + public AudioData loadAudio(String name){ + return loadAudio(new AudioKey(name, false)); + } + + public BitmapFont loadFont(String name){ + return (BitmapFont) loadAsset(new AssetKey(name)); + } + + public Spatial loadModel(ModelKey key){ + return (Spatial) loadAsset(key); + } + + public Spatial loadModel(String name){ + return loadModel(new ModelKey(name)); + } + + public FilterPostProcessor loadFilter(FilterKey key){ + return (FilterPostProcessor) loadAsset(key); + } + + public FilterPostProcessor loadFilter(String name){ + return loadFilter(new FilterKey(name)); + } + + /** + * Load a vertex/fragment shader combo. + * + * @param key + * @return the loaded {@link Shader} + */ + public Shader loadShader(ShaderKey key){ + // cache abuse in method + // that doesn't use loaders/locators + AssetCache cache = handler.getCache(SimpleAssetCache.class); + Shader shader = (Shader) cache.getFromCache(key); + if (shader == null){ + if (key.isUsesShaderNodes()) { + if(shaderGenerator == null){ + throw new UnsupportedOperationException("ShaderGenerator was not initialized, make sure assetManager.getGenerator(caps) has been called"); + } + shader = shaderGenerator.generateShader(); + } else { + String vertName = key.getVertName(); + String fragName = key.getFragName(); + + String vertSource = (String) loadAsset(new AssetKey(vertName)); + String fragSource = (String) loadAsset(new AssetKey(fragName)); + + shader = new Shader(); + shader.initialize(); + shader.addSource(Shader.ShaderType.Vertex, vertName, vertSource, key.getDefines().getCompiled(), key.getVertexShaderLanguage()); + shader.addSource(Shader.ShaderType.Fragment, fragName, fragSource, key.getDefines().getCompiled(), key.getFragmentShaderLanguage()); + } + + cache.addToCache(key, shader); + } + return shader; + } + + /** + * {@inheritDoc} + */ + @Override + public ShaderGenerator getShaderGenerator(EnumSet caps) { + if (shaderGenerator == null) { + if(caps.contains(Caps.GLSL150)){ + shaderGenerator = new Glsl150ShaderGenerator(this); + }else{ + shaderGenerator = new Glsl100ShaderGenerator(this); + } + } + return shaderGenerator; + } + + /** + * {@inheritDoc} + */ + @Override + public void setShaderGenerator(ShaderGenerator shaderGenerator) { + this.shaderGenerator = shaderGenerator; + } + + +} diff --git a/jme3-core/src/main/java/com/jme3/asset/FilterKey.java b/jme3-core/src/main/java/com/jme3/asset/FilterKey.java new file mode 100644 index 000000000..1bb1f60f0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/FilterKey.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.asset.cache.AssetCache; +import com.jme3.post.FilterPostProcessor; + +/** + * Used to load FilterPostProcessors which are not cached. + * + * @author Andrew Wason + */ +public class FilterKey extends AssetKey { + + public FilterKey(String name) { + super(name); + } + + public FilterKey() { + super(); + } + + @Override + public Class getCacheType(){ + // Do not cache filter processors + return null; + } +} diff --git a/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java b/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java new file mode 100644 index 000000000..a68e46927 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.asset.cache.AssetCache; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +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 assetManager; + + private final ThreadLocal parentAssetKey + = new ThreadLocal(); + + private final CopyOnWriteArrayList> locatorsList = + new CopyOnWriteArrayList>(); + + private final HashMap, ImplThreadLocal> classToLoaderMap = + new HashMap, ImplThreadLocal>(); + + private final ConcurrentHashMap> extensionToLoaderMap = + new ConcurrentHashMap>(); + + private final ConcurrentHashMap, AssetProcessor> classToProcMap = + new ConcurrentHashMap, AssetProcessor>(); + + private final ConcurrentHashMap, AssetCache> classToCacheMap = + new ConcurrentHashMap, AssetCache>(); + + public ImplHandler(AssetManager assetManager){ + this.assetManager = assetManager; + } + + protected class ImplThreadLocal extends ThreadLocal { + + private final Class type; + private final String path; + private final String[] extensions; + + public ImplThreadLocal(Class type, String[] extensions){ + this.type = type; + this.extensions = extensions; + this.path = null; + } + + public ImplThreadLocal(Class type, String path){ + this.type = type; + this.path = path; + this.extensions = null; + } + + public ImplThreadLocal(Class type){ + this.type = type; + this.path = null; + this.extensions = null; + } + + public String getPath() { + return path; + } + + public String[] getExtensions(){ + return extensions; + } + + 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; + } + } + + /** + * Establishes the asset key that is used for tracking dependent assets + * that have failed to load. When set, the {@link DesktopAssetManager} + * gets a hint that it should suppress {@link AssetNotFoundException}s + * and instead call the listener callback (if set). + * + * @param parentKey The parent key + */ + public void establishParentKey(AssetKey parentKey){ + if (parentAssetKey.get() == null){ + parentAssetKey.set(parentKey); + } + } + + public void releaseParentKey(AssetKey parentKey){ + if (parentAssetKey.get() == parentKey){ + parentAssetKey.set(null); + } + } + + public AssetKey getParentKey(){ + return parentAssetKey.get(); + } + + /** + * Attempts to locate the given resource name. + * @param key The full name of the resource. + * @return The AssetInfo containing resource information required for + * access, or null if not found. + */ + public AssetInfo tryLocate(AssetKey key){ + if (locatorsList.isEmpty()){ + logger.warning("There are no locators currently"+ + " registered. Use AssetManager."+ + "registerLocator() to register a"+ + " locator."); + return null; + } + + for (ImplThreadLocal local : locatorsList){ + AssetLocator locator = (AssetLocator) local.get(); + if (local.getPath() != null){ + locator.setRootPath((String) local.getPath()); + } + AssetInfo info = locator.locate(assetManager, key); + if (info != null) + return info; + } + + return null; + } + + public int getLocatorCount(){ + return locatorsList.size(); + } + + /** + * Returns the AssetLoader registered for the given extension + * of the current thread. + * @return AssetLoader registered with addLoader. + */ + public AssetLoader aquireLoader(AssetKey key){ + // No need to synchronize() against map, its concurrent + ImplThreadLocal local = extensionToLoaderMap.get(key.getExtension()); + if (local == null){ + throw new IllegalStateException("No loader registered for type \"" + + key.getExtension() + "\""); + + } + return (AssetLoader) local.get(); + } + + public void clearCache(){ + // The iterator of the values collection is thread safe + for (AssetCache cache : classToCacheMap.values()){ + cache.clearCache(); + } + } + + public T getCache(Class cacheClass) { + if (cacheClass == null) { + return null; + } + + T cache = (T) classToCacheMap.get(cacheClass); + if (cache == null) { + synchronized (classToCacheMap) { + cache = (T) classToCacheMap.get(cacheClass); + if (cache == null) { + try { + cache = cacheClass.newInstance(); + classToCacheMap.put(cacheClass, cache); + } catch (InstantiationException ex) { + throw new IllegalArgumentException("The cache class cannot" + + " be created, ensure it has empty constructor", ex); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException("The cache class cannot " + + "be accessed", ex); + } + } + } + } + return cache; + } + + public T getProcessor(Class procClass){ + if (procClass == null) + return null; + + T proc = (T) classToProcMap.get(procClass); + if (proc == null){ + synchronized(classToProcMap){ + proc = (T) classToProcMap.get(procClass); + if (proc == null) { + try { + proc = procClass.newInstance(); + classToProcMap.put(procClass, proc); + } catch (InstantiationException ex) { + throw new IllegalArgumentException("The processor class cannot" + + " be created, ensure it has empty constructor", ex); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException("The processor class cannot " + + "be accessed", ex); + } + } + } + } + return proc; + } + + public void addLoader(final Class loaderType, String ... extensions){ + // Synchronized access must be used for any ops on classToLoaderMap + ImplThreadLocal local = new ImplThreadLocal(loaderType, extensions); + for (String extension : extensions){ + extension = extension.toLowerCase(); + synchronized (classToLoaderMap){ + classToLoaderMap.put(loaderType, local); + extensionToLoaderMap.put(extension, local); + } + } + } + + public void removeLoader(final Class loaderType){ + // Synchronized access must be used for any ops on classToLoaderMap + // Find the loader ImplThreadLocal for this class + synchronized (classToLoaderMap){ + ImplThreadLocal local = classToLoaderMap.get(loaderType); + // Remove it from the class->loader map + classToLoaderMap.remove(loaderType); + // Remove it from the extension->loader map + for (String extension : local.getExtensions()){ + extensionToLoaderMap.remove(extension); + } + } + } + + public void addLocator(final Class locatorType, String rootPath){ + locatorsList.add(new ImplThreadLocal(locatorType, rootPath)); + } + + public void removeLocator(final Class locatorType, String rootPath){ + ArrayList> locatorsToRemove = new ArrayList>(); + Iterator> it = locatorsList.iterator(); + + while (it.hasNext()){ + ImplThreadLocal locator = it.next(); + if (locator.getPath().equals(rootPath) && + locator.getTypeClass().equals(locatorType)){ + //it.remove(); + // copy on write list doesn't support iterator remove, + // must use temporary list + locatorsToRemove.add(locator); + } + } + + locatorsList.removeAll(locatorsToRemove); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/asset/MaterialKey.java b/jme3-core/src/main/java/com/jme3/asset/MaterialKey.java new file mode 100644 index 000000000..c7b8d7c53 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/MaterialKey.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.asset.cache.AssetCache; +import com.jme3.asset.cache.WeakRefCloneAssetCache; +import com.jme3.material.Material; + +/** + * Used for loading {@link Material materials} only (not material definitions!). + * Material instances use cloneable smart asset management so that they and any + * referenced textures will be collected when all instances of the material + * become unreachable. + * + * @author Kirill Vainer + */ +public class MaterialKey extends AssetKey { + + public MaterialKey(String name) { + super(name); + } + + public MaterialKey() { + super(); + } + + @Override + public Class getCacheType() { + return WeakRefCloneAssetCache.class; + } + + @Override + public Class getProcessorType() { + return CloneableAssetProcessor.class; + } +} diff --git a/jme3-core/src/main/java/com/jme3/asset/ModelKey.java b/jme3-core/src/main/java/com/jme3/asset/ModelKey.java new file mode 100644 index 000000000..fa47df648 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/ModelKey.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.asset.cache.AssetCache; +import com.jme3.asset.cache.WeakRefCloneAssetCache; +import com.jme3.scene.Spatial; + +/** + * Used to load model files, such as OBJ or Blender models. + * This uses cloneable smart asset management, so that when all clones of + * this model become unreachable, the original asset is purged from the cache, + * allowing textures, materials, shaders, etc referenced by the model to + * become collected. + * + * @author Kirill Vainer + */ +public class ModelKey extends AssetKey { + + public ModelKey(String name) { + super(name); + } + + public ModelKey() { + super(); + } + + @Override + public Class getCacheType(){ + return WeakRefCloneAssetCache.class; + } + + @Override + public Class getProcessorType(){ + return CloneableAssetProcessor.class; + } +} diff --git a/jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java b/jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java new file mode 100644 index 000000000..db25a1217 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.asset.cache.AssetCache; +import com.jme3.shader.ShaderNodeDefinition; +import java.util.List; + +/** + * Used for loading {@link ShaderNodeDefinition shader nodes definition} + * + * Tells if the defintion has to be loaded with or without its documentation + * + * @author Kirill Vainer + */ +public class ShaderNodeDefinitionKey extends AssetKey> { + + private boolean loadDocumentation = false; + + /** + * creates a ShaderNodeDefinitionKey + * + * @param name the name of the asset to load + */ + public ShaderNodeDefinitionKey(String name) { + super(name); + } + + /** + * creates a ShaderNodeDefinitionKey + */ + public ShaderNodeDefinitionKey() { + super(); + } + + @Override + public Class getCacheType() { + return null; + } + + /** + * + * @return true if the asset loaded with this key will contain its + * documentation + */ + public boolean isLoadDocumentation() { + return loadDocumentation; + } + + /** + * sets to true to load the documentation along with the + * ShaderNodeDefinition + * + * @param loadDocumentation true to load the documentation along with the + * ShaderNodeDefinition + */ + public void setLoadDocumentation(boolean loadDocumentation) { + this.loadDocumentation = loadDocumentation; + } +} diff --git a/jme3-core/src/main/java/com/jme3/asset/TextureKey.java b/jme3-core/src/main/java/com/jme3/asset/TextureKey.java new file mode 100644 index 000000000..8a565e2d7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/TextureKey.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.asset.cache.AssetCache; +import com.jme3.asset.cache.WeakRefCloneAssetCache; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.Type; +import com.jme3.texture.TextureProcessor; +import java.io.IOException; + +/** + * Used to load textures from image files such as JPG or PNG. + * Note that texture loaders actually load the asset as an {@link Image} + * object, which is then converted to a {@link Texture} in the + * {@link TextureProcessor#postProcess(com.jme3.asset.AssetKey, java.lang.Object) } + * method. Since textures are cloneable smart assets, the texture stored + * in the cache will be collected when all clones of the texture become + * unreachable. + * + * @author Kirill Vainer + */ +public class TextureKey extends AssetKey { + + private boolean generateMips; + private boolean flipY; + private boolean asCube; + private boolean asTexture3D; + private int anisotropy; + private Texture.Type textureTypeHint = Texture.Type.TwoDimensional; + + public TextureKey(String name, boolean flipY) { + super(name); + this.flipY = flipY; + } + + public TextureKey(String name) { + super(name); + this.flipY = true; + } + + public TextureKey() { + } + + @Override + public String toString() { + return name + (flipY ? " (Flipped)" : "") + (asCube ? " (Cube)" : "") + (generateMips ? " (Mipmapped)" : ""); + } + + @Override + public Class getCacheType(){ + return WeakRefCloneAssetCache.class; + } + + @Override + public Class getProcessorType(){ + return TextureProcessor.class; + } + + public boolean isFlipY() { + return flipY; + } + + public void setFlipY(boolean flipY) { + this.flipY = flipY; + } + + public int getAnisotropy() { + return anisotropy; + } + + public void setAnisotropy(int anisotropy) { + this.anisotropy = anisotropy; + } + + public boolean isAsCube() { + return asCube; + } + + public void setAsCube(boolean asCube) { + this.asCube = asCube; + } + + public boolean isGenerateMips() { + return generateMips; + } + + public void setGenerateMips(boolean generateMips) { + this.generateMips = generateMips; + } + + public boolean isAsTexture3D() { + return asTexture3D; + } + + public void setAsTexture3D(boolean asTexture3D) { + this.asTexture3D = asTexture3D; + } + + public Type getTextureTypeHint() { + return textureTypeHint; + } + + public void setTextureTypeHint(Type textureTypeHint) { + this.textureTypeHint = textureTypeHint; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TextureKey other = (TextureKey) obj; + if (!super.equals(obj)) { + return false; + } + if (this.generateMips != other.generateMips) { + return false; + } + if (this.flipY != other.flipY) { + return false; + } + if (this.asCube != other.asCube) { + return false; + } + if (this.asTexture3D != other.asTexture3D) { + return false; + } + if (this.anisotropy != other.anisotropy) { + return false; + } + if (this.textureTypeHint != other.textureTypeHint) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 17 * hash + (super.hashCode()); + hash = 17 * hash + (this.generateMips ? 1 : 0); + hash = 17 * hash + (this.flipY ? 1 : 0); + hash = 17 * hash + (this.asCube ? 1 : 0); + hash = 17 * hash + (this.asTexture3D ? 1 : 0); + hash = 17 * hash + this.anisotropy; + hash = 17 * hash + (this.textureTypeHint != null ? this.textureTypeHint.hashCode() : 0); + return hash; + } + + @Override + 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/jme3-core/src/main/java/com/jme3/asset/ThreadingManager.java b/jme3-core/src/main/java/com/jme3/asset/ThreadingManager.java new file mode 100644 index 000000000..28f1cdb04 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/ThreadingManager.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import java.util.concurrent.*; + +/** + * 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(Runtime.getRuntime().availableProcessors(), + 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, "jME3-threadpool-" + (nextThreadId++)); + t.setDaemon(true); + t.setPriority(Thread.MIN_PRIORITY); + return t; + } + } + + protected class LoadingTask implements Callable { + + private final AssetKey assetKey; + + public LoadingTask(AssetKey assetKey) { + this.assetKey = assetKey; + } + + public T call() throws Exception { + return owner.loadAsset(assetKey); + } + } + + public Future loadAsset(AssetKey assetKey) { + return executor.submit(new LoadingTask(assetKey)); + } + + public static boolean isLoadingThread() { + return Thread.currentThread().getName().startsWith("jME3-threadpool"); + } + + +} diff --git a/jme3-core/src/main/java/com/jme3/asset/cache/AssetCache.java b/jme3-core/src/main/java/com/jme3/asset/cache/AssetCache.java new file mode 100644 index 000000000..929c254a4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/cache/AssetCache.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset.cache; + +import com.jme3.asset.AssetKey; + +/** + * AssetCache is an interface for asset caches. + * Allowing storage of loaded resources in order to improve their access time + * if they are requested again in a short period of time. + * Depending on the asset type and how it is used, a specialized + * caching method can be selected that is most appropriate for that asset type. + * The asset cache must be thread safe. + *

    + * Some caches are used to manage cloneable assets, which track reachability + * based on a shared key in all instances exposed in user code. + * E.g. {@link WeakRefCloneAssetCache} uses this approach. + * For those particular caches, either {@link #registerAssetClone(com.jme3.asset.AssetKey, java.lang.Object) } + * or {@link #notifyNoAssetClone() } MUST be called to avoid memory + * leaking following a successful {@link #addToCache(com.jme3.asset.AssetKey, java.lang.Object) } + * or {@link #getFromCache(com.jme3.asset.AssetKey) } call! + * + * @author Kirill Vainer + */ +public interface AssetCache { + /** + * Adds an asset to the cache. + * Once added, it should be possible to retrieve the asset + * by using the {@link #getFromCache(com.jme3.asset.AssetKey) } method. + * However the caching criteria may at some point choose that the asset + * should be removed from the cache to save memory, in that case, + * {@link #getFromCache(com.jme3.asset.AssetKey) } will return null. + *

    Thread-Safe + * + * @param The type of the asset to cache. + * @param key The asset key that can be used to look up the asset. + * @param obj The asset data to cache. + */ + public void addToCache(AssetKey key, T obj); + + /** + * This should be called by the asset manager when it has successfully + * acquired a cached asset (with {@link #getFromCache(com.jme3.asset.AssetKey) }) + * and cloned it for use. + *

    Thread-Safe + * + * @param The type of the asset to register. + * @param key The asset key of the loaded asset (used to retrieve from cache) + * @param clone The clone of the asset retrieved from + * the cache. + */ + public void registerAssetClone(AssetKey key, T clone); + + /** + * Notifies the cache that even though the methods {@link #addToCache(com.jme3.asset.AssetKey, java.lang.Object) } + * or {@link #getFromCache(com.jme3.asset.AssetKey) } were used, there won't + * be a call to {@link #registerAssetClone(com.jme3.asset.AssetKey, java.lang.Object) } + * for some reason. For example, if an error occurred during loading + * or if the addToCache/getFromCache were used from user code. + */ + public void notifyNoAssetClone(); + + /** + * Retrieves an asset from the cache. + * It is possible to add an asset to the cache using + * {@link #addToCache(com.jme3.asset.AssetKey, java.lang.Object) }. + * The asset may be removed from the cache automatically even if + * it was added previously, in that case, this method will return null. + *

    Thread-Safe + * + * @param The type of the asset to retrieve + * @param key The key used to lookup the asset. + * @return The asset that was previously cached, or null if not found. + */ + public T getFromCache(AssetKey key); + + /** + * Deletes an asset from the cache. + *

    Thread-Safe + * + * @param key The asset key to find the asset to delete. + * @return True if the asset was successfully found in the cache + * and removed. + */ + public boolean deleteFromCache(AssetKey key); + + /** + * Deletes all assets from the cache. + *

    Thread-Safe + */ + public void clearCache(); +} diff --git a/jme3-core/src/main/java/com/jme3/asset/cache/SimpleAssetCache.java b/jme3-core/src/main/java/com/jme3/asset/cache/SimpleAssetCache.java new file mode 100644 index 000000000..0b3617ac0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/cache/SimpleAssetCache.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset.cache; + +import com.jme3.asset.AssetKey; +import java.util.concurrent.ConcurrentHashMap; + +/** + * SimpleAssetCache is an asset cache + * that caches assets without any automatic removal policy. The user + * is expected to manually call {@link #deleteFromCache(com.jme3.asset.AssetKey) } + * to delete any assets. + * + * @author Kirill Vainer + */ +public class SimpleAssetCache implements AssetCache { + + private final ConcurrentHashMap keyToAssetMap = new ConcurrentHashMap(); + + public void addToCache(AssetKey key, T obj) { + keyToAssetMap.put(key, obj); + } + + public void registerAssetClone(AssetKey key, T clone) { + } + + public T getFromCache(AssetKey key) { + return (T) keyToAssetMap.get(key); + } + + public boolean deleteFromCache(AssetKey key) { + return keyToAssetMap.remove(key) != null; + } + + public void clearCache() { + keyToAssetMap.clear(); + } + + public void notifyNoAssetClone() { + } + +} diff --git a/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefAssetCache.java b/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefAssetCache.java new file mode 100644 index 000000000..672412870 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefAssetCache.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset.cache; + +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetProcessor; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A garbage collector bound asset cache that handles non-clonable objects. + * This cache assumes that the asset given to the user is the same asset + * that has been stored in the cache, in other words, + * {@link AssetProcessor#createClone(java.lang.Object) } for that asset + * returns the same object as the argument. + * This implementation will remove the asset from the cache + * once the asset is no longer referenced in user code and memory is low, + * e.g. the VM feels like purging the weak references for that asset. + * + * @author Kirill Vainer + */ +public class WeakRefAssetCache implements AssetCache { + + private static final Logger logger = Logger.getLogger(WeakRefAssetCache.class.getName()); + + private final ReferenceQueue refQueue = new ReferenceQueue(); + + private final ConcurrentHashMap assetCache + = new ConcurrentHashMap(); + + private static class AssetRef extends WeakReference { + + private final AssetKey assetKey; + + public AssetRef(AssetKey assetKey, Object originalAsset, ReferenceQueue refQueue){ + super(originalAsset, refQueue); + this.assetKey = assetKey; + } + } + + private void removeCollectedAssets(){ + int removedAssets = 0; + for (AssetRef ref; (ref = (AssetRef)refQueue.poll()) != null;){ + // Asset was collected, note that at this point the asset cache + // might not even have this asset anymore, it is OK. + if (assetCache.remove(ref.assetKey) != null){ + removedAssets ++; + //System.out.println("WeakRefAssetCache: The asset " + ref.assetKey + " was purged from the cache"); + } + } + if (removedAssets >= 1) { + logger.log(Level.FINE, "WeakRefAssetCache: {0} assets were purged from the cache.", removedAssets); + } + } + + public void addToCache(AssetKey key, T obj) { + removeCollectedAssets(); + + // NOTE: Some thread issues can hapen if another + // thread is loading an asset with the same key .. + AssetRef ref = new AssetRef(key, obj, refQueue); + assetCache.put(key, ref); + +// Texture t = (Texture) obj; +// Image i = t.getImage(); +// System.out.println("add to cache " + System.identityHashCode(i)); + } + + public T getFromCache(AssetKey key) { + AssetRef ref = assetCache.get(key); + if (ref != null){ + return (T) ref.get(); + }else{ + return null; + } + } + + public boolean deleteFromCache(AssetKey key) { + return assetCache.remove(key) != null; + } + + public void clearCache() { + assetCache.clear(); + } + + public void registerAssetClone(AssetKey key, T clone) { + } + + public void notifyNoAssetClone() { + } +} diff --git a/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java b/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java new file mode 100644 index 000000000..46ddfd199 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset.cache; + +import com.jme3.asset.AssetKey; +import com.jme3.asset.CloneableSmartAsset; +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * caches cloneable assets in a weak-key + * cache, allowing them to be collected when memory is low. + * The cache stores weak references to the asset keys, so that + * when all clones of the original asset are collected, will cause the + * asset to be automatically removed from the cache. + * +* @author Kirill Vainer + */ +public class WeakRefCloneAssetCache implements AssetCache { + + private static final Logger logger = Logger.getLogger(WeakRefAssetCache.class.getName()); + + private final ReferenceQueue refQueue = new ReferenceQueue(); + + /** + * Maps cloned key to AssetRef which has a weak ref to the original + * key and a strong ref to the original asset. + */ + private final ConcurrentHashMap smartCache + = new ConcurrentHashMap(); + + /** + * Stored in the ReferenceQueue to find out when originalKey is collected + * by GC. Once collected, the clonedKey is used to remove the asset + * from the cache. + */ + private static final class KeyRef extends PhantomReference { + + AssetKey clonedKey; + + public KeyRef(AssetKey originalKey, ReferenceQueue refQueue) { + super(originalKey, refQueue); + clonedKey = originalKey.clone(); + } + } + + /** + * Stores the original key and original asset. + * The asset info contains a cloneable asset (e.g. the original, from + * which all clones are made). Also a weak reference to the + * original key which is used when the clones are produced. + */ + private static final class AssetRef extends WeakReference { + + CloneableSmartAsset asset; + + public AssetRef(CloneableSmartAsset originalAsset, AssetKey originalKey) { + super(originalKey); + this.asset = originalAsset; + } + } + + private final ThreadLocal> assetLoadStack + = new ThreadLocal>() { + @Override + protected ArrayList initialValue() { + return new ArrayList(); + } + }; + + private void removeCollectedAssets(){ + int removedAssets = 0; + for (KeyRef ref; (ref = (KeyRef)refQueue.poll()) != null;){ + // (Cannot use ref.get() since it was just collected by GC!) + AssetKey key = ref.clonedKey; + + // Asset was collected, note that at this point the asset cache + // might not even have this asset anymore, it is OK. + if (smartCache.remove(key) != null){ + removedAssets ++; + //System.out.println("WeakRefAssetCache: The asset " + ref.assetKey + " was purged from the cache"); + } + } + if (removedAssets >= 1) { + logger.log(Level.FINE, "WeakRefAssetCache: {0} assets were purged from the cache.", removedAssets); + } + } + + public void addToCache(AssetKey originalKey, T obj) { + // Make room for new asset + removeCollectedAssets(); + + CloneableSmartAsset asset = (CloneableSmartAsset) obj; + + // No circular references, since the original asset is + // strongly referenced, we don't want the key strongly referenced. + asset.setKey(null); + + // Start tracking the collection of originalKey + // (this adds the KeyRef to the ReferenceQueue) + KeyRef ref = new KeyRef(originalKey, refQueue); + + // Place the asset in the cache, but use a clone of + // the original key. + smartCache.put(ref.clonedKey, new AssetRef(asset, originalKey)); + + // Push the original key used to load the asset + // so that it can be set on the clone later + ArrayList loadStack = assetLoadStack.get(); + loadStack.add(originalKey); + } + + public void registerAssetClone(AssetKey key, T clone) { + ArrayList loadStack = assetLoadStack.get(); + ((CloneableSmartAsset)clone).setKey(loadStack.remove(loadStack.size() - 1)); + } + + public void notifyNoAssetClone() { + ArrayList loadStack = assetLoadStack.get(); + loadStack.remove(loadStack.size() - 1); + } + + public T getFromCache(AssetKey key) { + AssetRef smartInfo; + synchronized (smartCache){ + smartInfo = smartCache.get(key); + } + + if (smartInfo == null) { + return null; + } else { + // NOTE: Optimization so that registerAssetClone() + // can check this and determine that the asset clone + // belongs to the asset retrieved here. + AssetKey keyForTheClone = smartInfo.get(); + if (keyForTheClone == null){ + // The asset was JUST collected by GC + // (between here and smartCache.get) + return null; + } + + // Prevent original key from getting collected + // while an asset is loaded for it. + ArrayList loadStack = assetLoadStack.get(); + loadStack.add(keyForTheClone); + + return (T) smartInfo.asset; + } + } + + public boolean deleteFromCache(AssetKey key) { + ArrayList loadStack = assetLoadStack.get(); + + if (!loadStack.isEmpty()){ + throw new UnsupportedOperationException("Cache cannot be modified" + + "while assets are being loaded"); + } + + return smartCache.remove(key) != null; + } + + public void clearCache() { + ArrayList loadStack = assetLoadStack.get(); + + if (!loadStack.isEmpty()){ + throw new UnsupportedOperationException("Cache cannot be modified" + + "while assets are being loaded"); + } + + smartCache.clear(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/asset/package.html b/jme3-core/src/main/java/com/jme3/asset/package.html new file mode 100644 index 000000000..6b9fd2138 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/package.html @@ -0,0 +1,37 @@ + + + + + + + + + +com.jme3.asset contains the {@link com.jme3.asset.AssetManager}, +a utility class that is used to load assets such as textures, models, and +sound effects in a jME3 application.
    + +

    + +

    AssetLoaders

    +{@link com.jme3.asset.AssetLoader asset loaders} are registered to load +assets of a particular format. For example, an AssetLoader that +loads TGA images should read a stream in .tga format and return an +{@link com.jme3.texture.Image} object as its output. +AssetLoaders are initialized once a file of that format +is loaded, there's only one AssetLoader per thread so +AssetLoader's load() method does not have to be thread safe. + +

    AssetLocators

    +{@link com.jme3.asset.AssetLocator}(s) are used to resolve +an asset name (a string) into an {@link java.io.InputStream} which is +contained in an {@link com.jme3.asset.AssetInfo} object. +There are AssetLocators for loading files from the application's +classpath, the local hard drive, a ZIP file, an HTTP server, and more. The user +can implement their own AssetLocators and register them with the AssetManager +to load their resources from their own location. + + + + + diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioBuffer.java b/jme3-core/src/main/java/com/jme3/audio/AudioBuffer.java new file mode 100644 index 000000000..230299f5d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/AudioBuffer.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +import com.jme3.audio.AudioData.DataType; +import com.jme3.util.BufferUtils; +import com.jme3.util.NativeObject; +import java.nio.ByteBuffer; + +/** + * An AudioBuffer is an implementation of AudioData + * where the audio is buffered (stored in memory). All parts of it + * are accessible at any time.
    + * AudioBuffers are useful for short sounds, like effects, etc. + * + * @author Kirill Vainer + */ +public class AudioBuffer extends AudioData { + + /** + * The audio data buffer. Should be direct and native ordered. + */ + protected ByteBuffer audioData; + + public AudioBuffer(){ + super(); + } + + protected AudioBuffer(int id){ + super(id); + } + + public DataType getDataType() { + return DataType.Buffer; + } + + /** + * @return The duration of the audio in seconds. It is expected + * that audio is uncompressed. + */ + public float getDuration(){ + int bytesPerSec = (bitsPerSample / 8) * channels * sampleRate; + if (audioData != null) + return (float) audioData.limit() / 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(); + } + + @Override + protected void deleteNativeBuffers() { + if (audioData != null) { + BufferUtils.destroyDirectBuffer(audioData); + } + } + + @Override + public void deleteObject(Object rendererObject) { + ((AudioRenderer)rendererObject).deleteAudioData(this); + } + + @Override + public NativeObject createDestructableClone() { + return new AudioBuffer(id); + } + + @Override + public long getUniqueId() { + return ((long)OBJTYPE_AUDIOBUFFER << 32) | ((long)id); + } +} diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioContext.java b/jme3-core/src/main/java/com/jme3/audio/AudioContext.java new file mode 100644 index 000000000..5bdad3b4b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/AudioContext.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +/** + * Holds render thread specific audio context information. + * + * @author Paul Speed + */ +public class AudioContext { + + private static ThreadLocal audioRenderer = new ThreadLocal(); + + public static void setAudioRenderer( AudioRenderer ar ) { + audioRenderer.set(ar); + } + + public static AudioRenderer getAudioRenderer() { + return audioRenderer.get(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioData.java b/jme3-core/src/main/java/com/jme3/audio/AudioData.java new file mode 100644 index 000000000..750aa1f84 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/AudioData.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +import com.jme3.util.NativeObject; + +/** + * 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) are + * streamed from the hard drive as they are played. + * + * @author Kirill Vainer + */ +public abstract class AudioData extends NativeObject { + + protected int sampleRate; + protected int channels; + protected int bitsPerSample; + + public enum DataType { + Buffer, + Stream + } + + public AudioData(){ + super(); + } + + protected AudioData(int id){ + super(id); + } + + /** + * @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/jme3-core/src/main/java/com/jme3/audio/AudioKey.java b/jme3-core/src/main/java/com/jme3/audio/AudioKey.java new file mode 100644 index 000000000..de937eb69 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/AudioKey.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetProcessor; +import com.jme3.asset.cache.AssetCache; +import com.jme3.asset.cache.WeakRefAssetCache; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import java.io.IOException; + +/** + * AudioKey is extending AssetKey by holding stream flag. + * + * @author Kirill Vainer + */ +public class AudioKey extends AssetKey { + + private boolean stream; + private boolean streamCache; + + /** + * Create a new AudioKey. + * + * @param name Name of the asset + * @param stream If true, the audio will be streamed from harddrive, + * otherwise it will be buffered entirely and then played. + * @param streamCache If stream is true, then this specifies if + * the stream cache is used. When enabled, the audio stream will + * be read entirely but not decoded, allowing features such as + * seeking, determining duration and looping. + */ + public AudioKey(String name, boolean stream, boolean streamCache){ + this(name, stream); + this.streamCache = streamCache; + } + + /** + * Create a new AudioKey + * + * @param name Name of the asset + * @param stream If true, the audio will be streamed from harddrive, + * otherwise it will be buffered entirely and then played. + */ + public AudioKey(String name, boolean stream){ + super(name); + this.stream = stream; + } + + public AudioKey(String name){ + super(name); + this.stream = false; + } + + public AudioKey(){ + } + + @Override + public String toString(){ + return name + (stream ? + (streamCache ? + " (Stream/Cache)" : + " (Stream)") : + " (Buffer)"); + } + + /** + * @return True if the loaded audio should be a {@link AudioStream} or + * false if it should be a {@link AudioBuffer}. + */ + public boolean isStream() { + return stream; + } + + /** + * Specifies if the stream cache is used. + * + * When enabled, the audio stream will + * be read entirely but not decoded, allowing features such as + * seeking, looping and determining duration. + */ + public boolean useStreamCache(){ + return streamCache; + } + + @Override + public Class getCacheType() { + if ((stream && streamCache) || !stream) { + // Use non-cloning cache + return WeakRefAssetCache.class; + } else { + // Disable caching for streaming audio + return null; + } + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AudioKey other = (AudioKey) obj; + if (!super.equals(other)) { + return false; + } + if (this.stream != other.stream) { + return false; + } + if (this.streamCache != other.streamCache) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 67 * hash + (super.hashCode()); + hash = 67 * hash + (this.stream ? 1 : 0); + hash = 67 * hash + (this.streamCache ? 1 : 0); + return hash; + } + + @Override + public Class getProcessorType() { + return null; + } + + @Override + public void write(JmeExporter ex) throws IOException{ + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(stream, "do_stream", false); + oc.write(streamCache, "use_stream_cache", false); + } + + @Override + public void read(JmeImporter im) throws IOException{ + super.read(im); + InputCapsule ic = im.getCapsule(this); + stream = ic.readBoolean("do_stream", false); + streamCache = ic.readBoolean("use_stream_cache", false); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java new file mode 100644 index 000000000..0e75db9fe --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java @@ -0,0 +1,769 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.util.PlaceholderAssets; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * An AudioNode is a scene Node which can play audio assets. + * + * An AudioNode is either positional or ambient, with positional being the + * default. Once a positional node is attached to the scene, its location and + * velocity relative to the {@link Listener} affect how it sounds when played. + * Positional nodes can only play monoaural (single-channel) assets, not stereo + * ones. + * + * An ambient AudioNode plays in "headspace", meaning that the node's location + * and velocity do not affect how it sounds when played. Ambient audio nodes can + * play stereo assets. + * + * The "positional" property of an AudioNode can be set via + * {@link AudioNode#setPositional(boolean) }. + * + * @author normenhansen + * @author Kirill Vainer + */ +public class AudioNode extends Node implements AudioSource { + + //Version #1 : AudioKey is now stored into "audio_key" instead of "key" + public static final int SAVABLE_VERSION = 1; + protected boolean loop = false; + protected float volume = 1; + protected float pitch = 1; + protected float timeOffset = 0; + protected Filter dryFilter; + protected AudioKey audioKey; + protected transient AudioData data = null; + protected transient volatile AudioSource.Status status = AudioSource.Status.Stopped; + protected transient volatile int channel = -1; + protected Vector3f velocity = new Vector3f(); + protected boolean reverbEnabled = true; + protected float maxDistance = 200; // 200 meters + protected float refDistance = 10; // 10 meters + protected Filter reverbFilter; + private boolean directional = false; + protected Vector3f direction = new Vector3f(0, 0, 1); + protected float innerAngle = 360; + protected float outerAngle = 360; + protected boolean positional = true; + + /** + * Status indicates the current status of the audio node. + * @deprecated - use AudioSource.Status instead + */ + @Deprecated + public enum Status { + /** + * The audio node is currently playing. This will be set if + * {@link AudioNode#play() } is called. + */ + Playing, + + /** + * The audio node is currently paused. + */ + Paused, + + /** + * The audio node is currently stopped. + * This will be set if {@link AudioNode#stop() } is called + * or the audio has reached the end of the file. + */ + Stopped, + } + + /** + * Creates a new AudioNode without any audio data set. + */ + public AudioNode() { + } + + /** + * Creates a new AudioNode with the given data and key. + * + * @param audioData The audio data contains the audio track to play. + * @param audioKey The audio key that was used to load the AudioData + */ + public AudioNode(AudioData audioData, AudioKey audioKey) { + setAudioData(audioData, audioKey); + } + + /** + * Creates a new AudioNode with the given audio file. + * + * @param assetManager The asset manager to use to load the audio file + * @param name The filename of the audio file + * @param stream If true, the audio will be streamed gradually from disk, + * otherwise, it will be buffered. + * @param streamCache If stream is also true, then this specifies if + * the stream cache is used. When enabled, the audio stream will + * be read entirely but not decoded, allowing features such as + * seeking, looping and determining duration. + */ + public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) { + this.audioKey = new AudioKey(name, stream, streamCache); + this.data = (AudioData) assetManager.loadAsset(audioKey); + } + + /** + * Creates a new AudioNode with the given audio file. + * + * @param assetManager The asset manager to use to load the audio file + * @param name The filename of the audio file + * @param stream If true, the audio will be streamed gradually from disk, + * otherwise, it will be buffered. + */ + public AudioNode(AssetManager assetManager, String name, boolean stream) { + this(assetManager, name, stream, false); + } + + /** + * Creates a new AudioNode with the given audio file. + * + * @param audioRenderer The audio renderer to use for playing. Cannot be null. + * @param assetManager The asset manager to use to load the audio file + * @param name The filename of the audio file + * + * @deprecated AudioRenderer parameter is ignored. + */ + public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) { + this(assetManager, name, false); + } + + /** + * Creates a new AudioNode with the given audio file. + * + * @param assetManager The asset manager to use to load the audio file + * @param name The filename of the audio file + */ + public AudioNode(AssetManager assetManager, String name) { + this(assetManager, name, false); + } + + protected AudioRenderer getRenderer() { + AudioRenderer result = AudioContext.getAudioRenderer(); + if( result == null ) + throw new IllegalStateException( "No audio renderer available, make sure call is being performed on render thread." ); + return result; + } + + /** + * Start playing the audio. + */ + public void play(){ + if (positional && data.getChannels() > 1) { + throw new IllegalStateException("Only mono audio is supported for positional audio nodes"); + } + getRenderer().playSource(this); + } + + /** + * Start playing an instance of this audio. This method can be used + * to play the same AudioNode multiple times. Note + * that changes to the parameters of this AudioNode will not effect the + * instances already playing. + */ + public void playInstance(){ + if (positional && data.getChannels() > 1) { + throw new IllegalStateException("Only mono audio is supported for positional audio nodes"); + } + getRenderer().playSourceInstance(this); + } + + /** + * Stop playing the audio that was started with {@link AudioNode#play() }. + */ + public void stop(){ + getRenderer().stopSource(this); + } + + /** + * Pause the audio that was started with {@link AudioNode#play() }. + */ + public void pause(){ + getRenderer().pauseSource(this); + } + + /** + * Do not use. + */ + public final void setChannel(int channel) { + if (status != AudioSource.Status.Stopped) { + throw new IllegalStateException("Can only set source id when stopped"); + } + + this.channel = channel; + } + + /** + * Do not use. + */ + public int getChannel() { + return channel; + } + + /** + * @return The {#link Filter dry filter} that is set. + * @see AudioNode#setDryFilter(com.jme3.audio.Filter) + */ + public Filter getDryFilter() { + return dryFilter; + } + + /** + * Set the dry filter to use for this audio node. + * + * When {@link AudioNode#setReverbEnabled(boolean) reverb} is used, + * the dry filter will only influence the "dry" portion of the audio, + * e.g. not the reverberated parts of the AudioNode playing. + * + * See the relevent documentation for the {@link Filter} to determine + * the effect. + * + * @param dryFilter The filter to set, or null to disable dry filter. + */ + public void setDryFilter(Filter dryFilter) { + this.dryFilter = dryFilter; + if (channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.DryFilter); + } + + /** + * Set the audio data to use for the audio. Note that this method + * can only be called once, if for example the audio node was initialized + * without an {@link AudioData}. + * + * @param audioData The audio data contains the audio track to play. + * @param audioKey The audio key that was used to load the AudioData + */ + public void setAudioData(AudioData audioData, AudioKey audioKey) { + if (data != null) { + throw new IllegalStateException("Cannot change data once its set"); + } + + data = audioData; + this.audioKey = audioKey; + } + + /** + * @return The {@link AudioData} set previously with + * {@link AudioNode#setAudioData(com.jme3.audio.AudioData, com.jme3.audio.AudioKey) } + * or any of the constructors that initialize the audio data. + */ + public AudioData getAudioData() { + return data; + } + + /** + * @return The {@link Status} of the audio node. + * The status will be changed when either the {@link AudioNode#play() } + * or {@link AudioNode#stop() } methods are called. + */ + public AudioSource.Status getStatus() { + return status; + } + + /** + * Do not use. + */ + public final void setStatus(AudioSource.Status status) { + this.status = status; + } + + /** + * @return True if the audio will keep looping after it is done playing, + * otherwise, false. + * @see AudioNode#setLooping(boolean) + */ + public boolean isLooping() { + return loop; + } + + /** + * Set the looping mode for the audio node. The default is false. + * + * @param loop True if the audio should keep looping after it is done playing. + */ + public void setLooping(boolean loop) { + this.loop = loop; + if (channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.Looping); + } + + /** + * @return The pitch of the audio, also the speed of playback. + * + * @see AudioNode#setPitch(float) + */ + public float getPitch() { + return pitch; + } + + /** + * Set the pitch of the audio, also the speed of playback. + * The value must be between 0.5 and 2.0. + * + * @param pitch The pitch to set. + * @throws IllegalArgumentException If pitch is not between 0.5 and 2.0. + */ + public void setPitch(float pitch) { + if (pitch < 0.5f || pitch > 2.0f) { + throw new IllegalArgumentException("Pitch must be between 0.5 and 2.0"); + } + + this.pitch = pitch; + if (channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.Pitch); + } + + /** + * @return The volume of this audio node. + * + * @see AudioNode#setVolume(float) + */ + public float getVolume() { + return volume; + } + + /** + * Set the volume of this audio node. + * + * The volume is specified as gain. 1.0 is the default. + * + * @param volume The volume to set. + * @throws IllegalArgumentException If volume is negative + */ + public void setVolume(float volume) { + if (volume < 0f) { + throw new IllegalArgumentException("Volume cannot be negative"); + } + + this.volume = volume; + if (channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.Volume); + } + + /** + * @return the time offset in the sound sample when to start playing. + */ + public float getTimeOffset() { + return timeOffset; + } + + /** + * Set the time offset in the sound sample when to start playing. + * + * @param timeOffset The time offset + * @throws IllegalArgumentException If timeOffset is negative + */ + public void setTimeOffset(float timeOffset) { + if (timeOffset < 0f) { + throw new IllegalArgumentException("Time offset cannot be negative"); + } + + this.timeOffset = timeOffset; + if (data instanceof AudioStream) { + ((AudioStream) data).setTime(timeOffset); + }else if(status == AudioSource.Status.Playing){ + stop(); + play(); + } + } + + public Vector3f getPosition() { + return getWorldTranslation(); + } + + /** + * @return The velocity of the audio node. + * + * @see AudioNode#setVelocity(com.jme3.math.Vector3f) + */ + public Vector3f getVelocity() { + return velocity; + } + + /** + * Set the velocity of the audio node. The velocity is expected + * to be in meters. Does nothing if the audio node is not positional. + * + * @param velocity The velocity to set. + * @see AudioNode#setPositional(boolean) + */ + public void setVelocity(Vector3f velocity) { + this.velocity.set(velocity); + if (channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.Velocity); + } + + /** + * @return True if reverb is enabled, otherwise false. + * + * @see AudioNode#setReverbEnabled(boolean) + */ + public boolean isReverbEnabled() { + return reverbEnabled; + } + + /** + * Set to true to enable reverberation effects for this audio node. + * Does nothing if the audio node is not positional. + *
    + * When enabled, the audio environment set with + * {@link AudioRenderer#setEnvironment(com.jme3.audio.Environment) } + * will apply a reverb effect to the audio playing from this audio node. + * + * @param reverbEnabled True to enable reverb. + */ + public void setReverbEnabled(boolean reverbEnabled) { + this.reverbEnabled = reverbEnabled; + if (channel >= 0) { + getRenderer().updateSourceParam(this, AudioParam.ReverbEnabled); + } + } + + /** + * @return Filter for the reverberations of this audio node. + * + * @see AudioNode#setReverbFilter(com.jme3.audio.Filter) + */ + public Filter getReverbFilter() { + return reverbFilter; + } + + /** + * Set the reverb filter for this audio node. + *
    + * The reverb filter will influence the reverberations + * of the audio node playing. This only has an effect if + * reverb is enabled. + * + * @param reverbFilter The reverb filter to set. + * @see AudioNode#setDryFilter(com.jme3.audio.Filter) + */ + public void setReverbFilter(Filter reverbFilter) { + this.reverbFilter = reverbFilter; + if (channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.ReverbFilter); + } + + /** + * @return Max distance for this audio node. + * + * @see AudioNode#setMaxDistance(float) + */ + public float getMaxDistance() { + return maxDistance; + } + + /** + * Set the maximum distance for the attenuation of the audio node. + * Does nothing if the audio node is not positional. + *
    + * The maximum distance is the distance beyond which the audio + * node will no longer be attenuated. Normal attenuation is logarithmic + * from refDistance (it reduces by half when the distance doubles). + * Max distance sets where this fall-off stops and the sound will never + * get any quieter than at that distance. If you want a sound to fall-off + * very quickly then set ref distance very short and leave this distance + * very long. + * + * @param maxDistance The maximum playing distance. + * @throws IllegalArgumentException If maxDistance is negative + */ + public void setMaxDistance(float maxDistance) { + if (maxDistance < 0) { + throw new IllegalArgumentException("Max distance cannot be negative"); + } + + this.maxDistance = maxDistance; + if (channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.MaxDistance); + } + + /** + * @return The reference playing distance for the audio node. + * + * @see AudioNode#setRefDistance(float) + */ + public float getRefDistance() { + return refDistance; + } + + /** + * Set the reference playing distance for the audio node. + * Does nothing if the audio node is not positional. + *
    + * The reference playing distance is the distance at which the + * audio node will be exactly half of its volume. + * + * @param refDistance The reference playing distance. + * @throws IllegalArgumentException If refDistance is negative + */ + public void setRefDistance(float refDistance) { + if (refDistance < 0) { + throw new IllegalArgumentException("Reference distance cannot be negative"); + } + + this.refDistance = refDistance; + if (channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.RefDistance); + } + + /** + * @return True if the audio node is directional + * + * @see AudioNode#setDirectional(boolean) + */ + public boolean isDirectional() { + return directional; + } + + /** + * Set the audio node to be directional. + * Does nothing if the audio node is not positional. + *
    + * After setting directional, you should call + * {@link AudioNode#setDirection(com.jme3.math.Vector3f) } + * to set the audio node's direction. + * + * @param directional If the audio node is directional + */ + public void setDirectional(boolean directional) { + this.directional = directional; + if (channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.IsDirectional); + } + + /** + * @return The direction of this audio node. + * + * @see AudioNode#setDirection(com.jme3.math.Vector3f) + */ + public Vector3f getDirection() { + return direction; + } + + /** + * Set the direction of this audio node. + * Does nothing if the audio node is not directional. + * + * @param direction + * @see AudioNode#setDirectional(boolean) + */ + public void setDirection(Vector3f direction) { + this.direction = direction; + if (channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.Direction); + } + + /** + * @return The directional audio node, cone inner angle. + * + * @see AudioNode#setInnerAngle(float) + */ + public float getInnerAngle() { + return innerAngle; + } + + /** + * Set the directional audio node cone inner angle. + * Does nothing if the audio node is not directional. + * + * @param innerAngle The cone inner angle. + */ + public void setInnerAngle(float innerAngle) { + this.innerAngle = innerAngle; + if (channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.InnerAngle); + } + + /** + * @return The directional audio node, cone outer angle. + * + * @see AudioNode#setOuterAngle(float) + */ + public float getOuterAngle() { + return outerAngle; + } + + /** + * Set the directional audio node cone outer angle. + * Does nothing if the audio node is not directional. + * + * @param outerAngle The cone outer angle. + */ + public void setOuterAngle(float outerAngle) { + this.outerAngle = outerAngle; + if (channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.OuterAngle); + } + + /** + * @return True if the audio node is positional. + * + * @see AudioNode#setPositional(boolean) + */ + public boolean isPositional() { + return positional; + } + + /** + * Set the audio node as positional. + * The position, velocity, and distance parameters effect positional + * audio nodes. Set to false if the audio node should play in "headspace". + * + * @param positional True if the audio node should be positional, otherwise + * false if it should be headspace. + */ + public void setPositional(boolean positional) { + this.positional = positional; + if (channel >= 0) { + getRenderer().updateSourceParam(this, AudioParam.IsPositional); + } + } + + @Override + public void updateGeometricState(){ + boolean updatePos = false; + if ((refreshFlags & RF_TRANSFORM) != 0){ + updatePos = true; + } + + super.updateGeometricState(); + + if (updatePos && channel >= 0) + getRenderer().updateSourceParam(this, AudioParam.Position); + } + + @Override + public AudioNode clone(){ + AudioNode clone = (AudioNode) super.clone(); + + clone.direction = direction.clone(); + clone.velocity = velocity.clone(); + + return clone; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(audioKey, "audio_key", null); + oc.write(loop, "looping", false); + oc.write(volume, "volume", 1); + oc.write(pitch, "pitch", 1); + oc.write(timeOffset, "time_offset", 0); + oc.write(dryFilter, "dry_filter", null); + + oc.write(velocity, "velocity", null); + oc.write(reverbEnabled, "reverb_enabled", false); + oc.write(reverbFilter, "reverb_filter", null); + oc.write(maxDistance, "max_distance", 20); + oc.write(refDistance, "ref_distance", 10); + + oc.write(directional, "directional", false); + oc.write(direction, "direction", null); + oc.write(innerAngle, "inner_angle", 360); + oc.write(outerAngle, "outer_angle", 360); + + oc.write(positional, "positional", false); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + + // NOTE: In previous versions of jME3, audioKey was actually + // written with the name "key". This has been changed + // to "audio_key" in case Spatial's key will be written as "key". + if (ic.getSavableVersion(AudioNode.class) == 0){ + audioKey = (AudioKey) ic.readSavable("key", null); + }else{ + audioKey = (AudioKey) ic.readSavable("audio_key", null); + } + + loop = ic.readBoolean("looping", false); + volume = ic.readFloat("volume", 1); + pitch = ic.readFloat("pitch", 1); + timeOffset = ic.readFloat("time_offset", 0); + dryFilter = (Filter) ic.readSavable("dry_filter", null); + + velocity = (Vector3f) ic.readSavable("velocity", null); + reverbEnabled = ic.readBoolean("reverb_enabled", false); + reverbFilter = (Filter) ic.readSavable("reverb_filter", null); + maxDistance = ic.readFloat("max_distance", 20); + refDistance = ic.readFloat("ref_distance", 10); + + directional = ic.readBoolean("directional", false); + direction = (Vector3f) ic.readSavable("direction", null); + innerAngle = ic.readFloat("inner_angle", 360); + outerAngle = ic.readFloat("outer_angle", 360); + + positional = ic.readBoolean("positional", false); + + if (audioKey != null) { + try { + data = im.getAssetManager().loadAsset(audioKey); + } catch (AssetNotFoundException ex){ + Logger.getLogger(AudioNode.class.getName()).log(Level.FINE, "Cannot locate {0} for audio node {1}", new Object[]{audioKey, key}); + data = PlaceholderAssets.getPlaceholderAudio(); + } + } + } + + @Override + public String toString() { + String ret = getClass().getSimpleName() + + "[status=" + status; + if (volume != 1f) { + ret += ", vol=" + volume; + } + if (pitch != 1f) { + ret += ", pitch=" + pitch; + } + return ret + "]"; + } +} diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioParam.java b/jme3-core/src/main/java/com/jme3/audio/AudioParam.java new file mode 100644 index 000000000..a2cd4b58d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/AudioParam.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +public enum AudioParam { + Volume, + Pitch, + Looping, + Position, + IsPositional, + Direction, + IsDirectional, + Velocity, + OuterAngle, + InnerAngle, + RefDistance, + MaxDistance, + DryFilter, + ReverbFilter, + ReverbEnabled; +} diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java b/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java new file mode 100644 index 000000000..ee15bf77e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/AudioRenderer.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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 AudioSource#setReverbEnabled(boolean) + * @param env The environment to set. + */ + public void setEnvironment(Environment env); + + public void playSourceInstance(AudioSource src); + public void playSource(AudioSource src); + public void pauseSource(AudioSource src); + public void stopSource(AudioSource src); + + public void updateSourceParam(AudioSource src, AudioParam param); + public void updateListenerParam(Listener listener, ListenerParam param); + + public void deleteFilter(Filter filter); + public void deleteAudioData(AudioData ad); + + /** + * Initializes the renderer. Should be the first method called + * before using the system. + */ + public void initialize(); + + /** + * Update the audio system. Must be called periodically. + * @param tpf Time per frame. + */ + public void update(float tpf); + + /** + * Cleanup/destroy the audio system. Call this when app closes. + */ + public void cleanup(); +} diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioSource.java b/jme3-core/src/main/java/com/jme3/audio/AudioSource.java new file mode 100644 index 000000000..3aa23b78d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/AudioSource.java @@ -0,0 +1,176 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.audio; + +import com.jme3.math.Vector3f; + +/** + * + * @author normenhansen + */ +public interface AudioSource { + /** + * Status indicates the current status of the audio source. + */ + public enum Status { + /** + * The audio source is currently playing. This will be set if + * {@link AudioSource#play() } is called. + */ + Playing, + + /** + * The audio source is currently paused. + */ + Paused, + + /** + * The audio source is currently stopped. + * This will be set if {@link AudioSource#stop() } is called + * or the audio has reached the end of the file. + */ + Stopped, + } + + + /** + * Do not use. + */ + public void setChannel(int channel); + + /** + * Do not use. + */ + public int getChannel(); + + /** + * @return The {#link Filter dry filter} that is set. + * @see AudioSource#setDryFilter(com.jme3.audio.Filter) + */ + public Filter getDryFilter(); + + /** + * @return The {@link AudioData} set previously with + * {@link AudioSource#setAudioData(com.jme3.audio.AudioData, com.jme3.audio.AudioKey) } + * or any of the constructors that initialize the audio data. + */ + public AudioData getAudioData(); + + /** + * Do not use. + */ + public void setStatus(Status status); + + /** + * @return The {@link Status} of the audio source. + * The status will be changed when either the {@link AudioSource#play() } + * or {@link AudioSource#stop() } methods are called. + */ + public Status getStatus(); + + /** + * @return True if the audio will keep looping after it is done playing, + * otherwise, false. + * @see AudioSource#setLooping(boolean) + */ + public boolean isLooping(); + + /** + * @return The pitch of the audio, also the speed of playback. + * + * @see AudioSource#setPitch(float) + */ + public float getPitch(); + + /** + * @return The volume of this audio source. + * + * @see AudioSource#setVolume(float) + */ + public float getVolume(); + + /** + * @return the time offset in the sound sample when to start playing. + */ + public float getTimeOffset(); + + /** + * @return The velocity of the audio source. + * + * @see AudioSource#setVelocity(com.jme3.math.Vector3f) + */ + public Vector3f getPosition(); + + /** + * @return The velocity of the audio source. + * + * @see AudioSource#setVelocity(com.jme3.math.Vector3f) + */ + public Vector3f getVelocity(); + + /** + * @return True if reverb is enabled, otherwise false. + * + * @see AudioSource#setReverbEnabled(boolean) + */ + public boolean isReverbEnabled(); + + /** + * @return Filter for the reverberations of this audio source. + * + * @see AudioSource#setReverbFilter(com.jme3.audio.Filter) + */ + public Filter getReverbFilter(); + + /** + * @return Max distance for this audio source. + * + * @see AudioSource#setMaxDistance(float) + */ + public float getMaxDistance(); + + /** + * @return The reference playing distance for the audio source. + * + * @see AudioSource#setRefDistance(float) + */ + public float getRefDistance(); + + /** + * @return True if the audio source is directional + * + * @see AudioSource#setDirectional(boolean) + */ + public boolean isDirectional(); + + /** + * @return The direction of this audio source. + * + * @see AudioSource#setDirection(com.jme3.math.Vector3f) + */ + public Vector3f getDirection(); + + /** + * @return The directional audio source, cone inner angle. + * + * @see AudioSource#setInnerAngle(float) + */ + public float getInnerAngle(); + + /** + * @return The directional audio source, cone outer angle. + * + * @see AudioSource#setOuterAngle(float) + */ + public float getOuterAngle(); + + /** + * @return True if the audio source is positional. + * + * @see AudioSource#setPositional(boolean) + */ + public boolean isPositional(); + +} diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioStream.java b/jme3-core/src/main/java/com/jme3/audio/AudioStream.java new file mode 100644 index 000000000..d528e01fe --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/AudioStream.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +import com.jme3.util.NativeObject; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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{ + + private final static Logger logger = Logger.getLogger(AudioStream.class.getName()); + protected InputStream in; + protected float duration = -1f; + protected boolean open = false; + protected int[] ids; + + public AudioStream(){ + super(); + } + + protected AudioStream(int[] ids){ + // Pass some dummy ID so handle + // doesn't get created. + super(-1); + // This is what gets destroyed in reality + this.ids = ids; + } + + public void updateData(InputStream in, float duration){ + if (id != -1 || this.in != null) + throw new IllegalStateException("Data already set!"); + + this.in = in; + this.duration = duration; + open = true; + } + + /** + * Reads samples from the stream. The format of the data + * depends on the getSampleRate(), getChannels(), getBitsPerSample() + * values. + * + * @param buf Buffer where to read the samples + * @param offset The offset in the buffer where to read samples + * @param length The length inside the buffer where to read samples + * @return number of bytes read. + */ + public int readSamples(byte[] buf, int offset, int length){ + if (!open) + return -1; + + try{ + return in.read(buf, offset, length); + }catch (IOException ex){ + return -1; + } + } + + /** + * Reads samples from the stream. + * + * @see AudioStream#readSamples(byte[], int, int) + * @param buf Buffer where to read the samples + * @return number of bytes read. + */ + public int readSamples(byte[] buf){ + return readSamples(buf, 0, buf.length); + } + + public float getDuration(){ + return duration; + } + + @Override + public int getId(){ + throw new RuntimeException("Don't use getId() on streams"); + } + + @Override + public void setId(int id){ + throw new RuntimeException("Don't use setId() on streams"); + } + + public void initIds(int count){ + ids = new int[count]; + } + + public int getId(int index){ + return ids[index]; + } + + public void setId(int index, int id){ + ids[index] = id; + } + + public int[] getIds(){ + return ids; + } + + public void setIds(int[] ids){ + this.ids = ids; + } + + @Override + public DataType getDataType() { + return DataType.Stream; + } + + @Override + public void resetObject() { + id = -1; + ids = null; + setUpdateNeeded(); + } + + @Override + public void deleteObject(Object rendererObject) { + // It seems that the audio renderer is already doing a good + // job at deleting audio streams when they finish playing. +// ((AudioRenderer)rendererObject).deleteAudioData(this); + } + + @Override + public NativeObject createDestructableClone() { + return new AudioStream(ids); + } + + /** + * @return Whether the stream is open or not. Reading from a closed + * stream will always return eof. + */ + public boolean isOpen(){ + return open; + } + + /** + * Closes the stream, releasing all data relating to it. Reading + * from the stream will return eof. + * @throws IOException + */ + public void close() { + if (in != null && open){ + try{ + in.close(); + }catch (IOException ex){ + } + open = false; + }else{ + throw new RuntimeException("AudioStream is already closed!"); + } + } + + + public void setTime(float time){ + if(in instanceof SeekableStream){ + ((SeekableStream)in).setTime(time); + }else{ + logger.log(Level.WARNING,"Cannot use setTime on a stream that is not seekable. You must load the file with the streamCache option set to true"); + } + } + + @Override + public long getUniqueId() { + return ((long)OBJTYPE_AUDIOSTREAM << 32) | ((long)ids[0]); + } +} diff --git a/jme3-core/src/main/java/com/jme3/audio/Environment.java b/jme3-core/src/main/java/com/jme3/audio/Environment.java new file mode 100644 index 000000000..72327ef00 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/Environment.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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 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/jme3-core/src/main/java/com/jme3/audio/Filter.java b/jme3-core/src/main/java/com/jme3/audio/Filter.java new file mode 100644 index 000000000..b46270c11 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/Filter.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.Savable; +import com.jme3.util.NativeObject; +import java.io.IOException; + +public abstract class Filter extends NativeObject implements Savable { + + public Filter(){ + super(); + } + + protected Filter(int id){ + super(id); + } + + public void write(JmeExporter ex) throws IOException { + // nothing to save + } + + public void read(JmeImporter im) throws IOException { + // nothing to read + } + + @Override + public void resetObject() { + this.id = -1; + setUpdateNeeded(); + } + + @Override + public void deleteObject(Object rendererObject) { + ((AudioRenderer)rendererObject).deleteFilter(this); + } + + @Override + public abstract NativeObject createDestructableClone(); + +} diff --git a/jme3-core/src/main/java/com/jme3/audio/Listener.java b/jme3-core/src/main/java/com/jme3/audio/Listener.java new file mode 100644 index 000000000..8e59eac0c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/Listener.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; + +public class Listener { + + private Vector3f location; + private Vector3f velocity; + private Quaternion rotation; + private float volume = 1; + private AudioRenderer renderer; + + public Listener(){ + location = new Vector3f(); + velocity = new Vector3f(); + rotation = new Quaternion(); + } + + public Listener(Listener source){ + location = source.location.clone(); + velocity = source.velocity.clone(); + rotation = source.rotation.clone(); + volume = source.volume; + } + + public void setRenderer(AudioRenderer renderer){ + this.renderer = renderer; + } + + public float getVolume() { + return volume; + } + + public void setVolume(float volume) { + this.volume = volume; + if (renderer != null) + renderer.updateListenerParam(this, ListenerParam.Volume); + } + + public Vector3f getLocation() { + return location; + } + + public Quaternion getRotation() { + return rotation; + } + + public Vector3f getVelocity() { + return velocity; + } + + public Vector3f getLeft(){ + return rotation.getRotationColumn(0); + } + + public Vector3f getUp(){ + return rotation.getRotationColumn(1); + } + + public Vector3f getDirection(){ + return rotation.getRotationColumn(2); + } + + public void setLocation(Vector3f location) { + this.location.set(location); + if (renderer != null) + renderer.updateListenerParam(this, ListenerParam.Position); + } + + public void setRotation(Quaternion rotation) { + this.rotation.set(rotation); + if (renderer != null) + renderer.updateListenerParam(this, ListenerParam.Rotation); + } + + public void setVelocity(Vector3f velocity) { + this.velocity.set(velocity); + if (renderer != null) + renderer.updateListenerParam(this, ListenerParam.Velocity); + } +} diff --git a/jme3-core/src/main/java/com/jme3/audio/ListenerParam.java b/jme3-core/src/main/java/com/jme3/audio/ListenerParam.java new file mode 100644 index 000000000..c9a46021e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/ListenerParam.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +public enum ListenerParam { + Position, + Velocity, + Rotation, + Volume; +} diff --git a/jme3-core/src/main/java/com/jme3/audio/LowPassFilter.java b/jme3-core/src/main/java/com/jme3/audio/LowPassFilter.java new file mode 100644 index 000000000..4445c2463 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/LowPassFilter.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.util.NativeObject; +import java.io.IOException; + +public class LowPassFilter extends Filter { + + protected float volume, highFreqVolume; + + public LowPassFilter(float volume, float highFreqVolume) { + super(); + setVolume(volume); + setHighFreqVolume(highFreqVolume); + } + + protected LowPassFilter(int id){ + super(id); + } + + public float getHighFreqVolume() { + return highFreqVolume; + } + + public void setHighFreqVolume(float highFreqVolume) { + if (highFreqVolume < 0 || highFreqVolume > 1) + throw new IllegalArgumentException("High freq volume must be between 0 and 1"); + + this.highFreqVolume = highFreqVolume; + this.updateNeeded = true; + } + + public float getVolume() { + return volume; + } + + public void setVolume(float volume) { + if (volume < 0 || volume > 1) + throw new IllegalArgumentException("Volume must be between 0 and 1"); + + this.volume = volume; + this.updateNeeded = true; + } + + public void write(JmeExporter ex) throws IOException{ + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(volume, "volume", 0); + oc.write(highFreqVolume, "hf_volume", 0); + } + + @Override + public void read(JmeImporter im) throws IOException{ + super.read(im); + InputCapsule ic = im.getCapsule(this); + volume = ic.readFloat("volume", 0); + highFreqVolume = ic.readFloat("hf_volume", 0); + } + + @Override + public NativeObject createDestructableClone() { + return new LowPassFilter(id); + } + + @Override + public long getUniqueId() { + return ((long)OBJTYPE_FILTER << 32) | ((long)id); + } +} diff --git a/jme3-core/src/main/java/com/jme3/audio/SeekableStream.java b/jme3-core/src/main/java/com/jme3/audio/SeekableStream.java new file mode 100644 index 000000000..85cd44ab4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/SeekableStream.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +/** + * + * @author Nehon + */ +public interface SeekableStream{ + + public void setTime(float time); + +} diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java new file mode 100644 index 000000000..07474d46d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java @@ -0,0 +1,1020 @@ +/* + * Copyright (c) 2009-2013 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bounding; + +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.collision.UnsupportedCollisionException; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.*; +import com.jme3.scene.Mesh; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.FloatBuffer; +//import com.jme.scene.TriMesh; + +/** + * BoundingBox describes a bounding volume as an axis-aligned box. + *
    + * Instances may be initialized by invoking the containAABB method. + * + * @author Joshua Slack + * @version $Id: BoundingBox.java,v 1.50 2007/09/22 16:46:35 irrisor Exp $ + */ +public class BoundingBox extends BoundingVolume { + /** + * the X-extent of the box (>=0, may be +Infinity) + */ + float xExtent; + /** + * the Y-extent of the box (>=0, may be +Infinity) + */ + float yExtent; + /** + * the Z-extent of the box (>=0, may be +Infinity) + */ + float zExtent; + + /** + * Instantiate a BoundingBox without initializing it. + */ + public BoundingBox() { + } + + /** + * Instantiate a BoundingBox with given center and extents. + * + * @param c the coordinates of the center of the box (not null, not altered) + * @param x the X-extent of the box (>=0, may be +Infinity) + * @param y the Y-extent of the box (>=0, may be +Infinity) + * @param z the Z-extent of the box (>=0, may be +Infinity) + */ + public BoundingBox(Vector3f c, float x, float y, float z) { + this.center.set(c); + this.xExtent = x; + this.yExtent = y; + this.zExtent = z; + } + + /** + * Instantiate a BoundingBox equivalent to an existing box. + * + * @param source the existing box (not null, not altered) + */ + 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(); + + Vector3f min = vars.vect1.set(new Vector3f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)); + Vector3f max = vars.vect2.set(new Vector3f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY)); + + Vector3f point; + for (int i = start; i < end; i++) { + point = tris[i].get(0); + checkMinMax(min, max, point); + point = tris[i].get(1); + checkMinMax(min, max, point); + point = tris[i].get(2); + checkMinMax(min, max, point); + } + + center.set(min.addLocal(max)); + center.multLocal(0.5f); + + xExtent = max.x - center.x; + yExtent = max.y - center.y; + zExtent = max.z - center.z; + + vars.release(); + } + + public void computeFromTris(int[] indices, Mesh mesh, int start, int end) { + if (end - start <= 0) { + return; + } + + TempVars vars = TempVars.get(); + + Vector3f vect1 = vars.vect1; + Vector3f vect2 = vars.vect2; + Triangle triangle = vars.triangle; + + Vector3f min = vect1.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); + Vector3f max = vect2.set(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); + Vector3f point; + + for (int i = start; i < end; i++) { + mesh.getTriangle(indices[i], triangle); + point = triangle.get(0); + checkMinMax(min, max, point); + point = triangle.get(1); + checkMinMax(min, max, point); + point = triangle.get(2); + checkMinMax(min, max, point); + } + + center.set(min.addLocal(max)); + center.multLocal(0.5f); + + xExtent = max.x - center.x; + yExtent = max.y - center.y; + zExtent = max.z - center.z; + + vars.release(); + } + + public static void checkMinMax(Vector3f min, Vector3f max, Vector3f point) { + if (point.x < min.x) { + min.x = point.x; + } + if (point.x > max.x) { + max.x = point.x; + } + if (point.y < min.y) { + min.y = point.y; + } + if (point.y > max.y) { + max.y = point.y; + } + if (point.z < min.z) { + min.z = point.z; + } + if (point.z > max.z) { + max.z = point.z; + } + } + + /** + * 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(); + + float[] tmpArray = vars.skinPositions; + + float minX = Float.POSITIVE_INFINITY, minY = Float.POSITIVE_INFINITY, minZ = Float.POSITIVE_INFINITY; + float maxX = Float.NEGATIVE_INFINITY, maxY = Float.NEGATIVE_INFINITY, maxZ = Float.NEGATIVE_INFINITY; + + int iterations = (int) FastMath.ceil(points.limit() / ((float) tmpArray.length)); + for (int i = iterations - 1; i >= 0; i--) { + int bufLength = Math.min(tmpArray.length, points.remaining()); + points.get(tmpArray, 0, bufLength); + + for (int j = 0; j < bufLength; j += 3) { + vars.vect1.x = tmpArray[j]; + vars.vect1.y = tmpArray[j+1]; + vars.vect1.z = tmpArray[j+2]; + + if (vars.vect1.x < minX) { + minX = vars.vect1.x; + } + if (vars.vect1.x > maxX) { + maxX = vars.vect1.x; + } + + if (vars.vect1.y < minY) { + minY = vars.vect1.y; + } + if (vars.vect1.y > maxY) { + maxY = vars.vect1.y; + } + + if (vars.vect1.z < minZ) { + minZ = vars.vect1.z; + } + if (vars.vect1.z > maxZ) { + maxZ = vars.vect1.z; + } + } + } + + vars.release(); + + center.set(minX + maxX, minY + maxY, minZ + maxZ); + center.multLocal(0.5f); + + xExtent = maxX - center.x; + yExtent = maxY - center.y; + zExtent = maxZ - center.z; + } + + /** + * transform modifies the center of the box to reflect the + * change made via a rotation, translation and scale. + * + * @param trans + * the transform to apply + * @param store + * box to store result in + */ + public BoundingVolume transform(Transform trans, BoundingVolume store) { + + BoundingBox box; + if (store == null || store.getType() != Type.AABB) { + box = new BoundingBox(); + } else { + box = (BoundingBox) store; + } + + center.mult(trans.getScale(), box.center); + trans.getRotation().mult(box.center, box.center); + box.center.addLocal(trans.getTranslation()); + + TempVars vars = TempVars.get(); + + Matrix3f transMatrix = vars.tempMat3; + transMatrix.set(trans.getRotation()); + // Make the rotation matrix all positive to get the maximum x/y/z extent + transMatrix.absoluteLocal(); + + Vector3f scale = trans.getScale(); + vars.vect1.set(xExtent * scale.x, yExtent * scale.y, zExtent * scale.z); + transMatrix.mult(vars.vect1, vars.vect2); + // Assign the biggest rotations after scales. + box.xExtent = FastMath.abs(vars.vect2.getX()); + box.yExtent = FastMath.abs(vars.vect2.getY()); + box.zExtent = FastMath.abs(vars.vect2.getZ()); + + vars.release(); + + return box; + } + + public BoundingVolume transform(Matrix4f trans, BoundingVolume store) { + BoundingBox box; + if (store == null || store.getType() != Type.AABB) { + box = new BoundingBox(); + } else { + box = (BoundingBox) store; + } + TempVars vars = TempVars.get(); + + + float w = trans.multProj(center, box.center); + box.center.divideLocal(w); + + Matrix3f transMatrix = vars.tempMat3; + trans.toRotationMatrix(transMatrix); + + // Make the rotation matrix all positive to get the maximum x/y/z extent + transMatrix.absoluteLocal(); + + vars.vect1.set(xExtent, yExtent, zExtent); + transMatrix.mult(vars.vect1, vars.vect1); + + // Assign the biggest rotations after scales. + box.xExtent = FastMath.abs(vars.vect1.getX()); + box.yExtent = FastMath.abs(vars.vect1.getY()); + box.zExtent = FastMath.abs(vars.vect1.getZ()); + + vars.release(); + + return box; + } + + /** + * 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 bounding box locally with a second + * bounding volume. The result contains both the original box and the second + * volume. + * + * @param volume the bounding volume to combine with this box (or null) (not + * altered) + * @return this box (with its components modified) or null if the second + * volume is of some type other than AABB or Sphere + */ + public BoundingVolume merge(BoundingVolume volume) { + return mergeLocal(volume); + } + + /** + * mergeLocal combines this bounding box locally with a second + * bounding volume. The result contains both the original box and the second + * volume. + * + * @param volume the bounding volume to combine with this box (or null) (not + * altered) + * @return this box (with its components modified) or null if the second + * volume is of some type other than AABB or Sphere + */ + public BoundingVolume mergeLocal(BoundingVolume volume) { + if (volume == null) { + return this; + } + + switch (volume.getType()) { + case AABB: + BoundingBox vBox = (BoundingBox) volume; + return mergeLocal(vBox.center, vBox.xExtent, vBox.yExtent, + vBox.zExtent); + + case Sphere: + BoundingSphere vSphere = (BoundingSphere) volume; + return mergeLocal(vSphere.center, vSphere.radius, + vSphere.radius, vSphere.radius); + +// 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; +// } + /** + * mergeLocal combines this bounding box locally with a second + * bounding box described by its center and extents. + * + * @param c the center of the second box (not null, not altered) + * @param x the X-extent of the second box + * @param y the Y-extent of the second box + * @param z the Z-extent of the second box + * @return the resulting merged box. + */ + private BoundingBox mergeLocal(Vector3f c, float x, float y, float z) { + if (xExtent == Float.POSITIVE_INFINITY + || x == Float.POSITIVE_INFINITY) { + center.x = 0; + xExtent = Float.POSITIVE_INFINITY; + } else { + float low = center.x - xExtent; + if (low > c.x - x) { + low = c.x - x; + } + float high = center.x + xExtent; + if (high < c.x + x) { + high = c.x + x; + } + center.x = (low + high) / 2; + xExtent = high - center.x; + } + + if (yExtent == Float.POSITIVE_INFINITY + || y == Float.POSITIVE_INFINITY) { + center.y = 0; + yExtent = Float.POSITIVE_INFINITY; + } else { + float low = center.y - yExtent; + if (low > c.y - y) { + low = c.y - y; + } + float high = center.y + yExtent; + if (high < c.y + y) { + high = c.y + y; + } + center.y = (low + high) / 2; + yExtent = high - center.y; + } + + if (zExtent == Float.POSITIVE_INFINITY + || z == Float.POSITIVE_INFINITY) { + center.z = 0; + zExtent = Float.POSITIVE_INFINITY; + } else { + float low = center.z - zExtent; + if (low > c.z - z) { + low = c.z - z; + } + float high = center.z + zExtent; + if (high < c.z + z) { + high = c.z + z; + } + center.z = (low + high) / 2; + zExtent = high - center.z; + } + + return this; + } + + /** + * 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: "[Center: xExtent: X.XX yExtent: Y.YY zExtent: + * Z.ZZ]". + * + * @return the string representation of this. + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [Center: " + center + " xExtent: " + + xExtent + " yExtent: " + yExtent + " zExtent: " + zExtent + + "]"; + } + + /** + * intersects determines if this Bounding Box intersects with another given + * bounding volume. If so, true is returned, otherwise, false is returned. + * + * @see BoundingVolume#intersects(com.jme3.bounding.BoundingVolume) + */ + public boolean intersects(BoundingVolume bv) { + return bv.intersectsBoundingBox(this); + } + + /** + * determines if this bounding box intersects a given bounding sphere. + * + * @see BoundingVolume#intersectsSphere(com.jme3.bounding.BoundingSphere) + */ + public boolean intersectsSphere(BoundingSphere bs) { + assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bs.center); + + if (FastMath.abs(center.x - bs.center.x) < bs.getRadius() + + xExtent + && FastMath.abs(center.y - bs.center.y) < bs.getRadius() + + yExtent + && FastMath.abs(center.z - bs.center.z) < bs.getRadius() + + zExtent) { + return true; + } + + return false; + } + + /** + * determines if this bounding box intersects a given bounding box. If the + * two boxes intersect in any way, true is returned. Otherwise, false is + * returned. + * + * @see BoundingVolume#intersectsBoundingBox(com.jme3.bounding.BoundingBox) + */ + public boolean intersectsBoundingBox(BoundingBox bb) { + assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bb.center); + + if (center.x + xExtent < bb.center.x - bb.xExtent + || center.x - xExtent > bb.center.x + bb.xExtent) { + return false; + } else if (center.y + yExtent < bb.center.y - bb.yExtent + || center.y - yExtent > bb.center.y + bb.yExtent) { + return false; + } else if (center.z + zExtent < bb.center.z - bb.zExtent + || center.z - zExtent > bb.center.z + bb.zExtent) { + return false; + } else { + return true; + } + } + + /** + * determines if this bounding box intersects with a given oriented bounding + * box. + * + * @see com.jme.bounding.BoundingVolume#intersectsOrientedBoundingBox(com.jme.bounding.OrientedBoundingBox) + */ +// public boolean intersectsOrientedBoundingBox(OrientedBoundingBox obb) { +// return obb.intersectsBoundingBox(this); +// } + /** + * determines if this bounding box intersects with a given ray object. If an + * intersection has occurred, true is returned, otherwise false is returned. + * + * @see BoundingVolume#intersects(com.jme3.math.Ray) + */ + public boolean intersects(Ray ray) { + assert Vector3f.isValidVector(center); + + float rhs; + + TempVars vars = TempVars.get(); + + Vector3f diff = ray.origin.subtract(getCenter(vars.vect2), vars.vect1); + + final float[] fWdU = vars.fWdU; + final float[] fAWdU = vars.fAWdU; + final float[] fDdU = vars.fDdU; + final float[] fADdU = vars.fADdU; + final float[] fAWxDdU = vars.fAWxDdU; + + fWdU[0] = ray.getDirection().dot(Vector3f.UNIT_X); + fAWdU[0] = FastMath.abs(fWdU[0]); + fDdU[0] = diff.dot(Vector3f.UNIT_X); + fADdU[0] = FastMath.abs(fDdU[0]); + if (fADdU[0] > xExtent && fDdU[0] * fWdU[0] >= 0.0) { + vars.release(); + return false; + } + + fWdU[1] = ray.getDirection().dot(Vector3f.UNIT_Y); + fAWdU[1] = FastMath.abs(fWdU[1]); + fDdU[1] = diff.dot(Vector3f.UNIT_Y); + fADdU[1] = FastMath.abs(fDdU[1]); + if (fADdU[1] > yExtent && fDdU[1] * fWdU[1] >= 0.0) { + vars.release(); + return false; + } + + fWdU[2] = ray.getDirection().dot(Vector3f.UNIT_Z); + fAWdU[2] = FastMath.abs(fWdU[2]); + fDdU[2] = diff.dot(Vector3f.UNIT_Z); + fADdU[2] = FastMath.abs(fDdU[2]); + if (fADdU[2] > zExtent && fDdU[2] * fWdU[2] >= 0.0) { + vars.release(); + return false; + } + + Vector3f wCrossD = ray.getDirection().cross(diff, vars.vect2); + + fAWxDdU[0] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_X)); + rhs = yExtent * fAWdU[2] + zExtent * fAWdU[1]; + if (fAWxDdU[0] > rhs) { + vars.release(); + return false; + } + + fAWxDdU[1] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_Y)); + rhs = xExtent * fAWdU[2] + zExtent * fAWdU[0]; + if (fAWxDdU[1] > rhs) { + vars.release(); + return false; + } + + fAWxDdU[2] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_Z)); + rhs = xExtent * fAWdU[1] + yExtent * fAWdU[0]; + if (fAWxDdU[2] > rhs) { + vars.release(); + return false; + } + + vars.release(); + return true; + } + + /** + * @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray) + */ + private int collideWithRay(Ray ray, CollisionResults results) { + TempVars vars = TempVars.get(); + + Vector3f diff = vars.vect1.set(ray.origin).subtractLocal(center); + Vector3f direction = vars.vect2.set(ray.direction); + + float[] t = {0f, Float.POSITIVE_INFINITY}; + + float saveT0 = t[0], saveT1 = t[1]; + boolean notEntirelyClipped = clip(+direction.x, -diff.x - xExtent, t) + && clip(-direction.x, +diff.x - xExtent, t) + && clip(+direction.y, -diff.y - yExtent, t) + && clip(-direction.y, +diff.y - yExtent, t) + && clip(+direction.z, -diff.z - zExtent, t) + && clip(-direction.z, +diff.z - zExtent, t); + vars.release(); + + if (notEntirelyClipped && (t[0] != saveT0 || t[1] != saveT1)) { + if (t[1] > t[0]) { + float[] distances = t; + Vector3f[] points = new Vector3f[]{ + new Vector3f(ray.direction).multLocal(distances[0]).addLocal(ray.origin), + new Vector3f(ray.direction).multLocal(distances[1]).addLocal(ray.origin) + }; + + CollisionResult result = new CollisionResult(points[0], distances[0]); + results.addCollision(result); + result = new CollisionResult(points[1], distances[1]); + results.addCollision(result); + return 2; + } + + Vector3f point = new Vector3f(ray.direction).multLocal(t[0]).addLocal(ray.origin); + CollisionResult result = new CollisionResult(point, t[0]); + results.addCollision(result); + return 1; + } + return 0; + } + + public int collideWith(Collidable other, CollisionResults results) { + if (other instanceof Ray) { + Ray ray = (Ray) other; + return collideWithRay(ray, results); + } else if (other instanceof Triangle) { + Triangle t = (Triangle) other; + if (intersects(t.get1(), t.get2(), t.get3())) { + CollisionResult r = new CollisionResult(); + results.addCollision(r); + return 1; + } + return 0; + } else { + throw new UnsupportedCollisionException("With: " + other.getClass().getSimpleName()); + } + } + + /** + * C code ported from + * http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/tribox3.txt + * + * @param v1 The first point in the triangle + * @param v2 The second point in the triangle + * @param v3 The third point in the triangle + * @return True if the bounding box intersects the triangle, false + * otherwise. + */ + public boolean intersects(Vector3f v1, Vector3f v2, Vector3f v3) { + return Intersection.intersect(this, v1, v2, v3); + } + + @Override + public boolean contains(Vector3f point) { + return FastMath.abs(center.x - point.x) < xExtent + && FastMath.abs(center.y - point.y) < yExtent + && FastMath.abs(center.z - point.z) < zExtent; + } + + @Override + public boolean intersects(Vector3f point) { + return FastMath.abs(center.x - point.x) <= xExtent + && FastMath.abs(center.y - point.y) <= yExtent + && FastMath.abs(center.z - point.z) <= zExtent; + } + + public float distanceToEdge(Vector3f point) { + // compute coordinates of point in box coordinate system + TempVars vars= TempVars.get(); + Vector3f closest = vars.vect1; + + point.subtract(center,closest); + + // project test point onto box + float sqrDistance = 0.0f; + float delta; + + if (closest.x < -xExtent) { + delta = closest.x + xExtent; + sqrDistance += delta * delta; + closest.x = -xExtent; + } else if (closest.x > xExtent) { + delta = closest.x - xExtent; + sqrDistance += delta * delta; + closest.x = xExtent; + } + + if (closest.y < -yExtent) { + delta = closest.y + yExtent; + sqrDistance += delta * delta; + closest.y = -yExtent; + } else if (closest.y > yExtent) { + delta = closest.y - yExtent; + sqrDistance += delta * delta; + closest.y = yExtent; + } + + if (closest.z < -zExtent) { + delta = closest.z + zExtent; + sqrDistance += delta * delta; + closest.z = -zExtent; + } else if (closest.z > zExtent) { + delta = closest.z - zExtent; + sqrDistance += delta * delta; + closest.z = zExtent; + } + + vars.release(); + return FastMath.sqrt(sqrDistance); + } + + /** + * 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) { + // This is the old if statement... + // if (numer > denom * t[1]) { + // + // The problem is that what is actually stored is + // numer/denom. In non-floating point, this math should + // work out the same but in floating point there can + // be subtle math errors. The multiply will exaggerate + // errors that may have been introduced when the value + // was originally divided. + // + // This is especially true when the bounding box has zero + // extents in some plane because the error rate is critical. + // comparing a to b * c is not the same as comparing a/b to c + // in this case. In fact, I tried converting this method to + // double and the and the error was in the last decimal place. + // + // So, instead, we now compare the divided version to the divided + // version. We lose some slight performance here as divide + // will be more expensive than the divide. Some microbenchmarks + // show divide to be 3x slower than multiple on Java 1.6. + // BUT... we also saved a multiply in the non-clipped case because + // we can reuse the divided version in both if checks. + // I think it's better to be right in this case. + // + // Bug that I'm fixing: rays going right through quads at certain + // angles and distances because they fail the bounding box test. + // Many Bothans died bring you this fix. + // -pspeed + float newT = numer / denom; + if (newT > t[1]) { + return false; + } + if (newT > t[0]) { + t[0] = newT; + } + return true; + } else if (denom < 0.0f) { + // Old if statement... see above + // if (numer > denom * t[0]) { + // + // Note though that denom is always negative in this block. + // When we move it over to the other side we have to flip + // the comparison. Algebra for the win. + float newT = numer / denom; + if (newT < t[0]) { + return false; + } + if (newT < t[1]) { + t[1] = newT; + } + 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); + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java new file mode 100644 index 000000000..aa48658fa --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java @@ -0,0 +1,859 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bounding; + +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.collision.UnsupportedCollisionException; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.*; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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 in C++ at + *

    + * http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-SmallestEnclosingSpheres&forum=cotd&id=-1
    broken link

    + *

    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(); + + Vector3f tempA = new Vector3f(); //vars.vect1; + Vector3f tempB = new Vector3f(); //vars.vect2; + Vector3f tempC = new Vector3f(); //vars.vect3; + Vector3f tempD = new Vector3f(); //vars.vect4; + + switch (b) { + case 0: + this.radius = 0; + this.center.set(0, 0, 0); + break; + case 1: + this.radius = 1f - RADIUS_EPSILON; + BufferUtils.populateFromBuffer(center, points, ap - 1); + break; + case 2: + BufferUtils.populateFromBuffer(tempA, points, ap - 1); + BufferUtils.populateFromBuffer(tempB, points, ap - 2); + setSphere(tempA, tempB); + break; + case 3: + BufferUtils.populateFromBuffer(tempA, points, ap - 1); + BufferUtils.populateFromBuffer(tempB, points, ap - 2); + BufferUtils.populateFromBuffer(tempC, points, ap - 3); + setSphere(tempA, tempB, tempC); + break; + case 4: + BufferUtils.populateFromBuffer(tempA, points, ap - 1); + BufferUtils.populateFromBuffer(tempB, points, ap - 2); + BufferUtils.populateFromBuffer(tempC, points, ap - 3); + BufferUtils.populateFromBuffer(tempD, points, ap - 4); + setSphere(tempA, tempB, tempC, tempD); + //vars.release(); + return; + } + for (int i = 0; i < p; i++) { + BufferUtils.populateFromBuffer(tempA, points, i + ap); + if (tempA.distanceSquared(center) - (radius * radius) > RADIUS_EPSILON - 1f) { + for (int j = i; j > 0; j--) { + BufferUtils.populateFromBuffer(tempB, points, j + ap); + BufferUtils.populateFromBuffer(tempC, points, j - 1 + ap); + BufferUtils.setInBuffer(tempC, points, j + ap); + BufferUtils.setInBuffer(tempB, points, j - 1 + ap); + } + recurseMini(points, i, b + 1, ap + 1); + } + } + //vars.release(); + } + + /** + * Calculates the minimum bounding sphere of 4 points. Used in welzl's + * algorithm. + * + * @param O + * The 1st point inside the sphere. + * @param A + * The 2nd point inside the sphere. + * @param B + * The 3rd point inside the sphere. + * @param C + * The 4th point inside the sphere. + * @see #calcWelzl(java.nio.FloatBuffer) + */ + private void setSphere(Vector3f O, Vector3f A, Vector3f B, Vector3f C) { + Vector3f a = A.subtract(O); + Vector3f b = B.subtract(O); + Vector3f c = C.subtract(O); + + float Denominator = 2.0f * (a.x * (b.y * c.z - c.y * b.z) - b.x + * (a.y * c.z - c.y * a.z) + c.x * (a.y * b.z - b.y * a.z)); + if (Denominator == 0) { + center.set(0, 0, 0); + radius = 0; + } else { + Vector3f o = a.cross(b).multLocal(c.lengthSquared()).addLocal( + c.cross(a).multLocal(b.lengthSquared())).addLocal( + b.cross(c).multLocal(a.lengthSquared())).divideLocal( + Denominator); + + radius = o.length() * RADIUS_EPSILON; + O.add(o, center); + } + } + + /** + * Calculates the minimum bounding sphere of 3 points. Used in welzl's + * algorithm. + * + * @param O + * The 1st point inside the sphere. + * @param A + * The 2nd point inside the sphere. + * @param B + * The 3rd point inside the sphere. + * @see #calcWelzl(java.nio.FloatBuffer) + */ + private void setSphere(Vector3f O, Vector3f A, Vector3f B) { + Vector3f a = A.subtract(O); + Vector3f b = B.subtract(O); + Vector3f acrossB = a.cross(b); + + float Denominator = 2.0f * acrossB.dot(acrossB); + + if (Denominator == 0) { + center.set(0, 0, 0); + radius = 0; + } else { + + Vector3f o = acrossB.cross(a).multLocal(b.lengthSquared()).addLocal(b.cross(acrossB).multLocal(a.lengthSquared())).divideLocal(Denominator); + radius = o.length() * RADIUS_EPSILON; + O.add(o, center); + } + } + + /** + * Calculates the minimum bounding sphere of 2 points. Used in welzl's + * algorithm. + * + * @param O + * The 1st point inside the sphere. + * @param A + * The 2nd point inside the sphere. + * @see #calcWelzl(java.nio.FloatBuffer) + */ + private void setSphere(Vector3f O, Vector3f A) { + radius = FastMath.sqrt(((A.x - O.x) * (A.x - O.x) + (A.y - O.y) + * (A.y - O.y) + (A.z - O.z) * (A.z - O.z)) / 4f) + RADIUS_EPSILON - 1f; + center.interpolateLocal(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.fine("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 trans + * the transform to apply + * @param store + * sphere to store result in + * @return BoundingVolume + * @return ref + */ + public BoundingVolume transform(Transform trans, BoundingVolume store) { + BoundingSphere sphere; + if (store == null || store.getType() != BoundingVolume.Type.Sphere) { + sphere = new BoundingSphere(1, new Vector3f(0, 0, 0)); + } else { + sphere = (BoundingSphere) store; + } + + center.mult(trans.getScale(), sphere.center); + trans.getRotation().mult(sphere.center, sphere.center); + sphere.center.addLocal(trans.getTranslation()); + sphere.radius = FastMath.abs(getMaxAxis(trans.getScale()) * radius) + RADIUS_EPSILON - 1f; + return sphere; + } + + public BoundingVolume transform(Matrix4f trans, BoundingVolume store) { + BoundingSphere sphere; + if (store == null || store.getType() != BoundingVolume.Type.Sphere) { + sphere = new BoundingSphere(1, new Vector3f(0, 0, 0)); + } else { + sphere = (BoundingSphere) store; + } + + trans.mult(center, sphere.center); + Vector3f axes = new Vector3f(1, 1, 1); + trans.mult(axes, axes); + float ax = getMaxAxis(axes); + sphere.radius = FastMath.abs(ax * radius) + RADIUS_EPSILON - 1f; + return sphere; + } + + private float getMaxAxis(Vector3f scale) { + float x = FastMath.abs(scale.x); + float y = FastMath.abs(scale.y); + float z = FastMath.abs(scale.z); + + if (x >= y) { + if (x >= z) { + return x; + } + return z; + } + + if (y >= z) { + return y; + } + + return z; + } + + /** + * 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; + TempVars vars = TempVars.get(); + Vector3f radVect = vars.vect1; + radVect.set(box.xExtent, box.yExtent, box.zExtent); + Vector3f temp_center = box.center; + float len = radVect.length(); + vars.release(); + return merge(len, temp_center, this); + } + +// case OBB: { +// return mergeOBB((OrientedBoundingBox) volume); +// } + + default: + return null; + } + } + +// /** +// * Merges this sphere with the given OBB. +// * +// * @param volume +// * The OBB to merge. +// * @return This sphere, after merging. +// */ +// private BoundingSphere mergeOBB(OrientedBoundingBox volume) { +// // compute edge points from the obb +// if (!volume.correctCorners) +// volume.computeCorners(); +// _mergeBuf.rewind(); +// for (int i = 0; i < 8; i++) { +// _mergeBuf.put(volume.vectorStore[i].x); +// _mergeBuf.put(volume.vectorStore[i].y); +// _mergeBuf.put(volume.vectorStore[i].z); +// } +// +// // remember old radius and center +// float oldRadius = radius; +// Vector3f oldCenter = _compVect2.set( center ); +// +// // compute new radius and center from obb points +// computeFromPoints(_mergeBuf); +// Vector3f newCenter = _compVect3.set( center ); +// float newRadius = radius; +// +// // restore old center and radius +// center.set( oldCenter ); +// radius = oldRadius; +// +// //merge obb points result +// merge( newRadius, newCenter, this ); +// +// return this; +// } + private BoundingVolume merge(float temp_radius, Vector3f temp_center, + BoundingSphere rVal) { + TempVars vars = TempVars.get(); + + Vector3f diff = temp_center.subtract(center, vars.vect1); + float lengthSquared = diff.lengthSquared(); + float radiusDiff = temp_radius - radius; + + float fRDiffSqr = radiusDiff * radiusDiff; + + if (fRDiffSqr >= lengthSquared) { + if (radiusDiff <= 0.0f) { + vars.release(); + return this; + } + + Vector3f rCenter = rVal.center; + if (rCenter == null) { + rVal.setCenter(rCenter = new Vector3f()); + } + rCenter.set(temp_center); + rVal.setRadius(temp_radius); + vars.release(); + return rVal; + } + + float length = (float) Math.sqrt(lengthSquared); + + Vector3f rCenter = rVal.center; + if (rCenter == null) { + rVal.setCenter(rCenter = new Vector3f()); + } + if (length > RADIUS_EPSILON) { + float coeff = (length + radiusDiff) / (2.0f * length); + rCenter.set(center.addLocal(diff.multLocal(coeff))); + } else { + rCenter.set(center); + } + + rVal.setRadius(0.5f * (length + radius + temp_radius)); + vars.release(); + return rVal; + } + + /** + * 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(); + + Vector3f diff = center.subtract(bs.center, vars.vect1); + float rsum = getRadius() + bs.getRadius(); + boolean eq = (diff.dot(diff) <= rsum * rsum); + vars.release(); + return eq; + } + + /* + * (non-Javadoc) + * + * @see com.jme.bounding.BoundingVolume#intersectsBoundingBox(com.jme.bounding.BoundingBox) + */ + public boolean intersectsBoundingBox(BoundingBox bb) { + assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bb.center); + + if (FastMath.abs(bb.center.x - center.x) < getRadius() + + bb.xExtent + && FastMath.abs(bb.center.y - center.y) < getRadius() + + bb.yExtent + && FastMath.abs(bb.center.z - center.z) < getRadius() + + bb.zExtent) { + return true; + } + + return false; + } + + /* + * (non-Javadoc) + * + * @see com.jme.bounding.BoundingVolume#intersectsOrientedBoundingBox(com.jme.bounding.OrientedBoundingBox) + */ +// public boolean intersectsOrientedBoundingBox(OrientedBoundingBox obb) { +// return obb.intersectsSphere(this); +// } + + /* + * (non-Javadoc) + * + * @see com.jme.bounding.BoundingVolume#intersects(com.jme.math.Ray) + */ + public boolean intersects(Ray ray) { + assert Vector3f.isValidVector(center); + + TempVars vars = TempVars.get(); + + Vector3f diff = vars.vect1.set(ray.getOrigin()).subtractLocal(center); + float radiusSquared = getRadius() * getRadius(); + float a = diff.dot(diff) - radiusSquared; + if (a <= 0.0) { + vars.release(); + // in sphere + return true; + } + + // outside sphere + float b = ray.getDirection().dot(diff); + vars.release(); + if (b >= 0.0) { + return false; + } + return b * b >= a; + } + + /* + * (non-Javadoc) + * + * @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray) + */ + private int collideWithRay(Ray ray, CollisionResults results) { + TempVars vars = TempVars.get(); + + Vector3f diff = vars.vect1.set(ray.getOrigin()).subtractLocal( + center); + float a = diff.dot(diff) - (getRadius() * getRadius()); + float a1, discr, root; + if (a <= 0.0) { + // inside sphere + a1 = ray.direction.dot(diff); + discr = (a1 * a1) - a; + root = FastMath.sqrt(discr); + + float distance = root - a1; + Vector3f point = new Vector3f(ray.direction).multLocal(distance).addLocal(ray.origin); + + CollisionResult result = new CollisionResult(point, distance); + results.addCollision(result); + vars.release(); + return 1; + } + + a1 = ray.direction.dot(diff); + vars.release(); + if (a1 >= 0.0) { + return 0; + } + + discr = a1 * a1 - a; + if (discr < 0.0) { + return 0; + } else if (discr >= FastMath.ZERO_TOLERANCE) { + root = FastMath.sqrt(discr); + float dist = -a1 - root; + Vector3f point = new Vector3f(ray.direction).multLocal(dist).addLocal(ray.origin); + results.addCollision(new CollisionResult(point, dist)); + + dist = -a1 + root; + point = new Vector3f(ray.direction).multLocal(dist).addLocal(ray.origin); + results.addCollision(new CollisionResult(point, dist)); + return 2; + } else { + float dist = -a1; + Vector3f point = new Vector3f(ray.direction).multLocal(dist).addLocal(ray.origin); + results.addCollision(new CollisionResult(point, dist)); + return 1; + } + } + + public int collideWith(Collidable other, CollisionResults results) { + if (other instanceof Ray) { + Ray ray = (Ray) other; + return collideWithRay(ray, results); + } else if (other instanceof Triangle){ + Triangle t = (Triangle) other; + + float r2 = radius * radius; + float d1 = center.distanceSquared(t.get1()); + float d2 = center.distanceSquared(t.get2()); + float d3 = center.distanceSquared(t.get3()); + + if (d1 <= r2 || d2 <= r2 || d3 <= r2) { + CollisionResult r = new CollisionResult(); + r.setDistance(FastMath.sqrt(Math.min(Math.min(d1, d2), d3)) - radius); + results.addCollision(r); + return 1; + } + + return 0; + } else { + throw new UnsupportedCollisionException(); + } + } + + @Override + public boolean contains(Vector3f point) { + return center.distanceSquared(point) < (getRadius() * getRadius()); + } + + @Override + public boolean intersects(Vector3f point) { + return center.distanceSquared(point) <= (getRadius() * getRadius()); + } + + public float distanceToEdge(Vector3f point) { + return center.distance(point) - radius; + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + try { + e.getCapsule(this).write(radius, "radius", 0); + } catch (IOException ex) { + logger.logp(Level.SEVERE, this.getClass().toString(), "write(JMEExporter)", "Exception", ex); + } + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + try { + radius = e.getCapsule(this).readFloat("radius", 0); + } catch (IOException ex) { + logger.logp(Level.SEVERE, this.getClass().toString(), "read(JMEImporter)", "Exception", ex); + } + } + + @Override + public float getVolume() { + return 4 * FastMath.ONE_THIRD * FastMath.PI * radius * radius * radius; + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java new file mode 100644 index 000000000..8a2b87cd1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bounding; + +import com.jme3.collision.Collidable; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.Savable; +import com.jme3.math.*; +import java.io.IOException; +import java.nio.FloatBuffer; + +/** + * 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 { + + /** + * The type of bounding volume being used. + */ + public enum Type { + /** + * {@link BoundingSphere} + */ + Sphere, + + /** + * {@link BoundingBox}. + */ + AABB, + + /** + * Currently unsupported by jME3. + */ + Capsule; + } + + protected int checkPlane = 0; + protected Vector3f center = new Vector3f(); + + public BoundingVolume() { + } + + public BoundingVolume(Vector3f center) { + this.center.set(center); + } + + /** + * Grabs the checkplane we should check first. + * + */ + public int getCheckPlane() { + return checkPlane; + } + + /** + * Sets the index of the plane that should be first checked during rendering. + * + * @param value + */ + public final void setCheckPlane(int value) { + checkPlane = value; + } + + /** + * getType returns the type of bounding volume this is. + */ + public abstract Type getType(); + + /** + * + * 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/jme3-core/src/main/java/com/jme3/bounding/Intersection.java b/jme3-core/src/main/java/com/jme3/bounding/Intersection.java new file mode 100644 index 000000000..9deb62abc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/bounding/Intersection.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bounding; + +import com.jme3.math.FastMath; +import com.jme3.math.Plane; +import com.jme3.math.Vector3f; +import com.jme3.util.TempVars; +import static java.lang.Math.max; +import static java.lang.Math.min; + +/** + * This class includes some utility methods for computing intersection + * between bounding volumes and triangles. + * @author Kirill + */ +public class Intersection { + + private static final void findMinMax(float x0, float x1, float x2, Vector3f minMax) { + minMax.set(x0, x0, 0); + if (x1 < minMax.x) { + minMax.setX(x1); + } + if (x1 > minMax.y) { + minMax.setY(x1); + } + if (x2 < minMax.x) { + minMax.setX(x2); + } + if (x2 > minMax.y) { + minMax.setY(x2); + } + } + +// private boolean axisTest(float a, float b, float fa, float fb, Vector3f v0, Vector3f v1, ) +// private boolean axisTestX01(float a, float b, float fa, float fb, +// Vector3f center, Vector3f ext, +// Vector3f v1, Vector3f v2, Vector3f v3){ +// float p0 = a * v0.y - b * v0.z; +// float p2 = a * v2.y - b * v2.z; +// if(p0 < p2){ +// min = p0; +// max = p2; +// } else { +// min = p2; +// max = p0; +// } +// float rad = fa * boxhalfsize.y + fb * boxhalfsize.z; +// if(min > rad || max < -rad) +// return false; +// } + public static boolean intersect(BoundingBox bbox, Vector3f v1, Vector3f v2, Vector3f v3) { + // use separating axis theorem to test overlap between triangle and box + // need to test for overlap in these directions: + // 1) the {x,y,z}-directions (actually, since we use the AABB of the triangle + // we do not even need to test these) + // 2) normal of the triangle + // 3) crossproduct(edge from tri, {x,y,z}-directin) + // this gives 3x3=9 more tests + + TempVars vars = TempVars.get(); + + + Vector3f tmp0 = vars.vect1, + tmp1 = vars.vect2, + tmp2 = vars.vect3; + + Vector3f e0 = vars.vect4, + e1 = vars.vect5, + e2 = vars.vect6; + + Vector3f center = bbox.getCenter(); + Vector3f extent = bbox.getExtent(null); + +// float min,max,p0,p1,p2,rad,fex,fey,fez; +// float normal[3] + + // This is the fastest branch on Sun + // move everything so that the boxcenter is in (0,0,0) + v1.subtract(center, tmp0); + v2.subtract(center, tmp1); + v3.subtract(center, tmp2); + + // compute triangle edges + tmp1.subtract(tmp0, e0); // tri edge 0 + tmp2.subtract(tmp1, e1); // tri edge 1 + tmp0.subtract(tmp2, e2); // tri edge 2 + + // Bullet 3: + // test the 9 tests first (this was faster) + float min, max; + float p0, p1, p2, rad; + float fex = FastMath.abs(e0.x); + float fey = FastMath.abs(e0.y); + float fez = FastMath.abs(e0.z); + + + + //AXISTEST_X01(e0[Z], e0[Y], fez, fey); + p0 = e0.z * tmp0.y - e0.y * tmp0.z; + p2 = e0.z * tmp2.y - e0.y * tmp2.z; + min = min(p0, p2); + max = max(p0, p2); + rad = fez * extent.y + fey * extent.z; + if (min > rad || max < -rad) { + vars.release(); + return false; + } + + // AXISTEST_Y02(e0[Z], e0[X], fez, fex); + p0 = -e0.z * tmp0.x + e0.x * tmp0.z; + p2 = -e0.z * tmp2.x + e0.x * tmp2.z; + min = min(p0, p2); + max = max(p0, p2); + rad = fez * extent.x + fex * extent.z; + if (min > rad || max < -rad) { + vars.release(); + return false; + } + + // AXISTEST_Z12(e0[Y], e0[X], fey, fex); + p1 = e0.y * tmp1.x - e0.x * tmp1.y; + p2 = e0.y * tmp2.x - e0.x * tmp2.y; + min = min(p1, p2); + max = max(p1, p2); + rad = fey * extent.x + fex * extent.y; + if (min > rad || max < -rad) { + vars.release(); + return false; + } + + fex = FastMath.abs(e1.x); + fey = FastMath.abs(e1.y); + fez = FastMath.abs(e1.z); + +// AXISTEST_X01(e1[Z], e1[Y], fez, fey); + p0 = e1.z * tmp0.y - e1.y * tmp0.z; + p2 = e1.z * tmp2.y - e1.y * tmp2.z; + min = min(p0, p2); + max = max(p0, p2); + rad = fez * extent.y + fey * extent.z; + if (min > rad || max < -rad) { + vars.release(); + return false; + } + + // AXISTEST_Y02(e1[Z], e1[X], fez, fex); + p0 = -e1.z * tmp0.x + e1.x * tmp0.z; + p2 = -e1.z * tmp2.x + e1.x * tmp2.z; + min = min(p0, p2); + max = max(p0, p2); + rad = fez * extent.x + fex * extent.z; + if (min > rad || max < -rad) { + vars.release(); + return false; + } + + // AXISTEST_Z0(e1[Y], e1[X], fey, fex); + p0 = e1.y * tmp0.x - e1.x * tmp0.y; + p1 = e1.y * tmp1.x - e1.x * tmp1.y; + min = min(p0, p1); + max = max(p0, p1); + rad = fey * extent.x + fex * extent.y; + if (min > rad || max < -rad) { + vars.release(); + return false; + } +// + fex = FastMath.abs(e2.x); + fey = FastMath.abs(e2.y); + fez = FastMath.abs(e2.z); + + // AXISTEST_X2(e2[Z], e2[Y], fez, fey); + p0 = e2.z * tmp0.y - e2.y * tmp0.z; + p1 = e2.z * tmp1.y - e2.y * tmp1.z; + min = min(p0, p1); + max = max(p0, p1); + rad = fez * extent.y + fey * extent.z; + if (min > rad || max < -rad) { + vars.release(); + return false; + } + + // AXISTEST_Y1(e2[Z], e2[X], fez, fex); + p0 = -e2.z * tmp0.x + e2.x * tmp0.z; + p1 = -e2.z * tmp1.x + e2.x * tmp1.z; + min = min(p0, p1); + max = max(p0, p1); + rad = fez * extent.x + fex * extent.y; + if (min > rad || max < -rad) { + vars.release(); + return false; + } + +// AXISTEST_Z12(e2[Y], e2[X], fey, fex); + p1 = e2.y * tmp1.x - e2.x * tmp1.y; + p2 = e2.y * tmp2.x - e2.x * tmp2.y; + min = min(p1, p2); + max = max(p1, p2); + rad = fey * extent.x + fex * extent.y; + if (min > rad || max < -rad) { + vars.release(); + return false; + } + + // Bullet 1: + // first test overlap in the {x,y,z}-directions + // find min, max of the triangle each direction, and test for overlap in + // that direction -- this is equivalent to testing a minimal AABB around + // the triangle against the AABB + + + Vector3f minMax = vars.vect7; + + // test in X-direction + findMinMax(tmp0.x, tmp1.x, tmp2.x, minMax); + if (minMax.x > extent.x || minMax.y < -extent.x) { + vars.release(); + return false; + } + + // test in Y-direction + findMinMax(tmp0.y, tmp1.y, tmp2.y, minMax); + if (minMax.x > extent.y || minMax.y < -extent.y) { + vars.release(); + return false; + } + + // test in Z-direction + findMinMax(tmp0.z, tmp1.z, tmp2.z, minMax); + if (minMax.x > extent.z || minMax.y < -extent.z) { + vars.release(); + return false; + } + +// // Bullet 2: +// // test if the box intersects the plane of the triangle +// // compute plane equation of triangle: normal * x + d = 0 +// Vector3f normal = new Vector3f(); +// e0.cross(e1, normal); + Plane p = vars.plane; + + p.setPlanePoints(v1, v2, v3); + if (bbox.whichSide(p) == Plane.Side.Negative) { + vars.release(); + return false; + } +// +// if(!planeBoxOverlap(normal,v0,boxhalfsize)) return false; + + vars.release(); + + return true; /* box and triangle overlaps */ + } +} diff --git a/jme3-core/src/main/java/com/jme3/bounding/OrientedBoundingBox.java b/jme3-core/src/main/java/com/jme3/bounding/OrientedBoundingBox.java new file mode 100644 index 000000000..1fe1624cb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/bounding/OrientedBoundingBox.java @@ -0,0 +1,1521 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bounding; + +/** + * 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/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java b/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java new file mode 100644 index 000000000..4872bb195 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java @@ -0,0 +1,716 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic; + +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.cinematic.events.AbstractCinematicEvent; +import com.jme3.cinematic.events.CinematicEvent; +import com.jme3.export.*; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.CameraNode; +import com.jme3.scene.Node; +import com.jme3.scene.control.CameraControl; +import com.jme3.scene.control.CameraControl.ControlDirection; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * An appstate for composing and playing cut scenes in a game. The cineamtic + * schedules CinematicEvents over a timeline. Once the Cinematic created it has + * to be attched to the stateManager. + * + * You can add various CinematicEvents to a Cinematic, see package + * com.jme3.cinematic.events + * + * Two main methods can be used to add an event : + * + * @see Cinematic#addCinematicEvent(float, + * com.jme3.cinematic.events.CinematicEvent) , that adds an event at the given + * time form the cinematic start. + * + * @see + * Cinematic#enqueueCinematicEvent(com.jme3.cinematic.events.CinematicEvent) + * that enqueue events one after the other according to their initialDuration + * + * a cinematic has convenient mathods to handle the playback : + * @see Cinematic#play() + * @see Cinematic#pause() + * @see Cinematic#stop() + * + * A cinematic is itself a CinematicEvent, meaning you can embed several + * Cinematics Embed cinematics must not be added to the stateManager though. + * + * Cinematic has a way to handle several point of view by creating CameraNode + * over a cam and activating them on schedule. + * @see Cinematic#bindCamera(java.lang.String, com.jme3.renderer.Camera) + * @see Cinematic#activateCamera(float, java.lang.String) + * @see Cinematic#setActiveCamera(java.lang.String) + * + * @author Nehon + */ +public class Cinematic extends AbstractCinematicEvent implements AppState { + + private static final Logger logger = Logger.getLogger(Application.class.getName()); + private Node scene; + protected TimeLine timeLine = new TimeLine(); + private int lastFetchedKeyFrame = -1; + private List cinematicEvents = new ArrayList(); + private Map cameras = new HashMap(); + private CameraNode currentCam; + private boolean initialized = false; + private Map> eventsData; + private float nextEnqueue = 0; + + /** + * Used for serialization creates a cinematic, don't use this constructor + * directly + */ + public Cinematic() { + } + + /** + * creates a cinematic + * + * @param scene the scene in which the cinematic should take place + */ + public Cinematic(Node scene) { + this.scene = scene; + } + + /** + * creates a cinematic + * + * @param scene the scene in which the cinematic should take place + * @param initialDuration the duration of the cinematic (without considering + * the speed) + */ + public Cinematic(Node scene, float initialDuration) { + super(initialDuration); + this.scene = scene; + } + + /** + * creates a cinematic + * + * @param scene the scene in which the cinematic should take place + * @param loopMode tells if this cinematic should be looped or not + */ + public Cinematic(Node scene, LoopMode loopMode) { + super(loopMode); + this.scene = scene; + } + + /** + * creates a cinematic + * + * @param scene the scene in which the cinematic should take place + * @param initialDuration the duration of the cinematic (without considering + * the speed) + * @param loopMode tells if this cinematic should be looped or not + */ + public Cinematic(Node scene, float initialDuration, LoopMode loopMode) { + super(initialDuration, loopMode); + this.scene = scene; + } + + /** + * called internally + */ + @Override + public void onPlay() { + if (isInitialized()) { + if (playState == PlayState.Paused) { + for (int i = 0; i < cinematicEvents.size(); i++) { + CinematicEvent ce = cinematicEvents.get(i); + if (ce.getPlayState() == PlayState.Paused) { + ce.play(); + } + } + } + } + } + + /** + * called internally + */ + @Override + public void onStop() { + time = 0; + lastFetchedKeyFrame = -1; + for (int i = 0; i < cinematicEvents.size(); i++) { + CinematicEvent ce = cinematicEvents.get(i); + ce.setTime(0); + ce.forceStop(); + } + setEnableCurrentCam(false); + } + + /** + * called internally + */ + @Override + public void onPause() { + for (int i = 0; i < cinematicEvents.size(); i++) { + CinematicEvent ce = cinematicEvents.get(i); + if (ce.getPlayState() == PlayState.Playing) { + ce.pause(); + } + } + } + + /** + * used internally for serialization + * + * @param ex + * @throws IOException + */ + @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); + + } + + /** + * used internally for srialization + * + * @param im + * @throws IOException + */ + @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); + } + + /** + * sets the speed of the cinematic. Note that it will set the speed of all + * events in the cinematic. 1 is normal speed. use 0.5f to make the + * cinematic twice slower, use 2 to make it twice faster + * + * @param speed the speed + */ + @Override + public void setSpeed(float speed) { + super.setSpeed(speed); + for (int i = 0; i < cinematicEvents.size(); i++) { + CinematicEvent ce = cinematicEvents.get(i); + ce.setSpeed(speed); + } + + + } + + /** + * used internally + * + * @param stateManager the state manager + * @param app the application + */ + public void initialize(AppStateManager stateManager, Application app) { + initEvent(app, this); + for (CinematicEvent cinematicEvent : cinematicEvents) { + cinematicEvent.initEvent(app, this); + } + + initialized = true; + } + + /** + * used internally + * + * @return + */ + public boolean isInitialized() { + return initialized; + } + + /** + * passing true has the same effect as play() you should use play(), + * pause(), stop() to handle the cinemaic playing state. + * + * @param enabled true or false + */ + public void setEnabled(boolean enabled) { + if (enabled) { + play(); + } + } + + /** + * return true if the cinematic appstate is enabled (the cinematic is + * playing) + * + * @return true if enabled + */ + public boolean isEnabled() { + return playState == PlayState.Playing; + } + + /** + * called internally + * + * @param stateManager the state manager + */ + public void stateAttached(AppStateManager stateManager) { + } + + /** + * called internally + * + * @param stateManager the state manager + */ + public void stateDetached(AppStateManager stateManager) { + stop(); + } + + /** + * called internally don't call it directly. + * + * @param tpf + */ + public void update(float tpf) { + if (isInitialized()) { + internalUpdate(tpf); + } + } + + /** + * used internally, don't call this directly. + * + * @param 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; + } + + /** + * This is used internally but can be alled to shuffle through the + * cinematic. + * + * @param time the time to shuffle to. + */ + @Override + public void setTime(float time) { + + //stopping all events + onStop(); + super.setTime(time); + + int keyFrameIndex = timeLine.getKeyFrameIndexFromTime(time); + //triggering all the event from start to "time" + //then computing timeOffset for each event + for (int i = 0; i <= keyFrameIndex; i++) { + KeyFrame keyFrame = timeLine.get(i); + if (keyFrame != null) { + for (CinematicEvent ce : keyFrame.getCinematicEvents()) { + float t = this.time - timeLine.getKeyFrameTime(keyFrame); + if (t >= 0 && (t <= ce.getInitialDuration() || ce.getLoopMode() != LoopMode.DontLoop)) { + ce.play(); + } + ce.setTime(t); + } + } + } + lastFetchedKeyFrame = keyFrameIndex; + if (playState != PlayState.Playing) { + pause(); + } + } + + /** + * Adds a cinematic event to this cinematic at the given timestamp. This + * operation returns a keyFrame + * + * @param timeStamp the time when the event will start after the begining of + * the cinematic + * @param cinematicEvent the cinematic event + * @return the keyFrame for that event. + */ + 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); + if (isInitialized()) { + cinematicEvent.initEvent(null, this); + } + return keyFrame; + } + + /** + * enqueue a cinematic event to a cinematic. This is a handy method when you + * want to chain event of a given duration without knowing their initial + * duration + * + * @param cinematicEvent the cinematic event to enqueue + * @return the timestamp the evnt was scheduled. + */ + public float enqueueCinematicEvent(CinematicEvent cinematicEvent) { + float scheduleTime = nextEnqueue; + addCinematicEvent(scheduleTime, cinematicEvent); + nextEnqueue += cinematicEvent.getInitialDuration(); + return scheduleTime; + } + + /** + * removes the first occurrence found of the given cinematicEvent. + * + * @param cinematicEvent the cinematicEvent to remove + * @return true if the element has been removed + */ + public boolean removeCinematicEvent(CinematicEvent cinematicEvent) { + cinematicEvent.dispose(); + cinematicEvents.remove(cinematicEvent); + for (KeyFrame keyFrame : timeLine.values()) { + if (keyFrame.cinematicEvents.remove(cinematicEvent)) { + return true; + } + } + return false; + } + + /** + * removes the first occurrence found of the given cinematicEvent for the + * given time stamp. + * + * @param timeStamp the timestamp when the cinematicEvent has been added + * @param cinematicEvent the cinematicEvent to remove + * @return true if the element has been removed + */ + public boolean removeCinematicEvent(float timeStamp, CinematicEvent cinematicEvent) { + cinematicEvent.dispose(); + KeyFrame keyFrame = timeLine.getKeyFrameAtTime(timeStamp); + return removeCinematicEvent(keyFrame, cinematicEvent); + } + + /** + * removes the first occurrence found of the given cinematicEvent for the + * given keyFrame + * + * @param keyFrame the keyFrame returned by the addCinematicEvent method. + * @param cinematicEvent the cinematicEvent to remove + * @return true if the element has been removed + */ + public boolean removeCinematicEvent(KeyFrame keyFrame, CinematicEvent cinematicEvent) { + cinematicEvent.dispose(); + boolean ret = keyFrame.cinematicEvents.remove(cinematicEvent); + cinematicEvents.remove(cinematicEvent); + if (keyFrame.isEmpty()) { + timeLine.removeKeyFrame(keyFrame.getIndex()); + } + return ret; + } + + /** + * called internally + * + * @see AppState#render() + */ + public void render(RenderManager rm) { + } + + /** + * called internally + * + * @see AppState#postRender() + */ + public void postRender() { + } + + /** + * called internally + * + * @see AppState#cleanup() + */ + public void cleanup() { + } + + /** + * fits the duration of the cinamatic to the duration of all its child + * cinematic events + */ + public void fitDuration() { + KeyFrame kf = timeLine.getKeyFrameAtIndex(timeLine.getLastKeyFrameIndex()); + float d = 0; + for (int i = 0; i < kf.getCinematicEvents().size(); i++) { + CinematicEvent ce = kf.getCinematicEvents().get(i); + float dur = timeLine.getKeyFrameTime(kf) + ce.getDuration() * ce.getSpeed(); + if (d < dur) { + d = dur; + } + } + + initialDuration = d; + } + + /** + * Binds a camera to this cinematic, tagged by a unique name. This methods + * creates and returns a CameraNode for the cam and attach it to the scene. + * The control direction is set to SpatialToCamera. This camera Node can + * then be used in other events to handle the camera movements during the + * playback + * + * @param cameraName the unique tag the camera should have + * @param cam the scene camera. + * @return the created CameraNode. + */ + public CameraNode bindCamera(String cameraName, Camera cam) { + if (cameras.containsKey(cameraName)) { + throw new IllegalArgumentException("Camera " + cameraName + " is already binded to this cinematic"); + } + CameraNode node = new CameraNode(cameraName, cam); + node.setControlDir(ControlDirection.SpatialToCamera); + node.getControl(CameraControl.class).setEnabled(false); + cameras.put(cameraName, node); + scene.attachChild(node); + return node; + } + + /** + * returns a cameraNode given its name + * + * @param cameraName the camera name (as registerd in + * Cinematic#bindCamera()) + * @return the cameraNode for this name + */ + public CameraNode getCamera(String cameraName) { + return cameras.get(cameraName); + } + + /** + * enable/disable the camera control of the cameraNode of the current cam + * + * @param enabled + */ + private void setEnableCurrentCam(boolean enabled) { + if (currentCam != null) { + currentCam.getControl(CameraControl.class).setEnabled(enabled); + } + } + + /** + * Sets the active camera instantly (use activateCamera if you want to + * schedule that event) + * + * @param cameraName the camera name (as registerd in + * Cinematic#bindCamera()) + */ + public void setActiveCamera(String cameraName) { + setEnableCurrentCam(false); + currentCam = cameras.get(cameraName); + if (currentCam == null) { + logger.log(Level.WARNING, "{0} is not a camera bond to the cinematic, cannot activate", cameraName); + } + setEnableCurrentCam(true); + } + + /** + * schedule an event that will activate the camera at the given time + * + * @param timeStamp the time to activate the cam + * @param cameraName the camera name (as registerd in + * Cinematic#bindCamera()) + */ + public void activateCamera(final float timeStamp, final String cameraName) { + addCinematicEvent(timeStamp, new AbstractCinematicEvent() { + @Override + public void play() { + super.play(); + stop(); + } + + @Override + public void onPlay() { + setActiveCamera(cameraName); + } + + @Override + public void onUpdate(float tpf) { + } + + @Override + public void onStop() { + } + + @Override + public void onPause() { + } + + @Override + public void forceStop() { + } + + @Override + public void setTime(float time) { + play(); + } + }); + } + + /** + * returns the complete eventdata map + * + * @return the eventdata map + */ + private Map> getEventsData() { + if (eventsData == null) { + eventsData = new HashMap>(); + } + return eventsData; + } + + /** + * used internally put an eventdata in the cinematic + * + * @param type the type of data + * @param key the key + * @param object the data + */ + public void putEventData(String type, Object key, Object object) { + Map> data = getEventsData(); + Map row = data.get(type); + if (row == null) { + row = new HashMap(); + } + row.put(key, object); + data.put(type, row); + } + + /** + * used internally return and event data + * + * @param type the type of data + * @param key the key + * @return + */ + public Object getEventData(String type, Object key) { + if (eventsData != null) { + Map row = eventsData.get(type); + if (row != null) { + return row.get(key); + } + } + return null; + } + + /** + * Used internally remove an eventData + * + * @param type the type of data + * @param key the key of the data + */ + public void removeEventData(String type, Object key) { + if (eventsData != null) { + Map row = eventsData.get(type); + if (row != null) { + row.remove(key); + } + } + } + + /** + * sets the scene to use for this cinematic it is expected that the scene is + * added before adding events to the cinematic + * + * @param scene the scene where the cinematic should ttake place. + */ + public void setScene(Node scene) { + this.scene = scene; + } + + /** + * return the scene where the cinematic occur + * + * @return the scene + */ + public Node getScene() { + return scene; + } + + /** + * clear the cinematic of its events. + */ + public void clear() { + dispose(); + cinematicEvents.clear(); + timeLine.clear(); + eventsData.clear(); + } + + /** + * used internally to cleanup the cinematic. Called when the clear() method + * is called + */ + @Override + public void dispose() { + for (CinematicEvent event : cinematicEvents) { + event.dispose(); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/cinematic/KeyFrame.java b/jme3-core/src/main/java/com/jme3/cinematic/KeyFrame.java new file mode 100644 index 000000000..a794e3cd0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/KeyFrame.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic; + +import com.jme3.cinematic.events.CinematicEvent; +import com.jme3.export.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Nehon + */ +public class KeyFrame implements Savable { + + List 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 boolean isEmpty(){ + return cinematicEvents.isEmpty(); + } + + 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/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java b/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java new file mode 100644 index 000000000..543e90eb4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic; + +import com.jme3.asset.AssetManager; +import com.jme3.cinematic.events.MotionEvent; +import com.jme3.export.*; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Spline; +import com.jme3.math.Spline.SplineType; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Curve; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Motion path is used to create a path between way points. + * @author Nehon + */ +public class MotionPath implements Savable { + + private Node debugNode; + private AssetManager assetManager; + private List listeners; + private Spline spline = new Spline(); + int prevWayPoint = 0; + + /** + * Create a motion Path + */ + public MotionPath() { + } + + /** + * interpolate the path giving the time since the beginnin and the motionControl + * this methods sets the new localTranslation to the spatial of the MotionEvent control. + * @param time the time since the animation started + * @param control the ocntrol over the moving spatial + */ + public float interpolatePath(float time, MotionEvent control, float tpf) { + + float traveledDistance = 0; + TempVars vars = TempVars.get(); + Vector3f temp = vars.vect1; + Vector3f tmpVector = vars.vect2; + //computing traveled distance according to new time + traveledDistance = time * (getLength() / control.getInitialDuration()); + + //getting waypoint index and current value from new traveled distance + Vector2f v = getWayPointIndexForDistance(traveledDistance); + + //setting values + control.setCurrentWayPoint((int) v.x); + control.setCurrentValue(v.y); + + //interpolating new position + getSpline().interpolate(control.getCurrentValue(), control.getCurrentWayPoint(), temp); + if (control.needsDirection()) { + tmpVector.set(temp); + control.setDirection(tmpVector.subtractLocal(control.getSpatial().getLocalTranslation()).normalizeLocal()); + } + checkWayPoint(control, tpf); + + control.getSpatial().setLocalTranslation(temp); + vars.release(); + return traveledDistance; + } + + public void checkWayPoint(MotionEvent control, float tpf) { + //Epsilon varies with the tpf to avoid missing a waypoint on low framerate. + float epsilon = tpf * 4f; + if (control.getCurrentWayPoint() != prevWayPoint) { + if (control.getCurrentValue() >= 0f && control.getCurrentValue() < epsilon) { + triggerWayPointReach(control.getCurrentWayPoint(), control); + prevWayPoint = control.getCurrentWayPoint(); + } + } + } + + 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/Unshaded.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + mat.setColor("Color", ColorRGBA.Blue); + Geometry lineGeometry = new Geometry("line", new Curve(spline, 0)); + lineGeometry.setMaterial(mat); + return lineGeometry; + } + + private Geometry CreateCatmullRomPath() { + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + mat.setColor("Color", ColorRGBA.Blue); + Geometry lineGeometry = new Geometry("line", new Curve(spline, 10)); + lineGeometry.setMaterial(mat); + return lineGeometry; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(spline, "spline", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + spline = (Spline) in.readSavable("spline", null); + + } + + /** + * compute the index of the waypoint and the interpolation value according to a distance + * returns a vector 2 containing the index in the x field and the interpolation value in the y field + * @param distance the distance traveled on this path + * @return the waypoint index and the interpolation value in a vector2 + */ + public Vector2f getWayPointIndexForDistance(float distance) { + float sum = 0; + distance = distance % spline.getTotalLength(); + int i = 0; + for (Float len : spline.getSegmentsLength()) { + if (sum + len >= distance) { + return new Vector2f((float) i, (distance - sum) / len); + } + sum += len; + i++; + } + return new Vector2f((float) spline.getControlPoints().size() - 1, 1.0f); + } + + /** + * Addsa waypoint to the path + * @param wayPoint a position in world space + */ + public void addWayPoint(Vector3f wayPoint) { + spline.addControlPoint(wayPoint); + } + + /** + * retruns the length of the path in world units + * @return the length + */ + public float getLength() { + return spline.getTotalLength(); + } + + /** + * returns the waypoint at the given index + * @param i the index + * @return returns the waypoint position + */ + public Vector3f getWayPoint(int i) { + return spline.getControlPoints().get(i); + } + + /** + * remove the waypoint from the path + * @param wayPoint the waypoint to remove + */ + public void removeWayPoint(Vector3f wayPoint) { + spline.removeControlPoint(wayPoint); + } + + /** + * remove the waypoint at the given index from the path + * @param i the index of the waypoint to remove + */ + public void removeWayPoint(int i) { + removeWayPoint(spline.getControlPoints().get(i)); + } + + /** + * returns an iterator on the waypoints collection + * @return + */ + public Iterator iterator() { + return spline.getControlPoints().iterator(); + } + + /** + * return the type of spline used for the path interpolation for this path + * @return the path interpolation spline type + */ + public SplineType getPathSplineType() { + return spline.getType(); + } + + /** + * sets the type of spline used for the path interpolation for this path + * @param pathSplineType + */ + public void setPathSplineType(SplineType pathSplineType) { + spline.setType(pathSplineType); + if (debugNode != null) { + Node parent = debugNode.getParent(); + debugNode.removeFromParent(); + debugNode.detachAllChildren(); + debugNode = null; + attachDebugNode(parent); + } + } + + /** + * disable the display of the path and the waypoints + */ + public void disableDebugShape() { + + debugNode.detachAllChildren(); + debugNode = null; + assetManager = null; + } + + /** + * enable the display of the path and the waypoints + * @param manager the assetManager + * @param rootNode the node where the debug shapes must be attached + */ + public void enableDebugShape(AssetManager manager, Node rootNode) { + assetManager = manager; + // computeTotalLentgh(); + attachDebugNode(rootNode); + } + + /** + * Adds a motion pathListener to the path + * @param listener the MotionPathListener to attach + */ + public void addListener(MotionPathListener listener) { + if (listeners == null) { + listeners = new ArrayList(); + } + 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, MotionEvent 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); + } + } + + public void clearWayPoints() { + spline.clearControlPoints(); + } + + /** + * Sets the path to be a cycle + * @param cycle + */ + public void setCycle(boolean cycle) { + + spline.setCycle(cycle); + if (debugNode != null) { + Node parent = debugNode.getParent(); + debugNode.removeFromParent(); + debugNode.detachAllChildren(); + debugNode = null; + attachDebugNode(parent); + } + + } + + /** + * returns true if the path is a cycle + * @return + */ + public boolean isCycle() { + return spline.isCycle(); + } + + public Spline getSpline() { + return spline; + } +} diff --git a/jme3-core/src/main/java/com/jme3/cinematic/MotionPathListener.java b/jme3-core/src/main/java/com/jme3/cinematic/MotionPathListener.java new file mode 100644 index 000000000..216d1af42 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/MotionPathListener.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic; + +import com.jme3.cinematic.events.MotionEvent; + +/** + * 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 MotionEvent objects that reached the waypoint + * @param wayPointIndex the index of the way point reached + */ + public void onWayPointReach(MotionEvent motionControl,int wayPointIndex); + +} diff --git a/jme3-core/src/main/java/com/jme3/cinematic/PlayState.java b/jme3-core/src/main/java/com/jme3/cinematic/PlayState.java new file mode 100644 index 000000000..2b78eca33 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/PlayState.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic; + +/** + * 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/jme3-core/src/main/java/com/jme3/cinematic/TimeLine.java b/jme3-core/src/main/java/com/jme3/cinematic/TimeLine.java new file mode 100644 index 000000000..e51c909dd --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/TimeLine.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic; + +import com.jme3.export.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; + +/** + * + * @author Nehon + */ +public class TimeLine extends HashMap implements Savable { + + protected int keyFramesPerSeconds = 30; + protected int lastKeyFrameIndex = 0; + + public TimeLine() { + super(); + } + + public KeyFrame getKeyFrameAtTime(float time) { + return get(getKeyFrameIndexFromTime(time)); + } + + public KeyFrame getKeyFrameAtIndex(int keyFrameIndex) { + return get(keyFrameIndex); + } + + public void addKeyFrameAtTime(float time, KeyFrame keyFrame) { + addKeyFrameAtIndex(getKeyFrameIndexFromTime(time), keyFrame); + } + + public void addKeyFrameAtIndex(int keyFrameIndex, KeyFrame keyFrame) { + put(keyFrameIndex, keyFrame); + keyFrame.setIndex(keyFrameIndex); + if (lastKeyFrameIndex < keyFrameIndex) { + lastKeyFrameIndex = keyFrameIndex; + } + } + + public void removeKeyFrame(int keyFrameIndex) { + remove(keyFrameIndex); + if (lastKeyFrameIndex == keyFrameIndex) { + KeyFrame kf = null; + for (int i = keyFrameIndex; kf == null && i >= 0; i--) { + kf = getKeyFrameAtIndex(i); + lastKeyFrameIndex = i; + } + } + } + + + + public void removeKeyFrame(float time) { + removeKeyFrame(getKeyFrameIndexFromTime(time)); + } + + public int getKeyFrameIndexFromTime(float time) { + return Math.round(time * keyFramesPerSeconds); + } + + public float getKeyFrameTime(KeyFrame keyFrame) { + return (float)keyFrame.getIndex()/(float)keyFramesPerSeconds; + } + + public Collection 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/jme3-core/src/main/java/com/jme3/cinematic/events/AbstractCinematicEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/AbstractCinematicEvent.java new file mode 100644 index 000000000..8acd12708 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/AbstractCinematicEvent.java @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic.events; + +import com.jme3.animation.LoopMode; +import com.jme3.app.Application; +import com.jme3.cinematic.Cinematic; +import com.jme3.cinematic.PlayState; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * This call contains the basic behaviour of a cinematic event. + * Every cinematic event must extend this class. + * + * A cinematic event must be given an inital duration in seconds + * (duration of the event at speed = 1). Default is 10 sec. + * @author Nehon + */ +public abstract class AbstractCinematicEvent implements CinematicEvent { + + protected PlayState playState = PlayState.Stopped; + protected LoopMode loopMode = LoopMode.DontLoop; + protected float initialDuration = 10; + protected float speed = 1; + protected float time = 0; + protected boolean resuming = false; + + /** + * The list of listeners. + */ + protected List listeners; + + /** + * Contruct a cinematic event (empty constructor). + */ + public AbstractCinematicEvent() { + } + + /** + * Contruct a cinematic event with the given initial duration. + * @param initialDuration + */ + public AbstractCinematicEvent(float initialDuration) { + this.initialDuration = initialDuration; + } + + /** + * Contruct a cinematic event with the given loopMode. + * @param loopMode + */ + public AbstractCinematicEvent(LoopMode loopMode) { + this.loopMode = loopMode; + } + + /** + * Contruct a cinematic event with the given loopMode and the given initialDuration. + * @param initialDuration the duration of the event at speed = 1. + * @param loopMode the loop mode of the event. + */ + public AbstractCinematicEvent(float initialDuration, LoopMode loopMode) { + this.initialDuration = initialDuration; + this.loopMode = loopMode; + } + + /** + * Implement this method if the event needs different handling when + * stopped naturally (when the event reach its end), + * or when it was force-stopped during playback. + * By default, this method just calls regular stop(). + */ + public void forceStop(){ + stop(); + } + + /** + * Play this event. + */ + public void play() { + onPlay(); + playState = PlayState.Playing; + if (listeners != null) { + for (int i = 0; i < listeners.size(); i++) { + CinematicEventListener cel = listeners.get(i); + cel.onPlay(this); + } + } + } + + /** + * Implement this method with code that you want to execute when the event is started. + */ + protected abstract void onPlay(); + + /** + * Used internally only. + * @param tpf time per frame. + */ + public void internalUpdate(float tpf) { + if (playState == PlayState.Playing) { + time = time + (tpf * speed); + onUpdate(tpf); + if (time >= initialDuration && loopMode == LoopMode.DontLoop) { + stop(); + } else if(time >= initialDuration && loopMode == LoopMode.Loop){ + setTime(0); + } + } + + } + + /** + * Implement this method with the code that you want to execute on update + * (only called when the event is playing). + * @param tpf time per frame + */ + protected abstract void onUpdate(float tpf); + + /** + * Stops the animation. + * Next time when play() is called, the animation starts from the beginning. + */ + 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); + } + } + } + + /** + * Implement this method with code that you want to execute when the event is stopped. + */ + protected abstract void onStop(); + + /** + * Pause this event. + * Next time when play() is called, the animation restarts from here. + */ + 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); + } + } + } + + /** + * Implement this method with code that you want to execute when the event is paused. + */ + public abstract void onPause(); + + /** + * Returns the actual duration of the animtion (initialDuration/speed) + * @return + */ + public float getDuration() { + return initialDuration / speed; + } + + /** + * Sets the speed of the animation. + * At speed = 1, the animation will last initialDuration seconds, + * At speed = 2, the animation will last initialDuration/2... + * @param speed + */ + public void setSpeed(float speed) { + this.speed = speed; + } + + /** + * Returns the speed of the animation. + * @return + */ + public float getSpeed() { + return speed; + } + + /** + * Returns the current playstate of the animation (playing or paused or stopped). + * @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 animation at speed = 1 in seconds. + * @param initialDuration + */ + public void setInitialDuration(float initialDuration) { + this.initialDuration = initialDuration; + } + + /** + * Returns the 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; + } + + /** + * Used for serialization only. + * @param ex exporter + * @throws IOException + */ + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(playState, "playState", PlayState.Stopped); + oc.write(speed, "speed", 1); + oc.write(initialDuration, "initalDuration", 10); + oc.write(loopMode, "loopMode", LoopMode.DontLoop); + } + + /** + * Used for serialization only. + * @param im importer + * @throws IOException + */ + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + playState = ic.readEnum("playState", PlayState.class, PlayState.Stopped); + speed = ic.readFloat("speed", 1); + initialDuration = ic.readFloat("initalDuration", 10); + loopMode = ic.readEnum("loopMode", LoopMode.class, LoopMode.DontLoop); + } + + /** + * Initialize this event (called internally only). + * @param app + * @param cinematic + */ + public void initEvent(Application app, Cinematic cinematic) { + } + + /** + * Returns the list of CinematicEventListeners added to this event. + * @return + */ + private List getListeners() { + if (listeners == null) { + listeners = new ArrayList(); + } + return listeners; + } + + /** + * Add a CinematicEventListener to this event. + * @param listener CinematicEventListener + */ + public void addListener(CinematicEventListener listener) { + getListeners().add(listener); + } + + /** + * Remove a CinematicEventListener from this event. + * @param listener CinematicEventListener + */ + public void removeListener(CinematicEventListener listener) { + getListeners().remove(listener); + } + + /** + * Fast-forward the event to the given timestamp. Time=0 is the start of the event. + * @param time the time to fast forward to. + */ + public void setTime(float time) { + this.time = time ; + } + + /** + * Return the current timestamp of the event. Time=0 is the start of the event. + */ + public float getTime() { + return time; + } + + public void dispose() { + } + + +} diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java new file mode 100644 index 000000000..1376f78eb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic.events; + +import com.jme3.animation.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.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +/** + * An event based on an animation of a model. The model has to hold an + * AnimControl with valid animation (bone or spatial animations). + * + * It helps to schedule the playback of an animation on a model in a Cinematic. + * + * + * @author Nehon + */ +public class AnimationEvent extends AbstractCinematicEvent { + + // Version #2: directly keeping track on the model instead of trying to retrieve + //it from the scene according to its name, because the name is not supposed to be unique + //For backward compatibility, if the model is null it's looked up into the scene + public static final int SAVABLE_VERSION = 2; + private static final Logger log = Logger.getLogger(AnimationEvent.class.getName()); + public static final String MODEL_CHANNELS = "modelChannels"; + protected AnimChannel channel; + protected String animationName; + protected Spatial model; + //kept for backward compatibility + protected String modelName; + protected float blendTime = 0; + protected int channelIndex = 0; + // parent cinematic + protected Cinematic cinematic; + + /** + * used for serialization don't call directly use one of the following + * contructors + */ + public AnimationEvent() { + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + */ + public AnimationEvent(Spatial model, String animationName) { + this.model = model; + this.animationName = animationName; + initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param initialDuration the initialduration of the event + */ + public AnimationEvent(Spatial model, String animationName, float initialDuration) { + super(initialDuration); + this.model = model; + this.animationName = animationName; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param loopMode the loopMode + * @see LoopMode + */ + public AnimationEvent(Spatial model, String animationName, LoopMode loopMode) { + super(loopMode); + initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); + this.model = model; + this.animationName = animationName; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param initialDuration the initialduration of the event + * @param loopMode the loopMode + * @see LoopMode + */ + public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode) { + super(initialDuration, loopMode); + this.model = model; + this.animationName = animationName; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param initialDuration the initialduration of the event + * @param blendTime the time during the animation are gonna be blended + * @see AnimChannel#setAnim(java.lang.String, float) + */ + public AnimationEvent(Spatial model, String animationName, float initialDuration, float blendTime) { + super(initialDuration); + this.model = model; + this.animationName = animationName; + this.blendTime = blendTime; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param loopMode the loopMode + * @see LoopMode + * @param blendTime the time during the animation are gonna be blended + * @see AnimChannel#setAnim(java.lang.String, float) + */ + public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, float blendTime) { + super(loopMode); + initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); + this.model = model; + this.animationName = animationName; + this.blendTime = blendTime; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param initialDuration the initialduration of the event + * @param loopMode the loopMode + * @see LoopMode + * @param blendTime the time during the animation are gonna be blended + * @see AnimChannel#setAnim(java.lang.String, float) + */ + public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode, float blendTime) { + super(initialDuration, loopMode); + this.model = model; + this.animationName = animationName; + this.blendTime = blendTime; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param loopMode the loopMode + * @see LoopMode + * @param channelIndex the index of the channel default is 0. Events on the + * same channelIndex will use the same channel. + */ + public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, int channelIndex) { + super(loopMode); + initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); + this.model = model; + this.animationName = animationName; + this.channelIndex = channelIndex; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param channelIndex the index of the channel default is 0. Events on the + * same channelIndex will use the same channel. + */ + public AnimationEvent(Spatial model, String animationName, int channelIndex) { + this.model = model; + this.animationName = animationName; + initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); + this.channelIndex = channelIndex; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param initialDuration the initialduration of the event + * @param channelIndex the index of the channel default is 0. Events on the + * same channelIndex will use the same channel. + */ + public AnimationEvent(Spatial model, String animationName, float initialDuration, int channelIndex) { + super(initialDuration); + this.model = model; + this.animationName = animationName; + this.channelIndex = channelIndex; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param initialDuration the initialduration of the event + * @param loopMode the loopMode + * @see LoopMode + * @param channelIndex the index of the channel default is 0. Events on the + * same channelIndex will use the same channel. + */ + public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode, int channelIndex) { + super(initialDuration, loopMode); + this.model = model; + this.animationName = animationName; + this.channelIndex = channelIndex; + } + + @Override + public void initEvent(Application app, Cinematic cinematic) { + super.initEvent(app, cinematic); + this.cinematic = cinematic; + if (channel == null) { + Object s = cinematic.getEventData(MODEL_CHANNELS, model); + if (s == null) { + s = new HashMap(); + cinematic.putEventData(MODEL_CHANNELS, model, s); + } + + Map map = (Map) s; + this.channel = map.get(channelIndex); + if (this.channel == null) { + if (model == null) { + //the model is null we try to find it according to the name + //this should occur only when loading an old saved cinematic + //othewise it's an error + model = cinematic.getScene().getChild(modelName); + } + if (model != null) { + channel = model.getControl(AnimControl.class).createChannel(); + map.put(channelIndex, channel); + } else { + //it's an error + throw new UnsupportedOperationException("model should not be null"); + } + } + + } + } + + @Override + public void setTime(float time) { + super.setTime(time); + if (!animationName.equals(channel.getAnimationName())) { + channel.setAnim(animationName, blendTime); + } + float t = time; + if (loopMode == loopMode.Loop) { + t = t % channel.getAnimMaxTime(); + } + if (loopMode == loopMode.Cycle) { + float parity = (float) Math.ceil(time / channel.getAnimMaxTime()); + if (parity > 0 && parity % 2 == 0) { + t = channel.getAnimMaxTime() - t % channel.getAnimMaxTime(); + } else { + t = t % channel.getAnimMaxTime(); + } + + } + if (t < 0) { + channel.setTime(0); + channel.reset(true); + } + if (t > channel.getAnimMaxTime()) { + channel.setTime(t); + channel.getControl().update(0); + stop(); + } else { + channel.setTime(t); + channel.getControl().update(0); + } + } + + @Override + public void onPlay() { + channel.getControl().setEnabled(true); + if (playState == PlayState.Stopped) { + channel.setAnim(animationName, blendTime); + channel.setSpeed(speed); + channel.setLoopMode(loopMode); + channel.setTime(0); + } + } + + @Override + public void setSpeed(float speed) { + super.setSpeed(speed); + if (channel != null) { + channel.setSpeed(speed); + } + } + + @Override + public void onUpdate(float tpf) { + } + + @Override + public void onStop() { + } + + @Override + public void forceStop() { + if (channel != null) { + channel.setTime(time); + channel.reset(false); + } + super.forceStop(); + } + + @Override + public void onPause() { + if (channel != null) { + channel.getControl().setEnabled(false); + } + } + + @Override + public void setLoopMode(LoopMode loopMode) { + super.setLoopMode(loopMode); + if (channel != null) { + channel.setLoopMode(loopMode); + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + + oc.write(model, "model", null); + oc.write(animationName, "animationName", ""); + oc.write(blendTime, "blendTime", 0f); + oc.write(channelIndex, "channelIndex", 0); + + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + if (im.getFormatVersion() == 0) { + modelName = ic.readString("modelName", ""); + } + //FIXME always the same issue, because of the clonning of assets, this won't work + //we have to somehow store userdata in the spatial and then recurse the + //scene sub scenegraph to find the correct instance of the model + //This brings a reflaxion about the cinematic being an appstate, + //shouldn't it be a control over the scene + // this would allow to use the cloneForSpatial method and automatically + //rebind cloned references of original objects. + //for now as nobody probably ever saved a cinematic, this is not a critical issue + model = (Spatial) ic.readSavable("model", null); + animationName = ic.readString("animationName", ""); + blendTime = ic.readFloat("blendTime", 0f); + channelIndex = ic.readInt("channelIndex", 0); + } + + @Override + public void dispose() { + super.dispose(); + Object o = cinematic.getEventData(MODEL_CHANNELS, model); + if (o != null) { + ArrayList list = (ArrayList) o; + list.remove(channel); + if (list.isEmpty()) { + cinematic.removeEventData(MODEL_CHANNELS, model); + } + } + cinematic = null; + channel = null; + } +} diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationTrack.java b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationTrack.java new file mode 100644 index 000000000..7e8a25849 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationTrack.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic.events; + +import com.jme3.animation.LoopMode; +import com.jme3.scene.Spatial; + +/** + * @deprecated use AnimationEvent instead + * @author Nehon + */ +@Deprecated +public class AnimationTrack extends AnimationEvent { + + public AnimationTrack() { + super(); + } + + public AnimationTrack(Spatial model, String animationName) { + super(model, animationName); + } + + public AnimationTrack(Spatial model, String animationName, float initialDuration) { + super(model, animationName, initialDuration); + } + + public AnimationTrack(Spatial model, String animationName, LoopMode loopMode) { + super(model, animationName, loopMode); + + } + + public AnimationTrack(Spatial model, String animationName, float initialDuration, LoopMode loopMode) { + super(model, animationName, initialDuration, loopMode); + } +} diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/CinematicEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/CinematicEvent.java new file mode 100644 index 000000000..ddac3f01f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/CinematicEvent.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic.events; + +import com.jme3.animation.LoopMode; +import com.jme3.app.Application; +import com.jme3.cinematic.Cinematic; +import com.jme3.cinematic.PlayState; +import com.jme3.export.Savable; + +/** + * + * @author Nehon + */ +public interface CinematicEvent extends Savable { + + /** + * Starts the animation + */ + public void play(); + + /** + * Stops the animation + */ + public void stop(); + + /** + * this method can be implemented if the event needs different handling when + * stopped naturally (when the event reach its end) + * or when it was forced stopped during playback + * otherwise it just call regular stop() + */ + public void forceStop(); + + /** + * Pauses the animation + */ + public void pause(); + + /** + * Returns the actual duration of the animation + * @return the duration + */ + public float getDuration(); + + /** + * Sets the speed of the animation (1 is normal speed, 2 is twice faster) + * @param speed + */ + public void setSpeed(float speed); + + /** + * returns the speed of the animation + * @return the speed + */ + public float getSpeed(); + + /** + * returns the PlayState of the animation + * @return the plat state + */ + public PlayState getPlayState(); + + /** + * @param loop Set the loop mode for the channel. The loop mode + * determines what will happen to the animation once it finishes + * playing. + * + * For more information, see the LoopMode enum class. + * @see LoopMode + */ + public void setLoopMode(LoopMode loop); + + /** + * @return The loop mode currently set for the animation. The loop mode + * determines what will happen to the animation once it finishes + * playing. + * + * For more information, see the LoopMode enum class. + * @see LoopMode + */ + public LoopMode getLoopMode(); + + /** + * returns the initial duration of the animation at speed = 1 in seconds. + * @return the initial duration + */ + public float getInitialDuration(); + + /** + * Sets the duration of the antionamtion at speed = 1 in seconds + * @param initialDuration + */ + public void setInitialDuration(float initialDuration); + + /** + * called internally in the update method, place here anything you want to run in the update loop + * @param tpf time per frame + */ + public void internalUpdate(float tpf); + + /** + * initialize this event + * @param app the application + * @param cinematic the cinematic + */ + public void initEvent(Application app, Cinematic cinematic); + + /** + * When this method is invoked, the event should fast forward to the given time according tim 0 is the start of the event. + * @param time the time to fast forward to + */ + public void setTime(float time); + + /** + * returns the current time of the cinematic event + * @return the time + */ + public float getTime(); + + /** + * method called when an event is removed from a cinematic + * this method should remove any reference to any external objects. + */ + public void dispose(); + +} diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/CinematicEventListener.java b/jme3-core/src/main/java/com/jme3/cinematic/events/CinematicEventListener.java new file mode 100644 index 000000000..014d8a6f7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/CinematicEventListener.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic.events; + +/** + * + * @author Nehon + */ +public interface CinematicEventListener { + + public void onPlay(CinematicEvent cinematic); + public void onPause(CinematicEvent cinematic); + public void onStop(CinematicEvent cinematic); +} diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java new file mode 100644 index 000000000..edd4990f5 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic.events; + +import com.jme3.animation.LoopMode; +import com.jme3.app.Application; +import com.jme3.cinematic.Cinematic; +import com.jme3.cinematic.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 MotionEvent extends AbstractCinematicEvent implements Control { + + protected Spatial spatial; + protected int currentWayPoint; + protected float currentValue; + protected Vector3f direction = new Vector3f(); + protected Vector3f lookAt; + protected Vector3f upVector = Vector3f.UNIT_Y; + protected Quaternion rotation; + protected Direction directionType = Direction.None; + protected MotionPath path; + private boolean isControl = true; + /** + * the distance traveled by the spatial on the path + */ + protected float traveledDistance = 0; + + /** + * Enum for the different type of target direction behavior + */ + public enum Direction { + + /** + * the target stay in the starting direction + */ + None, + /** + * The target rotates with the direction of the path + */ + Path, + /** + * The target rotates with the direction of the path but with the additon of a rtotation + * you need to use the setRotation mathod when using this Direction + */ + PathAndRotation, + /** + * The target rotates with the given rotation + */ + Rotation, + /** + * The target looks at a point + * You need to use the setLookAt method when using this direction + */ + LookAt + } + + /** + * Create MotionTrack, + * when using this constructor don't forget to assign spatial and path + */ + public MotionEvent() { + super(); + } + + /** + * Creates a MotionPath for the given spatial on the given motion path + * @param spatial + * @param path + */ + public MotionEvent(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 MotionEvent(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 MotionEvent(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 MotionEvent(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) { + internalUpdate(tpf); + } + } + + @Override + public void internalUpdate(float tpf) { + if (playState == PlayState.Playing) { + time = time + (tpf * speed); + if (loopMode == loopMode.Loop && time < 0) { + time = initialDuration; + } + if ((time >= initialDuration || time < 0) && loopMode == loopMode.DontLoop) { + if (time >= initialDuration) { + path.triggerWayPointReach(path.getNbWayPoints() - 1, this); + } + stop(); + } else { + onUpdate(tpf); + } + } + } + + @Override + public void initEvent(Application app, Cinematic cinematic) { + super.initEvent(app, cinematic); + isControl = false; + } + + @Override + public void setTime(float time) { + super.setTime(time); + onUpdate(0); + } + + public void onUpdate(float tpf) { + traveledDistance = path.interpolatePath(time, this, tpf); + computeTargetDirection(); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(lookAt, "lookAt", Vector3f.ZERO); + oc.write(upVector, "upVector", Vector3f.UNIT_Y); + oc.write(rotation, "rotation", Quaternion.IDENTITY); + oc.write(directionType, "directionType", Direction.None); + oc.write(path, "path", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + lookAt = (Vector3f) in.readSavable("lookAt", Vector3f.ZERO); + upVector = (Vector3f) in.readSavable("upVector", Vector3f.UNIT_Y); + rotation = (Quaternion) in.readSavable("rotation", Quaternion.IDENTITY); + directionType = in.readEnum("directionType", Direction.class, Direction.None); + path = (MotionPath) in.readSavable("path", null); + } + + /** + * this method is meant to be called by the motion path only + * @return + */ + public boolean needsDirection() { + return directionType == Direction.Path || directionType == Direction.PathAndRotation; + } + + private void computeTargetDirection() { + switch (directionType) { + case Path: + Quaternion q = new Quaternion(); + q.lookAt(direction, upVector); + 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, upVector); + 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) { + MotionEvent control = new MotionEvent(spatial, path); + control.playState = playState; + control.currentWayPoint = currentWayPoint; + control.currentValue = currentValue; + control.direction = direction.clone(); + control.lookAt = lookAt.clone(); + control.upVector = upVector.clone(); + control.rotation = rotation.clone(); + control.initialDuration = initialDuration; + control.speed = speed; + control.loopMode = loopMode; + control.directionType = directionType; + + return control; + } + + @Override + public void onPlay() { + traveledDistance = 0; + } + + @Override + public void onStop() { + currentWayPoint = 0; + } + + @Override + public void onPause() { + } + + /** + * this method is meant to be called by the motion path only + * @return + */ + public float getCurrentValue() { + return currentValue; + } + + /** + * this method is meant to be called by the motion path only + * + */ + public void setCurrentValue(float currentValue) { + this.currentValue = currentValue; + } + + /** + * this method is meant to be called by the motion path only + * @return + */ + public int getCurrentWayPoint() { + return currentWayPoint; + } + + /** + * this method is meant to be called by the motion path only + * + */ + public void setCurrentWayPoint(int currentWayPoint) { + this.currentWayPoint = currentWayPoint; + } + + /** + * returns the direction the spatial is moving + * @return + */ + public Vector3f getDirection() { + return direction; + } + + /** + * Sets the direction of the spatial, using the Y axis as the up vector + * Use MotionEvent#setDirection((Vector3f direction,Vector3f upVector) if + * you want a custum up vector. + * This method is used by the motion path. + * @param direction + */ + public void setDirection(Vector3f direction) { + setDirection(direction, Vector3f.UNIT_Y); + } + + /** + * Sets the direction of the spatial witht ht egiven up vector + * This method is used by the motion path. + * @param direction + * @param upVector the up vector to consider for this direction + */ + public void setDirection(Vector3f direction,Vector3f upVector) { + this.direction.set(direction); + this.upVector.set(upVector); + } + + /** + * returns the direction type of the target + * @return the direction type + */ + public Direction getDirectionType() { + return directionType; + } + + /** + * Sets the direction type of the target + * On each update the direction given to the target can have different behavior + * See the Direction Enum for explanations + * @param directionType the direction type + */ + public void setDirectionType(Direction directionType) { + this.directionType = directionType; + } + + /** + * Set the lookAt for the target + * This can be used only if direction Type is Direction.LookAt + * @param lookAt the position to look at + * @param upVector the up vector + */ + public void setLookAt(Vector3f lookAt, Vector3f upVector) { + this.lookAt = lookAt; + this.upVector = upVector; + } + + /** + * returns the rotation of the target + * @return the rotation quaternion + */ + public Quaternion getRotation() { + return rotation; + } + + /** + * sets the rotation of the target + * This can be used only if direction Type is Direction.PathAndRotation or Direction.Rotation + * With PathAndRotation the target will face the direction of the path multiplied by the given Quaternion. + * With Rotation the rotation of the target will be set with the given Quaternion. + * @param rotation the rotation quaternion + */ + public void setRotation(Quaternion rotation) { + this.rotation = rotation; + } + + /** + * retun the motion path this control follows + * @return + */ + public MotionPath getPath() { + return path; + } + + /** + * Sets the motion path to follow + * @param path + */ + public void setPath(MotionPath path) { + this.path = path; + } + + public void setEnabled(boolean enabled) { + if (enabled) { + play(); + } else { + pause(); + } + } + + public boolean isEnabled() { + return playState != PlayState.Stopped; + } + + public void render(RenderManager rm, ViewPort vp) { + } + + public void setSpatial(Spatial spatial) { + this.spatial = spatial; + } + + public Spatial getSpatial() { + return spatial; + } + + /** + * return the distance traveled by the spatial on the path + * @return + */ + public float getTraveledDistance() { + return traveledDistance; + } +} diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/MotionTrack.java b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionTrack.java new file mode 100644 index 000000000..2958241bb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionTrack.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic.events; + +import com.jme3.animation.LoopMode; +import com.jme3.cinematic.MotionPath; +import com.jme3.scene.Spatial; + +/** + * + * @author Nehon + * @deprecated use MotionEvent instead + */ +@Deprecated +public class MotionTrack extends MotionEvent { + + /** + * 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(spatial, 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(spatial, path, initialDuration); + } + + /** + * 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(spatial, path, 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(spatial, path, initialDuration, loopMode); + } +} diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/SoundEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/SoundEvent.java new file mode 100644 index 000000000..1d52f221f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/SoundEvent.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic.events; + +import com.jme3.animation.LoopMode; +import com.jme3.app.Application; +import com.jme3.audio.AudioNode; +import com.jme3.audio.AudioSource; +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 SoundEvent extends AbstractCinematicEvent { + + protected String path; + protected AudioNode audioNode; + protected boolean stream = false; + + /** + * creates a sound track from the given resource path + * @param path the path to an audi file (ie : "Sounds/mySound.wav") + */ + public SoundEvent(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 SoundEvent(String path, boolean stream) { + this(path); + this.stream = stream; + } + + /** + * 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 + * @param initialDuration the nitial duration of the event + */ + public SoundEvent(String path, boolean stream, float initialDuration) { + super(initialDuration); + this.path = path; + this.stream = stream; + } + + /** + * 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 + * @param loopMode the loopMode + * @see LoopMode + */ + public SoundEvent(String path, boolean stream, LoopMode loopMode) { + super(loopMode); + this.path = path; + this.stream = stream; + } + + /** + * 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 + * @param initialDuration the nitial duration of the event + * @param loopMode the loopMode + * @see LoopMode + */ + public SoundEvent(String path, boolean stream, float initialDuration, LoopMode loopMode) { + super(initialDuration, loopMode); + this.path = path; + this.stream = stream; + } + + /** + * creates a sound track from the given resource path + * @param path the path to an audi file (ie : "Sounds/mySound.wav") + * @param initialDuration the nitial duration of the event + */ + public SoundEvent(String path, float initialDuration) { + super(initialDuration); + 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 loopMode the loopMode + * @see LoopMode + */ + public SoundEvent(String path, LoopMode loopMode) { + super(loopMode); + 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 initialDuration the nitial duration of the event + * @param loopMode the loopMode + * @see LoopMode + */ + public SoundEvent(String path, float initialDuration, LoopMode loopMode) { + super(initialDuration, loopMode); + this.path = path; + } + + /** + * creates a sound event + * used for serialization + */ + public SoundEvent() { + } + + @Override + public void initEvent(Application app, Cinematic cinematic) { + super.initEvent(app, cinematic); + audioNode = new AudioNode(app.getAssetManager(), path, stream); + audioNode.setPositional(false); + setLoopMode(loopMode); + } + + @Override + public void setTime(float time) { + super.setTime(time); + //can occur on rewind + if (time < 0f) { + stop(); + }else{ + audioNode.setTimeOffset(time); + } + } + + @Override + public void onPlay() { + audioNode.play(); + } + + @Override + public void onStop() { + audioNode.stop(); + + } + + @Override + public void onPause() { + audioNode.pause(); + } + + @Override + public void onUpdate(float tpf) { + if (audioNode.getStatus() == AudioSource.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/jme3-core/src/main/java/com/jme3/cinematic/events/SoundTrack.java b/jme3-core/src/main/java/com/jme3/cinematic/events/SoundTrack.java new file mode 100644 index 000000000..2b245b757 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/SoundTrack.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic.events; + +import com.jme3.animation.LoopMode; + +/** + * A sound track to be played in a cinematic. + * @author Nehon + * @deprecated use SoundEvent instead + */ +@Deprecated +public class SoundTrack extends SoundEvent { + + /** + * 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) { + super(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) { + super(path, stream); + } + + public SoundTrack(String path, boolean stream, float initialDuration) { + super(path, stream, initialDuration); + } + + public SoundTrack(String path, boolean stream, LoopMode loopMode) { + super(path, stream, loopMode); + } + + public SoundTrack(String path, boolean stream, float initialDuration, LoopMode loopMode) { + super(path, stream, initialDuration, loopMode); + + } + + public SoundTrack(String path, float initialDuration) { + super(path, initialDuration); + } + + public SoundTrack(String path, LoopMode loopMode) { + super(path, loopMode); + } + + public SoundTrack(String path, float initialDuration, LoopMode loopMode) { + super(path, initialDuration, loopMode); + } + + public SoundTrack() { + super(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/collision/Collidable.java b/jme3-core/src/main/java/com/jme3/collision/Collidable.java new file mode 100644 index 000000000..5da7b8a54 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/Collidable.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.collision; + +/** + * Interface for Collidable objects. + * Classes that implement this interface are marked as collidable, meaning + * they support collision detection between other objects that are also + * collidable. + * + * @author Kirill Vainer + */ +public interface Collidable { + + /** + * Check collision with another Collidable. + * + * @param other The object to check collision against + * @param results Will contain the list of {@link CollisionResult}s. + * @return how many collisions were found between this and other + */ + public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException; +} diff --git a/jme3-core/src/main/java/com/jme3/collision/CollisionResult.java b/jme3-core/src/main/java/com/jme3/collision/CollisionResult.java new file mode 100644 index 000000000..a777bc9b6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/CollisionResult.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.collision; + +import com.jme3.math.Triangle; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; + +/** + * A CollisionResult represents a single collision instance + * between two {@link Collidable}. A collision check can result in many + * collision instances (places where collision has occured). + * + * @author Kirill Vainer + */ +public class CollisionResult implements Comparable { + + 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) { + return Float.compare(distance, other.distance); + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof CollisionResult){ + return ((CollisionResult)obj).compareTo(this) == 0; + } + return super.equals(obj); + } + + public Vector3f getContactPoint() { + return contactPoint; + } + + public Vector3f getContactNormal() { + return contactNormal; + } + + public float getDistance() { + return distance; + } + + public Geometry getGeometry() { + return geometry; + } + + public int getTriangleIndex() { + return triangleIndex; + } + + public String toString() { + return "CollisionResult[geometry=" + geometry + + ", contactPoint=" + contactPoint + + ", contactNormal=" + contactNormal + + ", distance=" + distance + + ", triangleIndex=" + triangleIndex + + "]"; + } +} diff --git a/jme3-core/src/main/java/com/jme3/collision/CollisionResults.java b/jme3-core/src/main/java/com/jme3/collision/CollisionResults.java new file mode 100644 index 000000000..7f714fdb6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/CollisionResults.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.collision; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; + +/** + * CollisionResults is a collection returned as a result of a + * collision detection operation done by {@link Collidable}. + * + * @author Kirill Vainer + */ +public class CollisionResults implements Iterable { + + private final ArrayList results = new ArrayList(); + private boolean sorted = true; + + /** + * Clears all collision results added to this list + */ + public void clear(){ + results.clear(); + } + + /** + * Iterator for iterating over the collision results. + * + * @return the iterator + */ + public Iterator 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/jme3-core/src/main/java/com/jme3/collision/MotionAllowedListener.java b/jme3-core/src/main/java/com/jme3/collision/MotionAllowedListener.java new file mode 100644 index 000000000..fa4dda339 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/MotionAllowedListener.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.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/jme3-core/src/main/java/com/jme3/collision/SweepSphere.java b/jme3-core/src/main/java/com/jme3/collision/SweepSphere.java new file mode 100644 index 000000000..de26c8e99 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/SweepSphere.java @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.collision; + +import com.jme3.math.*; + +/** + * No longer public .. + * + * @author Kirill Vainer + */ +@Deprecated +class SweepSphere implements Collidable { + + private Vector3f velocity = new Vector3f(); + private Vector3f center = new Vector3f(); + private Vector3f dimension = new Vector3f(); + private Vector3f invDim = new Vector3f(); + + private final Triangle scaledTri = new Triangle(); + private final Plane triPlane = new Plane(); + private final Vector3f temp1 = new Vector3f(), + temp2 = new Vector3f(), + temp3 = new Vector3f(); + private final Vector3f sVelocity = new Vector3f(), + sCenter = new Vector3f(); + + public Vector3f getCenter() { + return center; + } + + public void setCenter(Vector3f center) { + this.center.set(center); + } + + public Vector3f getDimension() { + return dimension; + } + + public void setDimension(Vector3f dimension) { + this.dimension.set(dimension); + this.invDim.set(1,1,1).divideLocal(dimension); + } + + public void setDimension(float x, float y, float z){ + this.dimension.set(x,y,z); + this.invDim.set(1,1,1).divideLocal(dimension); + } + + public void setDimension(float dim){ + this.dimension.set(dim, dim, dim); + this.invDim.set(1,1,1).divideLocal(dimension); + } + + public Vector3f getVelocity() { + return velocity; + } + + public void setVelocity(Vector3f velocity) { + this.velocity.set(velocity); + } + + private boolean pointsOnSameSide(Vector3f p1, Vector3f p2, Vector3f line1, Vector3f line2) { + // V1 = (line2 - line1) x (p1 - line1) + // V2 = (p2 - line1) x (line2 - line1) + + temp1.set(line2).subtractLocal(line1); + temp3.set(temp1); + temp2.set(p1).subtractLocal(line1); + temp1.crossLocal(temp2); + + temp2.set(p2).subtractLocal(line1); + temp3.crossLocal(temp2); + + // V1 . V2 >= 0 + return temp1.dot(temp3) >= 0; + } + + private boolean isPointInTriangle(Vector3f point, AbstractTriangle tri) { + if (pointsOnSameSide(point, tri.get1(), tri.get2(), tri.get3()) + && pointsOnSameSide(point, tri.get2(), tri.get1(), tri.get3()) + && pointsOnSameSide(point, tri.get3(), tri.get1(), tri.get2())) + return true; + return false; + } + + private static float getLowestRoot(float a, float b, float c, float maxR) { + float determinant = b * b - 4f * a * c; + if (determinant < 0){ + return Float.NaN; + } + + float sqrtd = FastMath.sqrt(determinant); + float r1 = (-b - sqrtd) / (2f * a); + float r2 = (-b + sqrtd) / (2f * a); + + if (r1 > r2){ + float temp = r2; + r2 = r1; + r1 = temp; + } + + if (r1 > 0 && r1 < maxR){ + return r1; + } + + if (r2 > 0 && r2 < maxR){ + return r2; + } + + return Float.NaN; + } + + private float collideWithVertex(Vector3f sCenter, Vector3f sVelocity, + float velocitySquared, Vector3f vertex, float t) { + // A = velocity * velocity + // B = 2 * (velocity . (center - vertex)); + // C = ||vertex - center||^2 - 1f; + + temp1.set(sCenter).subtractLocal(vertex); + float a = velocitySquared; + float b = 2f * sVelocity.dot(temp1); + float c = temp1.negateLocal().lengthSquared() - 1f; + float newT = getLowestRoot(a, b, c, t); + +// float A = velocitySquared; +// float B = sCenter.subtract(vertex).dot(sVelocity) * 2f; +// float C = vertex.subtract(sCenter).lengthSquared() - 1f; +// +// float newT = getLowestRoot(A, B, C, Float.MAX_VALUE); +// if (newT > 1.0f) +// newT = Float.NaN; + + return newT; + } + + private float collideWithSegment(Vector3f sCenter, + Vector3f sVelocity, + float velocitySquared, + Vector3f l1, + Vector3f l2, + float t, + Vector3f store) { + Vector3f edge = temp1.set(l2).subtractLocal(l1); + Vector3f base = temp2.set(l1).subtractLocal(sCenter); + + float edgeSquared = edge.lengthSquared(); + float baseSquared = base.lengthSquared(); + + float EdotV = edge.dot(sVelocity); + float EdotB = edge.dot(base); + + float a = (edgeSquared * -velocitySquared) + EdotV * EdotV; + float b = (edgeSquared * 2f * sVelocity.dot(base)) + - (2f * EdotV * EdotB); + float c = (edgeSquared * (1f - baseSquared)) + EdotB * EdotB; + float newT = getLowestRoot(a, b, c, t); + if (!Float.isNaN(newT)){ + float f = (EdotV * newT - EdotB) / edgeSquared; + if (f >= 0f && f < 1f){ + store.scaleAdd(f, edge, l1); + return newT; + } + } + return Float.NaN; + } + + private CollisionResult collideWithTriangle(AbstractTriangle tri){ + // scale scaledTriangle based on dimension + scaledTri.get1().set(tri.get1()).multLocal(invDim); + scaledTri.get2().set(tri.get2()).multLocal(invDim); + scaledTri.get3().set(tri.get3()).multLocal(invDim); +// Vector3f sVelocity = velocity.mult(invDim); +// Vector3f sCenter = center.mult(invDim); + velocity.mult(invDim, sVelocity); + center.mult(invDim, sCenter); + + triPlane.setPlanePoints(scaledTri); + + float normalDotVelocity = triPlane.getNormal().dot(sVelocity); + // XXX: sVelocity.normalize() needed? + // back facing scaledTriangles not considered + if (normalDotVelocity > 0f) + return null; + + float t0, t1; + boolean embedded = false; + + float signedDistanceToPlane = triPlane.pseudoDistance(sCenter); + if (normalDotVelocity == 0.0f){ + // we are travelling exactly parrallel to the plane + if (FastMath.abs(signedDistanceToPlane) >= 1.0f){ + // no collision possible + return null; + }else{ + // we are embedded + t0 = 0; + t1 = 1; + embedded = true; + System.out.println("EMBEDDED"); + return null; + } + }else{ + t0 = (-1f - signedDistanceToPlane) / normalDotVelocity; + t1 = ( 1f - signedDistanceToPlane) / normalDotVelocity; + + if (t0 > t1){ + float tf = t1; + t1 = t0; + t0 = tf; + } + + if (t0 > 1.0f || t1 < 0.0f){ + // collision is out of this sVelocity range + return null; + } + + // clamp the interval to [0, 1] + t0 = Math.max(t0, 0.0f); + t1 = Math.min(t1, 1.0f); + } + + boolean foundCollision = false; + float minT = 1f; + + Vector3f contactPoint = new Vector3f(); + Vector3f contactNormal = new Vector3f(); + +// if (!embedded){ + // check against the inside of the scaledTriangle + // contactPoint = sCenter - p.normal + t0 * sVelocity + contactPoint.set(sVelocity); + contactPoint.multLocal(t0); + contactPoint.addLocal(sCenter); + contactPoint.subtractLocal(triPlane.getNormal()); + + // test to see if the collision is on a scaledTriangle interior + if (isPointInTriangle(contactPoint, scaledTri) && !embedded){ + foundCollision = true; + + minT = t0; + + // scale collision point back into R3 + contactPoint.multLocal(dimension); + contactNormal.set(velocity).multLocal(t0); + contactNormal.addLocal(center); + contactNormal.subtractLocal(contactPoint).normalizeLocal(); + +// contactNormal.set(triPlane.getNormal()); + + CollisionResult result = new CollisionResult(); + result.setContactPoint(contactPoint); + result.setContactNormal(contactNormal); + result.setDistance(minT * velocity.length()); + return result; + } +// } + + float velocitySquared = sVelocity.lengthSquared(); + + Vector3f v1 = scaledTri.get1(); + Vector3f v2 = scaledTri.get2(); + Vector3f v3 = scaledTri.get3(); + + // vertex 1 + float newT; + newT = collideWithVertex(sCenter, sVelocity, velocitySquared, v1, minT); + if (!Float.isNaN(newT)){ + minT = newT; + contactPoint.set(v1); + foundCollision = true; + } + + // vertex 2 + newT = collideWithVertex(sCenter, sVelocity, velocitySquared, v2, minT); + if (!Float.isNaN(newT)){ + minT = newT; + contactPoint.set(v2); + foundCollision = true; + } + + // vertex 3 + newT = collideWithVertex(sCenter, sVelocity, velocitySquared, v3, minT); + if (!Float.isNaN(newT)){ + minT = newT; + contactPoint.set(v3); + foundCollision = true; + } + + // edge 1-2 + newT = collideWithSegment(sCenter, sVelocity, velocitySquared, v1, v2, minT, contactPoint); + if (!Float.isNaN(newT)){ + minT = newT; + foundCollision = true; + } + + // edge 2-3 + newT = collideWithSegment(sCenter, sVelocity, velocitySquared, v2, v3, minT, contactPoint); + if (!Float.isNaN(newT)){ + minT = newT; + foundCollision = true; + } + + // edge 3-1 + newT = collideWithSegment(sCenter, sVelocity, velocitySquared, v3, v1, minT, contactPoint); + if (!Float.isNaN(newT)){ + minT = newT; + foundCollision = true; + } + + if (foundCollision){ + // compute contact normal based on minimum t + contactPoint.multLocal(dimension); + contactNormal.set(velocity).multLocal(t0); + contactNormal.addLocal(center); + contactNormal.subtractLocal(contactPoint).normalizeLocal(); + + CollisionResult result = new CollisionResult(); + result.setContactPoint(contactPoint); + result.setContactNormal(contactNormal); + result.setDistance(minT * velocity.length()); + + return result; + }else{ + return null; + } + } + + public CollisionResult collideWithSweepSphere(SweepSphere other){ + temp1.set(velocity).subtractLocal(other.velocity); + temp2.set(center).subtractLocal(other.center); + temp3.set(dimension).addLocal(other.dimension); + // delta V + // delta C + // Rsum + + float a = temp1.lengthSquared(); + float b = 2f * temp1.dot(temp2); + float c = temp2.lengthSquared() - temp3.getX() * temp3.getX(); + float t = getLowestRoot(a, b, c, 1); + + // no collision + if (Float.isNaN(t)) + return null; + + CollisionResult result = new CollisionResult(); + result.setDistance(velocity.length() * t); + + temp1.set(velocity).multLocal(t).addLocal(center); + temp2.set(other.velocity).multLocal(t).addLocal(other.center); + temp3.set(temp2).subtractLocal(temp1); + // temp3 contains center to other.center vector + + // normalize it to get normal + temp2.set(temp3).normalizeLocal(); + result.setContactNormal(new Vector3f(temp2)); + + // temp3 is contact point + temp3.set(temp2).multLocal(dimension).addLocal(temp1); + result.setContactPoint(new Vector3f(temp3)); + + return result; + } + + public static void main(String[] args){ + SweepSphere ss = new SweepSphere(); + ss.setCenter(Vector3f.ZERO); + ss.setDimension(1); + ss.setVelocity(new Vector3f(10, 10, 10)); + + SweepSphere ss2 = new SweepSphere(); + ss2.setCenter(new Vector3f(5, 5, 5)); + ss2.setDimension(1); + ss2.setVelocity(new Vector3f(-10, -10, -10)); + + CollisionResults cr = new CollisionResults(); + ss.collideWith(ss2, cr); + if (cr.size() > 0){ + CollisionResult c = cr.getClosestCollision(); + System.out.println("D = "+c.getDistance()); + System.out.println("P = "+c.getContactPoint()); + System.out.println("N = "+c.getContactNormal()); + } + } + + public int collideWith(Collidable other, CollisionResults results) + throws UnsupportedCollisionException { + if (other instanceof AbstractTriangle){ + AbstractTriangle tri = (AbstractTriangle) other; + CollisionResult result = collideWithTriangle(tri); + if (result != null){ + results.addCollision(result); + return 1; + } + return 0; + }else if (other instanceof SweepSphere){ + SweepSphere sph = (SweepSphere) other; + + CollisionResult result = collideWithSweepSphere(sph); + if (result != null){ + results.addCollision(result); + return 1; + } + return 0; + }else{ + throw new UnsupportedCollisionException(); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/collision/UnsupportedCollisionException.java b/jme3-core/src/main/java/com/jme3/collision/UnsupportedCollisionException.java new file mode 100644 index 000000000..e782e8e81 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/UnsupportedCollisionException.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.collision; + +/** + * Thrown by {@link Collidable} when the requested collision query could not + * be completed because one of the collidables does not support colliding with + * the other. + * + * @author Kirill Vainer + */ +public class UnsupportedCollisionException extends UnsupportedOperationException { + + public UnsupportedCollisionException(Throwable arg0) { + super(arg0); + } + + public UnsupportedCollisionException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + public UnsupportedCollisionException(String arg0) { + super(arg0); + } + + public UnsupportedCollisionException() { + super(); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java b/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java new file mode 100644 index 000000000..52e4f91ca --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.collision.bih; + +import com.jme3.bounding.BoundingBox; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.export.*; +import com.jme3.math.Matrix4f; +import com.jme3.math.Ray; +import com.jme3.math.Triangle; +import com.jme3.math.Vector3f; +import com.jme3.util.TempVars; +import java.io.IOException; +import static java.lang.Math.max; +import static java.lang.Math.min; +import java.util.ArrayList; + +/** + * Bounding Interval Hierarchy. + * Based on: + * + * Instant Ray Tracing: The Bounding Interval Hierarchy + * By Carsten Wächter and Alexander Keller + */ +public final class BIHNode implements Savable { + + private int leftIndex, rightIndex; + private BIHNode left; + private BIHNode right; + private float leftPlane; + private float rightPlane; + private int axis; + + //Do not do this: It increases memory usage of each BIHNode by at least 56 bytes! + // + //private Triangle tmpTriangle = new Triangle(); + + public BIHNode(int l, int r) { + leftIndex = l; + rightIndex = r; + axis = 3; // indicates leaf + } + + public BIHNode(int axis) { + this.axis = axis; + } + + public BIHNode() { + } + + public BIHNode getLeftChild() { + return left; + } + + public void setLeftChild(BIHNode left) { + this.left = left; + } + + public float getLeftPlane() { + return leftPlane; + } + + public void setLeftPlane(float leftPlane) { + this.leftPlane = leftPlane; + } + + public BIHNode getRightChild() { + return right; + } + + public void setRightChild(BIHNode right) { + this.right = right; + } + + public float getRightPlane() { + return rightPlane; + } + + public void setRightPlane(float rightPlane) { + this.rightPlane = rightPlane; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(leftIndex, "left_index", 0); + oc.write(rightIndex, "right_index", 0); + oc.write(leftPlane, "left_plane", 0); + oc.write(rightPlane, "right_plane", 0); + oc.write(axis, "axis", 0); + oc.write(left, "left_node", null); + oc.write(right, "right_node", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + leftIndex = ic.readInt("left_index", 0); + rightIndex = ic.readInt("right_index", 0); + leftPlane = ic.readFloat("left_plane", 0); + rightPlane = ic.readFloat("right_plane", 0); + axis = ic.readInt("axis", 0); + left = (BIHNode) ic.readSavable("left_node", null); + right = (BIHNode) ic.readSavable("right_node", null); + } + + public static final class BIHStackData { + + private final BIHNode node; + private final float min, max; + + BIHStackData(BIHNode node, float min, float max) { + this.node = node; + this.min = min; + this.max = max; + } + } + + public final int intersectWhere(Collidable col, + BoundingBox box, + Matrix4f worldMatrix, + BIHTree tree, + CollisionResults results) { + + TempVars vars = TempVars.get(); + ArrayList stack = vars.bihStack; + stack.clear(); + + float[] minExts = {box.getCenter().x - box.getXExtent(), + box.getCenter().y - box.getYExtent(), + box.getCenter().z - box.getZExtent()}; + + float[] maxExts = {box.getCenter().x + box.getXExtent(), + box.getCenter().y + box.getYExtent(), + box.getCenter().z + box.getZExtent()}; + + stack.add(new BIHStackData(this, 0, 0)); + + Triangle t = new Triangle(); + int cols = 0; + + stackloop: + while (stack.size() > 0) { + BIHNode node = stack.remove(stack.size() - 1).node; + + while (node.axis != 3) { + int a = node.axis; + + float maxExt = maxExts[a]; + float minExt = minExts[a]; + + if (node.leftPlane < node.rightPlane) { + // means there's a gap in the middle + // if the box is in that gap, we stop there + if (minExt > node.leftPlane + && maxExt < node.rightPlane) { + continue stackloop; + } + } + + if (maxExt < node.rightPlane) { + node = node.left; + } else if (minExt > node.leftPlane) { + node = node.right; + } else { + stack.add(new BIHStackData(node.right, 0, 0)); + node = node.left; + } +// if (maxExt < node.leftPlane +// && maxExt < node.rightPlane){ +// node = node.left; +// }else if (minExt > node.leftPlane +// && minExt > node.rightPlane){ +// node = node.right; +// }else{ + +// } + } + + for (int i = node.leftIndex; i <= node.rightIndex; i++) { + tree.getTriangle(i, t.get1(), t.get2(), t.get3()); + if (worldMatrix != null) { + worldMatrix.mult(t.get1(), t.get1()); + worldMatrix.mult(t.get2(), t.get2()); + worldMatrix.mult(t.get3(), t.get3()); + } + + int added = col.collideWith(t, results); + + if (added > 0) { + int index = tree.getTriangleIndex(i); + int start = results.size() - added; + + for (int j = start; j < results.size(); j++) { + CollisionResult cr = results.getCollisionDirect(j); + cr.setTriangleIndex(index); + } + + cols += added; + } + } + } + vars.release(); + return cols; + } + + public final int intersectBrute(Ray r, + Matrix4f worldMatrix, + BIHTree tree, + float sceneMin, + float sceneMax, + CollisionResults results) { + float tHit = Float.POSITIVE_INFINITY; + + TempVars vars = TempVars.get(); + + Vector3f v1 = vars.vect1, + v2 = vars.vect2, + v3 = vars.vect3; + + int cols = 0; + + ArrayList stack = vars.bihStack; + stack.clear(); + stack.add(new BIHStackData(this, 0, 0)); + stackloop: + while (stack.size() > 0) { + + BIHStackData data = stack.remove(stack.size() - 1); + BIHNode node = data.node; + + leafloop: + while (node.axis != 3) { // while node is not a leaf + BIHNode nearNode, farNode; + nearNode = node.left; + farNode = node.right; + + stack.add(new BIHStackData(farNode, 0, 0)); + node = nearNode; + } + + // a leaf + for (int i = node.leftIndex; i <= node.rightIndex; i++) { + tree.getTriangle(i, v1, v2, v3); + + if (worldMatrix != null) { + worldMatrix.mult(v1, v1); + worldMatrix.mult(v2, v2); + worldMatrix.mult(v3, v3); + } + + float t = r.intersects(v1, v2, v3); + if (t < tHit) { + tHit = t; + Vector3f contactPoint = new Vector3f(r.direction).multLocal(tHit).addLocal(r.origin); + CollisionResult cr = new CollisionResult(contactPoint, tHit); + cr.setTriangleIndex(tree.getTriangleIndex(i)); + results.addCollision(cr); + cols++; + } + } + } + vars.release(); + return cols; + } + + public final int intersectWhere(Ray r, + Matrix4f worldMatrix, + BIHTree tree, + float sceneMin, + float sceneMax, + CollisionResults results) { + + TempVars vars = TempVars.get(); + ArrayList stack = vars.bihStack; + stack.clear(); + +// float tHit = Float.POSITIVE_INFINITY; + + Vector3f o = vars.vect1.set(r.getOrigin()); + Vector3f d = vars.vect2.set(r.getDirection()); + + Matrix4f inv =vars.tempMat4.set(worldMatrix).invertLocal(); + + inv.mult(r.getOrigin(), r.getOrigin()); + + // Fixes rotation collision bug + inv.multNormal(r.getDirection(), r.getDirection()); +// inv.multNormalAcross(r.getDirection(), r.getDirection()); + + float[] origins = {r.getOrigin().x, + r.getOrigin().y, + r.getOrigin().z}; + + float[] invDirections = {1f / r.getDirection().x, + 1f / r.getDirection().y, + 1f / r.getDirection().z}; + + r.getDirection().normalizeLocal(); + + Vector3f v1 = vars.vect3, + v2 = vars.vect4, + v3 = vars.vect5; + int cols = 0; + + stack.add(new BIHStackData(this, sceneMin, sceneMax)); + stackloop: + while (stack.size() > 0) { + + BIHStackData data = stack.remove(stack.size() - 1); + BIHNode node = data.node; + float tMin = data.min, + tMax = data.max; + + if (tMax < tMin) { + continue; + } + + leafloop: + while (node.axis != 3) { // while node is not a leaf + int a = node.axis; + + // find the origin and direction value for the given axis + float origin = origins[a]; + float invDirection = invDirections[a]; + + float tNearSplit, tFarSplit; + BIHNode nearNode, farNode; + + tNearSplit = (node.leftPlane - origin) * invDirection; + tFarSplit = (node.rightPlane - origin) * invDirection; + nearNode = node.left; + farNode = node.right; + + if (invDirection < 0) { + float tmpSplit = tNearSplit; + tNearSplit = tFarSplit; + tFarSplit = tmpSplit; + + BIHNode tmpNode = nearNode; + nearNode = farNode; + farNode = tmpNode; + } + + if (tMin > tNearSplit && tMax < tFarSplit) { + continue stackloop; + } + + if (tMin > tNearSplit) { + tMin = max(tMin, tFarSplit); + node = farNode; + } else if (tMax < tFarSplit) { + tMax = min(tMax, tNearSplit); + node = nearNode; + } else { + stack.add(new BIHStackData(farNode, max(tMin, tFarSplit), tMax)); + tMax = min(tMax, tNearSplit); + node = nearNode; + } + } + +// if ( (node.rightIndex - node.leftIndex) > minTrisPerNode){ +// // on demand subdivision +// node.subdivide(); +// stack.add(new BIHStackData(node, tMin, tMax)); +// continue stackloop; +// } + + // a leaf + for (int i = node.leftIndex; i <= node.rightIndex; i++) { + tree.getTriangle(i, v1, v2, v3); + + float t = r.intersects(v1, v2, v3); + if (!Float.isInfinite(t)) { + if (worldMatrix != null) { + worldMatrix.mult(v1, v1); + worldMatrix.mult(v2, v2); + worldMatrix.mult(v3, v3); + float t_world = new Ray(o, d).intersects(v1, v2, v3); + t = t_world; + } + + Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null); + Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o); + float worldSpaceDist = o.distance(contactPoint); + + CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist); + cr.setContactNormal(contactNormal); + cr.setTriangleIndex(tree.getTriangleIndex(i)); + results.addCollision(cr); + cols++; + } + } + } + vars.release(); + r.setOrigin(o); + r.setDirection(d); + + return cols; + } +} diff --git a/jme3-core/src/main/java/com/jme3/collision/bih/BIHTree.java b/jme3-core/src/main/java/com/jme3/collision/bih/BIHTree.java new file mode 100644 index 000000000..6c43658fe --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/bih/BIHTree.java @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.collision.bih; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingSphere; +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; +import com.jme3.collision.UnsupportedCollisionException; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.math.Ray; +import com.jme3.math.Vector3f; +import com.jme3.scene.CollisionData; +import com.jme3.scene.Mesh; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.scene.mesh.VirtualIndexBuffer; +import com.jme3.scene.mesh.WrappedIndexBuffer; +import com.jme3.util.TempVars; +import java.io.IOException; +import static java.lang.Math.max; +import java.nio.FloatBuffer; + +public class BIHTree implements CollisionData { + + public static final int MAX_TREE_DEPTH = 100; + public static final int MAX_TRIS_PER_NODE = 21; + private Mesh mesh; + private BIHNode root; + private int maxTrisPerNode; + private int numTris; + private float[] pointData; + private int[] triIndices; + + private transient CollisionResults boundResults = new CollisionResults(); + private transient float[] bihSwapTmp; + + private static final TriangleAxisComparator[] comparators = new TriangleAxisComparator[] + { + new TriangleAxisComparator(0), + new TriangleAxisComparator(1), + new TriangleAxisComparator(2) + }; + + private void initTriList(FloatBuffer vb, IndexBuffer ib) { + pointData = new float[numTris * 3 * 3]; + int p = 0; + for (int i = 0; i < numTris * 3; i += 3) { + int vert = ib.get(i) * 3; + pointData[p++] = vb.get(vert++); + pointData[p++] = vb.get(vert++); + pointData[p++] = vb.get(vert); + + vert = ib.get(i + 1) * 3; + pointData[p++] = vb.get(vert++); + pointData[p++] = vb.get(vert++); + pointData[p++] = vb.get(vert); + + vert = ib.get(i + 2) * 3; + pointData[p++] = vb.get(vert++); + pointData[p++] = vb.get(vert++); + pointData[p++] = vb.get(vert); + } + + triIndices = new int[numTris]; + for (int i = 0; i < numTris; i++) { + triIndices[i] = i; + } + } + + public BIHTree(Mesh mesh, int maxTrisPerNode) { + this.mesh = mesh; + this.maxTrisPerNode = maxTrisPerNode; + + if (maxTrisPerNode < 1 || mesh == null) { + throw new IllegalArgumentException(); + } + + bihSwapTmp = new float[9]; + + FloatBuffer vb = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); + IndexBuffer ib = mesh.getIndexBuffer(); + if (ib == null) { + ib = new VirtualIndexBuffer(mesh.getVertexCount(), mesh.getMode()); + } else if (mesh.getMode() != Mode.Triangles) { + ib = new WrappedIndexBuffer(mesh); + } + + numTris = ib.size() / 3; + initTriList(vb, ib); + } + + public BIHTree(Mesh mesh) { + this(mesh, MAX_TRIS_PER_NODE); + } + + public BIHTree() { + } + + public void construct() { + BoundingBox sceneBbox = createBox(0, numTris - 1); + root = createNode(0, numTris - 1, sceneBbox, 0); + } + + private BoundingBox createBox(int l, int r) { + TempVars vars = TempVars.get(); + + Vector3f min = vars.vect1.set(new Vector3f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)); + Vector3f max = vars.vect2.set(new Vector3f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY)); + + Vector3f v1 = vars.vect3, + v2 = vars.vect4, + v3 = vars.vect5; + + for (int i = l; i <= r; i++) { + getTriangle(i, v1, v2, v3); + BoundingBox.checkMinMax(min, max, v1); + BoundingBox.checkMinMax(min, max, v2); + BoundingBox.checkMinMax(min, max, v3); + } + + BoundingBox bbox = new BoundingBox(min, max); + vars.release(); + return bbox; + } + + int getTriangleIndex(int triIndex) { + return triIndices[triIndex]; + } + + private int sortTriangles(int l, int r, float split, int axis) { + int pivot = l; + int j = r; + + TempVars vars = TempVars.get(); + + Vector3f v1 = vars.vect1, + v2 = vars.vect2, + v3 = vars.vect3; + + while (pivot <= j) { + getTriangle(pivot, v1, v2, v3); + v1.addLocal(v2).addLocal(v3).multLocal(FastMath.ONE_THIRD); + if (v1.get(axis) > split) { + swapTriangles(pivot, j); + --j; + } else { + ++pivot; + } + } + + vars.release(); + pivot = (pivot == l && j < pivot) ? j : pivot; + return pivot; + } + + private void setMinMax(BoundingBox bbox, boolean doMin, int axis, float value) { + Vector3f min = bbox.getMin(null); + Vector3f max = bbox.getMax(null); + + if (doMin) { + min.set(axis, value); + } else { + max.set(axis, value); + } + + bbox.setMinMax(min, max); + } + + private float getMinMax(BoundingBox bbox, boolean doMin, int axis) { + if (doMin) { + return bbox.getMin(null).get(axis); + } else { + return bbox.getMax(null).get(axis); + } + } + +// private BIHNode createNode2(int l, int r, BoundingBox nodeBbox, int depth){ +// if ((r - l) < maxTrisPerNode || depth > 100) +// return createLeaf(l, r); +// +// BoundingBox currentBox = createBox(l, r); +// int axis = depth % 3; +// float split = currentBox.getCenter().get(axis); +// +// TriangleAxisComparator comparator = comparators[axis]; +// Arrays.sort(tris, l, r, comparator); +// int splitIndex = -1; +// +// float leftPlane, rightPlane = Float.POSITIVE_INFINITY; +// leftPlane = tris[l].getExtreme(axis, false); +// for (int i = l; i <= r; i++){ +// BIHTriangle tri = tris[i]; +// if (splitIndex == -1){ +// float v = tri.getCenter().get(axis); +// if (v > split){ +// if (i == 0){ +// // no left plane +// splitIndex = -2; +// }else{ +// splitIndex = i; +// // first triangle assigned to right +// rightPlane = tri.getExtreme(axis, true); +// } +// }else{ +// // triangle assigned to left +// float ex = tri.getExtreme(axis, false); +// if (ex > leftPlane) +// leftPlane = ex; +// } +// }else{ +// float ex = tri.getExtreme(axis, true); +// if (ex < rightPlane) +// rightPlane = ex; +// } +// } +// +// if (splitIndex < 0){ +// splitIndex = (r - l) / 2; +// +// leftPlane = Float.NEGATIVE_INFINITY; +// rightPlane = Float.POSITIVE_INFINITY; +// +// for (int i = l; i < splitIndex; i++){ +// float ex = tris[i].getExtreme(axis, false); +// if (ex > leftPlane){ +// leftPlane = ex; +// } +// } +// for (int i = splitIndex; i <= r; i++){ +// float ex = tris[i].getExtreme(axis, true); +// if (ex < rightPlane){ +// rightPlane = ex; +// } +// } +// } +// +// BIHNode node = new BIHNode(axis); +// node.leftPlane = leftPlane; +// node.rightPlane = rightPlane; +// +// node.leftIndex = l; +// node.rightIndex = r; +// +// BoundingBox leftBbox = new BoundingBox(currentBox); +// setMinMax(leftBbox, false, axis, split); +// node.left = createNode2(l, splitIndex-1, leftBbox, depth+1); +// +// BoundingBox rightBbox = new BoundingBox(currentBox); +// setMinMax(rightBbox, true, axis, split); +// node.right = createNode2(splitIndex, r, rightBbox, depth+1); +// +// return node; +// } + private BIHNode createNode(int l, int r, BoundingBox nodeBbox, int depth) { + if ((r - l) < maxTrisPerNode || depth > MAX_TREE_DEPTH) { + return new BIHNode(l, r); + } + + BoundingBox currentBox = createBox(l, r); + + Vector3f exteriorExt = nodeBbox.getExtent(null); + Vector3f interiorExt = currentBox.getExtent(null); + exteriorExt.subtractLocal(interiorExt); + + int axis = 0; + if (exteriorExt.x > exteriorExt.y) { + if (exteriorExt.x > exteriorExt.z) { + axis = 0; + } else { + axis = 2; + } + } else { + if (exteriorExt.y > exteriorExt.z) { + axis = 1; + } else { + axis = 2; + } + } + if (exteriorExt.equals(Vector3f.ZERO)) { + axis = 0; + } + +// Arrays.sort(tris, l, r, comparators[axis]); + float split = currentBox.getCenter().get(axis); + int pivot = sortTriangles(l, r, split, axis); + if (pivot == l || pivot == r) { + pivot = (r + l) / 2; + } + + //If one of the partitions is empty, continue with recursion: same level but different bbox + if (pivot < l) { + //Only right + BoundingBox rbbox = new BoundingBox(currentBox); + setMinMax(rbbox, true, axis, split); + return createNode(l, r, rbbox, depth + 1); + } else if (pivot > r) { + //Only left + BoundingBox lbbox = new BoundingBox(currentBox); + setMinMax(lbbox, false, axis, split); + return createNode(l, r, lbbox, depth + 1); + } else { + //Build the node + BIHNode node = new BIHNode(axis); + + //Left child + BoundingBox lbbox = new BoundingBox(currentBox); + setMinMax(lbbox, false, axis, split); + + //The left node right border is the plane most right + node.setLeftPlane(getMinMax(createBox(l, max(l, pivot - 1)), false, axis)); + node.setLeftChild(createNode(l, max(l, pivot - 1), lbbox, depth + 1)); //Recursive call + + //Right Child + BoundingBox rbbox = new BoundingBox(currentBox); + setMinMax(rbbox, true, axis, split); + //The right node left border is the plane most left + node.setRightPlane(getMinMax(createBox(pivot, r), true, axis)); + node.setRightChild(createNode(pivot, r, rbbox, depth + 1)); //Recursive call + + return node; + } + } + + public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3) { + int pointIndex = index * 9; + + v1.x = pointData[pointIndex++]; + v1.y = pointData[pointIndex++]; + v1.z = pointData[pointIndex++]; + + v2.x = pointData[pointIndex++]; + v2.y = pointData[pointIndex++]; + v2.z = pointData[pointIndex++]; + + v3.x = pointData[pointIndex++]; + v3.y = pointData[pointIndex++]; + v3.z = pointData[pointIndex++]; + } + + public void swapTriangles(int index1, int index2) { + int p1 = index1 * 9; + int p2 = index2 * 9; + + // store p1 in tmp + System.arraycopy(pointData, p1, bihSwapTmp, 0, 9); + + // copy p2 to p1 + System.arraycopy(pointData, p2, pointData, p1, 9); + + // copy tmp to p2 + System.arraycopy(bihSwapTmp, 0, pointData, p2, 9); + + // swap indices + int tmp2 = triIndices[index1]; + triIndices[index1] = triIndices[index2]; + triIndices[index2] = tmp2; + } + + private int collideWithRay(Ray r, + Matrix4f worldMatrix, + BoundingVolume worldBound, + CollisionResults results) { + + boundResults.clear(); + worldBound.collideWith(r, boundResults); + if (boundResults.size() > 0) { + float tMin = boundResults.getClosestCollision().getDistance(); + float tMax = boundResults.getFarthestCollision().getDistance(); + + if (tMax <= 0) { + tMax = Float.POSITIVE_INFINITY; + } else if (tMin == tMax) { + tMin = 0; + } + + if (tMin <= 0) { + tMin = 0; + } + + if (r.getLimit() < Float.POSITIVE_INFINITY) { + tMax = Math.min(tMax, r.getLimit()); + if (tMin > tMax){ + return 0; + } + } + +// return root.intersectBrute(r, worldMatrix, this, tMin, tMax, results); + return root.intersectWhere(r, worldMatrix, this, tMin, tMax, results); + } + return 0; + } + + private int collideWithBoundingVolume(BoundingVolume bv, + Matrix4f worldMatrix, + CollisionResults results) { + BoundingBox bbox; + if (bv instanceof BoundingSphere) { + BoundingSphere sphere = (BoundingSphere) bv; + bbox = new BoundingBox(bv.getCenter().clone(), sphere.getRadius(), + sphere.getRadius(), + sphere.getRadius()); + } else if (bv instanceof BoundingBox) { + bbox = new BoundingBox((BoundingBox) bv); + } else { + throw new UnsupportedCollisionException(); + } + + bbox.transform(worldMatrix.invert(), bbox); + return root.intersectWhere(bv, bbox, worldMatrix, this, results); + } + + public int collideWith(Collidable other, + Matrix4f worldMatrix, + BoundingVolume worldBound, + CollisionResults results) { + + if (other instanceof Ray) { + Ray ray = (Ray) other; + return collideWithRay(ray, worldMatrix, worldBound, results); + } else if (other instanceof BoundingVolume) { + BoundingVolume bv = (BoundingVolume) other; + return collideWithBoundingVolume(bv, worldMatrix, results); + } else { + throw new UnsupportedCollisionException(); + } + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(mesh, "mesh", null); + oc.write(root, "root", null); + oc.write(maxTrisPerNode, "tris_per_node", 0); + oc.write(pointData, "points", null); + oc.write(triIndices, "indices", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + mesh = (Mesh) ic.readSavable("mesh", null); + root = (BIHNode) ic.readSavable("root", null); + maxTrisPerNode = ic.readInt("tris_per_node", 0); + pointData = ic.readFloatArray("points", null); + triIndices = ic.readIntArray("indices", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/collision/bih/BIHTriangle.java b/jme3-core/src/main/java/com/jme3/collision/bih/BIHTriangle.java new file mode 100644 index 000000000..bff45b5d8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/bih/BIHTriangle.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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/jme3-core/src/main/java/com/jme3/collision/bih/TriangleAxisComparator.java b/jme3-core/src/main/java/com/jme3/collision/bih/TriangleAxisComparator.java new file mode 100644 index 000000000..836a972e9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/bih/TriangleAxisComparator.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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/jme3-core/src/main/java/com/jme3/effect/Particle.java b/jme3-core/src/main/java/com/jme3/effect/Particle.java new file mode 100644 index 000000000..522306ad9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/Particle.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; + +/** + * Represents a single particle in a {@link ParticleEmitter}. + * + * @author Kirill Vainer + */ +public class Particle { + + /** + * Particle velocity. + */ + public final Vector3f velocity = new Vector3f(); + + /** + * Current particle position + */ + public final Vector3f position = new Vector3f(); + + /** + * Particle color + */ + public final ColorRGBA color = new ColorRGBA(0,0,0,0); + + /** + * Particle size or radius. + */ + public float size; + + /** + * Particle remaining life, in seconds. + */ + public float life; + + /** + * The initial particle life + */ + public float startlife; + + /** + * Particle rotation angle (in radians). + */ + public float angle; + + /** + * Particle rotation angle speed (in radians). + */ + public float rotateSpeed; + + /** + * Particle image index. + */ + public int imageIndex = 0; + + /** + * Distance to camera. Only used for sorted particles. + */ + //public float distToCam; +} diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleComparator.java b/jme3-core/src/main/java/com/jme3/effect/ParticleComparator.java new file mode 100644 index 000000000..78ee034d7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleComparator.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect; + +import com.jme3.renderer.Camera; +import java.util.Comparator; + +@Deprecated +class ParticleComparator implements Comparator { + + private Camera cam; + + public void setCamera(Camera cam){ + this.cam = cam; + } + + public int compare(Particle p1, Particle p2) { + return 0; // unused + /* + if (p1.life <= 0 || p2.life <= 0) + return 0; + +// if (p1.life <= 0) +// return 1; +// else if (p2.life <= 0) +// return -1; + + float d1 = p1.distToCam, d2 = p2.distToCam; + + if (d1 == -1){ + d1 = cam.distanceToNearPlane(p1.position); + p1.distToCam = d1; + } + if (d2 == -1){ + d2 = cam.distanceToNearPlane(p2.position); + p2.distToCam = d2; + } + + if (d1 < d2) + return 1; + else if (d1 > d2) + return -1; + else + return 0; + */ + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java new file mode 100644 index 000000000..f68f574aa --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java @@ -0,0 +1,1208 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect; + +import com.jme3.bounding.BoundingBox; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.effect.influencers.DefaultParticleInfluencer; +import com.jme3.effect.influencers.ParticleInfluencer; +import com.jme3.effect.shapes.EmitterPointShape; +import com.jme3.effect.shapes.EmitterShape; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.Control; +import com.jme3.util.TempVars; +import java.io.IOException; + +/** + * ParticleEmitter is a special kind of geometry which simulates + * a particle system. + *

    + * Particle emitters can be used to simulate various kinds of phenomena, + * such as fire, smoke, explosions and much more. + *

    + * Particle emitters have many properties which are used to control the + * simulation. The interpretation of these properties depends on the + * {@link ParticleInfluencer} that has been assigned to the emitter via + * {@link ParticleEmitter#setParticleInfluencer(com.jme3.effect.influencers.ParticleInfluencer) }. + * By default the implementation {@link DefaultParticleInfluencer} is used. + * + * @author Kirill Vainer + */ +public class ParticleEmitter extends Geometry { + + private boolean enabled = true; + private static final EmitterShape DEFAULT_SHAPE = new EmitterPointShape(Vector3f.ZERO); + private static final ParticleInfluencer DEFAULT_INFLUENCER = new DefaultParticleInfluencer(); + private ParticleEmitterControl control; + private EmitterShape shape = DEFAULT_SHAPE; + private ParticleMesh particleMesh; + private ParticleInfluencer particleInfluencer = DEFAULT_INFLUENCER; + private ParticleMesh.Type meshType; + private Particle[] particles; + private int firstUnUsed; + private int lastUsed; +// private int next = 0; +// private ArrayList unusedIndices = new ArrayList(); + private boolean randomAngle; + private boolean selectRandomImage; + private boolean facingVelocity; + private float particlesPerSec = 20; + private float timeDifference = 0; + private float lowLife = 3f; + private float highLife = 7f; + private Vector3f gravity = new Vector3f(0.0f, 0.1f, 0.0f); + private float rotateSpeed; + private Vector3f faceNormal = new Vector3f(Vector3f.NAN); + private int imagesX = 1; + private int imagesY = 1; + + private ColorRGBA startColor = new ColorRGBA(0.4f, 0.4f, 0.4f, 0.5f); + private ColorRGBA endColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 0.0f); + private float startSize = 0.2f; + private float endSize = 2f; + private boolean worldSpace = true; + //variable that helps with computations + private transient Vector3f temp = new Vector3f(); + + public static class ParticleEmitterControl implements Control { + + ParticleEmitter parentEmitter; + + public ParticleEmitterControl() { + } + + public ParticleEmitterControl(ParticleEmitter parentEmitter) { + this.parentEmitter = parentEmitter; + } + + public Control cloneForSpatial(Spatial spatial) { + return this; // WARNING: Sets wrong control on spatial. Will be + // fixed automatically by ParticleEmitter.clone() method. + } + + public void setSpatial(Spatial spatial) { + } + + public void setEnabled(boolean enabled) { + parentEmitter.setEnabled(enabled); + } + + public boolean isEnabled() { + return parentEmitter.isEnabled(); + } + + public void update(float tpf) { + parentEmitter.updateFromControl(tpf); + } + + public void render(RenderManager rm, ViewPort vp) { + parentEmitter.renderFromControl(rm, vp); + } + + public void write(JmeExporter ex) throws IOException { + } + + public void read(JmeImporter im) throws IOException { + } + } + + @Override + public ParticleEmitter clone() { + return clone(true); + } + + @Override + public ParticleEmitter clone(boolean cloneMaterial) { + ParticleEmitter clone = (ParticleEmitter) super.clone(cloneMaterial); + clone.shape = shape.deepClone(); + + // Reinitialize particle list + clone.setNumParticles(particles.length); + + clone.faceNormal = faceNormal.clone(); + clone.startColor = startColor.clone(); + clone.endColor = endColor.clone(); + clone.particleInfluencer = particleInfluencer.clone(); + + // remove wrong control + clone.controls.remove(control); + + // put correct control + clone.controls.add(new ParticleEmitterControl(clone)); + + // Reinitialize particle mesh + switch (meshType) { + case Point: + clone.particleMesh = new ParticlePointMesh(); + clone.setMesh(clone.particleMesh); + break; + case Triangle: + clone.particleMesh = new ParticleTriMesh(); + clone.setMesh(clone.particleMesh); + break; + default: + throw new IllegalStateException("Unrecognized particle type: " + meshType); + } + clone.particleMesh.initParticleData(clone, clone.particles.length); + clone.particleMesh.setImagesXY(clone.imagesX, clone.imagesY); + + return clone; + } + + public ParticleEmitter(String name, Type type, int numParticles) { + super(name); + setBatchHint(BatchHint.Never); + // ignore world transform, unless user sets inLocalSpace + this.setIgnoreTransform(true); + + // particles neither receive nor cast shadows + this.setShadowMode(ShadowMode.Off); + + // particles are usually transparent + this.setQueueBucket(Bucket.Transparent); + + meshType = type; + + // Must create clone of shape/influencer so that a reference to a static is + // not maintained + shape = shape.deepClone(); + particleInfluencer = particleInfluencer.clone(); + + control = new ParticleEmitterControl(this); + controls.add(control); + + switch (meshType) { + case Point: + particleMesh = new ParticlePointMesh(); + this.setMesh(particleMesh); + break; + case Triangle: + particleMesh = new ParticleTriMesh(); + this.setMesh(particleMesh); + break; + default: + throw new IllegalStateException("Unrecognized particle type: " + meshType); + } + this.setNumParticles(numParticles); +// particleMesh.initParticleData(this, particles.length); + } + + /** + * For serialization only. Do not use. + */ + public ParticleEmitter() { + super(); + setBatchHint(BatchHint.Never); + } + + public void setShape(EmitterShape shape) { + this.shape = shape; + } + + public EmitterShape getShape() { + return shape; + } + + /** + * Set the {@link ParticleInfluencer} to influence this particle emitter. + * + * @param particleInfluencer the {@link ParticleInfluencer} to influence + * this particle emitter. + * + * @see ParticleInfluencer + */ + public void setParticleInfluencer(ParticleInfluencer particleInfluencer) { + this.particleInfluencer = particleInfluencer; + } + + /** + * Returns the {@link ParticleInfluencer} that influences this + * particle emitter. + * + * @return the {@link ParticleInfluencer} that influences this + * particle emitter. + * + * @see ParticleInfluencer + */ + public ParticleInfluencer getParticleInfluencer() { + return particleInfluencer; + } + + /** + * Returns the mesh type used by the particle emitter. + * + * + * @return the mesh type used by the particle emitter. + * + * @see #setMeshType(com.jme3.effect.ParticleMesh.Type) + * @see ParticleEmitter#ParticleEmitter(java.lang.String, com.jme3.effect.ParticleMesh.Type, int) + */ + public ParticleMesh.Type getMeshType() { + return meshType; + } + + /** + * Sets the type of mesh used by the particle emitter. + * @param meshType The mesh type to use + */ + public void setMeshType(ParticleMesh.Type meshType) { + this.meshType = meshType; + switch (meshType) { + case Point: + particleMesh = new ParticlePointMesh(); + this.setMesh(particleMesh); + break; + case Triangle: + particleMesh = new ParticleTriMesh(); + this.setMesh(particleMesh); + break; + default: + throw new IllegalStateException("Unrecognized particle type: " + meshType); + } + this.setNumParticles(particles.length); + } + + /** + * Returns true if particles should spawn in world space. + * + * @return true if particles should spawn in world space. + * + * @see ParticleEmitter#setInWorldSpace(boolean) + */ + public boolean isInWorldSpace() { + return worldSpace; + } + + /** + * Set to true if particles should spawn in world space. + * + *

    If set to true and the particle emitter is moved in the scene, + * then particles that have already spawned won't be effected by this + * motion. If set to false, the particles will emit in local space + * and when the emitter is moved, so are all the particles that + * were emitted previously. + * + * @param worldSpace true if particles should spawn in world space. + */ + public void setInWorldSpace(boolean worldSpace) { + this.setIgnoreTransform(worldSpace); + this.worldSpace = worldSpace; + } + + /** + * Returns the number of visible particles (spawned but not dead). + * + * @return the number of visible particles + */ + public int getNumVisibleParticles() { +// return unusedIndices.size() + next; + return lastUsed + 1; + } + + /** + * Set the maximum amount of particles that + * can exist at the same time with this emitter. + * Calling this method many times is not recommended. + * + * @param numParticles the maximum amount of particles that + * can exist at the same time with this emitter. + */ + public final void setNumParticles(int numParticles) { + particles = new Particle[numParticles]; + for (int i = 0; i < numParticles; i++) { + particles[i] = new Particle(); + } + //We have to reinit the mesh's buffers with the new size + particleMesh.initParticleData(this, particles.length); + particleMesh.setImagesXY(this.imagesX, this.imagesY); + firstUnUsed = 0; + lastUsed = -1; + } + + public int getMaxNumParticles() { + return particles.length; + } + + /** + * Returns a list of all particles (shouldn't be used in most cases). + * + *

    + * 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. + * + * @return a list of all particles. + */ + public Particle[] getParticles() { + return particles; + } + + /** + * Get the normal which particles are facing. + * + * @return the normal which particles are facing. + * + * @see ParticleEmitter#setFaceNormal(com.jme3.math.Vector3f) + */ + public Vector3f getFaceNormal() { + if (Vector3f.isValidVector(faceNormal)) { + return faceNormal; + } else { + return null; + } + } + + /** + * Sets the normal which particles are facing. + * + *

    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; + } + } + + /** + * Returns the rotation speed in radians/sec for particles. + * + * @return the rotation speed in radians/sec for particles. + * + * @see ParticleEmitter#setRotateSpeed(float) + */ + public float getRotateSpeed() { + return rotateSpeed; + } + + /** + * Set the rotation speed in radians/sec for particles + * spawned after the invocation of this method. + * + * @param rotateSpeed the rotation speed in radians/sec for particles + * spawned after the invocation of this method. + */ + public void setRotateSpeed(float rotateSpeed) { + this.rotateSpeed = rotateSpeed; + } + + /** + * Returns true if every particle spawned + * should have a random facing angle. + * + * @return true if every particle spawned + * should have a random facing angle. + * + * @see ParticleEmitter#setRandomAngle(boolean) + */ + public boolean isRandomAngle() { + return randomAngle; + } + + /** + * Set to true if every particle spawned + * should have a random facing angle. + * + * @param randomAngle if every particle spawned + * should have a random facing angle. + */ + public void setRandomAngle(boolean randomAngle) { + this.randomAngle = randomAngle; + } + + /** + * Returns true if every particle spawned should get a random + * image. + * + * @return True if every particle spawned should get a random + * image. + * + * @see ParticleEmitter#setSelectRandomImage(boolean) + */ + public boolean isSelectRandomImage() { + return selectRandomImage; + } + + /** + * Set to true if every particle spawned + * should get a random image from a pool of images constructed from + * the texture, with X by Y possible images. + * + *

    By default, X and Y are equal + * to 1, thus allowing only 1 possible image to be selected, but if the + * particle is configured with multiple images by using {@link ParticleEmitter#setImagesX(int) } + * and {#link ParticleEmitter#setImagesY(int) } methods, then multiple images + * can be selected. Setting to false will cause each particle to have an animation + * of images displayed, starting at image 1, and going until image X*Y when + * the particle reaches its end of life. + * + * @param selectRandomImage True if every particle spawned should get a random + * image. + */ + public void setSelectRandomImage(boolean selectRandomImage) { + this.selectRandomImage = selectRandomImage; + } + + /** + * Check if particles spawned should face their velocity. + * + * @return True if particles spawned should face their velocity. + * + * @see ParticleEmitter#setFacingVelocity(boolean) + */ + public boolean isFacingVelocity() { + return facingVelocity; + } + + /** + * Set to true if particles spawned should face + * their velocity (or direction to which they are moving towards). + * + *

    This is typically used for e.g spark effects. + * + * @param followVelocity True if particles spawned should face their velocity. + * + */ + public void setFacingVelocity(boolean followVelocity) { + this.facingVelocity = followVelocity; + } + + /** + * Get the end color of the particles spawned. + * + * @return the end color of the particles spawned. + * + * @see ParticleEmitter#setEndColor(com.jme3.math.ColorRGBA) + */ + public ColorRGBA getEndColor() { + return endColor; + } + + /** + * Set the end color of the particles spawned. + * + *

    The + * particle color at any time is determined by blending the start color + * and end color based on the particle's current time of life relative + * to its end of life. + * + * @param endColor the end color of the particles spawned. + */ + public void setEndColor(ColorRGBA endColor) { + this.endColor.set(endColor); + } + + /** + * Get the end size of the particles spawned. + * + * @return the end size of the particles spawned. + * + * @see ParticleEmitter#setEndSize(float) + */ + public float getEndSize() { + return endSize; + } + + /** + * Set the end size of the particles spawned. + * + *

    The + * particle size at any time is determined by blending the start size + * and end size based on the particle's current time of life relative + * to its end of life. + * + * @param endSize the end size of the particles spawned. + */ + public void setEndSize(float endSize) { + this.endSize = endSize; + } + + /** + * Get the gravity vector. + * + * @return the gravity vector. + * + * @see ParticleEmitter#setGravity(com.jme3.math.Vector3f) + */ + public Vector3f getGravity() { + return gravity; + } + + /** + * This method sets the gravity vector. + * + * @param gravity the gravity vector + */ + public void setGravity(Vector3f gravity) { + this.gravity.set(gravity); + } + + /** + * Sets the gravity vector. + * + * @param x the x component of the gravity vector + * @param y the y component of the gravity vector + * @param z the z component of the gravity vector + */ + public void setGravity(float x, float y, float z) { + this.gravity.x = x; + this.gravity.y = y; + this.gravity.z = z; + } + + /** + * Get the high value of life. + * + * @return the high value of life. + * + * @see ParticleEmitter#setHighLife(float) + */ + public float getHighLife() { + return highLife; + } + + /** + * Set the high value of life. + * + *

    The particle's lifetime/expiration + * is determined by randomly selecting a time between low life and high life. + * + * @param highLife the high value of life. + */ + public void setHighLife(float highLife) { + this.highLife = highLife; + } + + /** + * Get the number of images along the X axis (width). + * + * @return the number of images along the X axis (width). + * + * @see ParticleEmitter#setImagesX(int) + */ + public int getImagesX() { + return imagesX; + } + + /** + * Set the number of images along the X axis (width). + * + *

    To determine + * how multiple particle images are selected and used, see the + * {@link ParticleEmitter#setSelectRandomImage(boolean) } method. + * + * @param imagesX the number of images along the X axis (width). + */ + public void setImagesX(int imagesX) { + this.imagesX = imagesX; + particleMesh.setImagesXY(this.imagesX, this.imagesY); + } + + /** + * Get the number of images along the Y axis (height). + * + * @return the number of images along the Y axis (height). + * + * @see ParticleEmitter#setImagesY(int) + */ + public int getImagesY() { + return imagesY; + } + + /** + * Set the number of images along the Y axis (height). + * + *

    To determine how multiple particle images are selected and used, see the + * {@link ParticleEmitter#setSelectRandomImage(boolean) } method. + * + * @param imagesY the number of images along the Y axis (height). + */ + public void setImagesY(int imagesY) { + this.imagesY = imagesY; + particleMesh.setImagesXY(this.imagesX, this.imagesY); + } + + /** + * Get the low value of life. + * + * @return the low value of life. + * + * @see ParticleEmitter#setLowLife(float) + */ + public float getLowLife() { + return lowLife; + } + + /** + * Set the low value of life. + * + *

    The particle's lifetime/expiration + * is determined by randomly selecting a time between low life and high life. + * + * @param lowLife the low value of life. + */ + public void setLowLife(float lowLife) { + this.lowLife = lowLife; + } + + /** + * Get the number of particles to spawn per + * second. + * + * @return the number of particles to spawn per + * second. + * + * @see ParticleEmitter#setParticlesPerSec(float) + */ + public float getParticlesPerSec() { + return particlesPerSec; + } + + /** + * Set the number of particles to spawn per + * second. + * + * @param particlesPerSec the number of particles to spawn per + * second. + */ + public void setParticlesPerSec(float particlesPerSec) { + this.particlesPerSec = particlesPerSec; + timeDifference = 0; + } + + /** + * Get the start color of the particles spawned. + * + * @return the start color of the particles spawned. + * + * @see ParticleEmitter#setStartColor(com.jme3.math.ColorRGBA) + */ + public ColorRGBA getStartColor() { + return startColor; + } + + /** + * Set the start color of the particles spawned. + * + *

    The particle color at any time is determined by blending the start color + * and end color based on the particle's current time of life relative + * to its end of life. + * + * @param startColor the start color of the particles spawned + */ + public void setStartColor(ColorRGBA startColor) { + this.startColor.set(startColor); + } + + /** + * Get the start color of the particles spawned. + * + * @return the start color of the particles spawned. + * + * @see ParticleEmitter#setStartSize(float) + */ + public float getStartSize() { + return startSize; + } + + /** + * Set the start size of the particles spawned. + * + *

    The particle size at any time is determined by blending the start size + * and end size based on the particle's current time of life relative + * to its end of life. + * + * @param startSize the start size of the particles spawned. + */ + public void setStartSize(float startSize) { + this.startSize = startSize; + } + + /** + * @deprecated Use ParticleEmitter.getParticleInfluencer().getInitialVelocity() instead. + */ + @Deprecated + public Vector3f getInitialVelocity() { + return particleInfluencer.getInitialVelocity(); + } + + /** + * @param initialVelocity Set the initial velocity a particle is spawned with, + * the initial velocity given in the parameter will be varied according + * to the velocity variation set in {@link ParticleEmitter#setVelocityVariation(float) }. + * A particle will move toward its velocity unless it is effected by the + * gravity. + * + * @deprecated + * This method is deprecated. + * Use ParticleEmitter.getParticleInfluencer().setInitialVelocity(initialVelocity); instead. + * + * @see ParticleEmitter#setVelocityVariation(float) + * @see ParticleEmitter#setGravity(float) + */ + @Deprecated + public void setInitialVelocity(Vector3f initialVelocity) { + this.particleInfluencer.setInitialVelocity(initialVelocity); + } + + /** + * @deprecated + * This method is deprecated. + * Use ParticleEmitter.getParticleInfluencer().getVelocityVariation(); instead. + * @return the initial velocity variation factor + */ + @Deprecated + public float getVelocityVariation() { + return particleInfluencer.getVelocityVariation(); + } + + /** + * @param variation Set the variation by which the initial velocity + * of the particle is determined. 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. + * + * @deprecated + * This method is deprecated. + * Use ParticleEmitter.getParticleInfluencer().setVelocityVariation(variation); instead. + */ + @Deprecated + public void setVelocityVariation(float variation) { + this.particleInfluencer.setVelocityVariation(variation); + } + + private Particle emitParticle(Vector3f min, Vector3f max) { + int idx = lastUsed + 1; + if (idx >= particles.length) { + return null; + } + + Particle p = particles[idx]; + if (selectRandomImage) { + p.imageIndex = FastMath.nextRandomInt(0, imagesY - 1) * imagesX + FastMath.nextRandomInt(0, imagesX - 1); + } + + p.startlife = lowLife + FastMath.nextRandomFloat() * (highLife - lowLife); + p.life = p.startlife; + p.color.set(startColor); + p.size = startSize; + //shape.getRandomPoint(p.position); + particleInfluencer.influenceParticle(p, shape); + if (worldSpace) { + worldTransform.transformVector(p.position, p.position); + worldTransform.getRotation().mult(p.velocity, p.velocity); + // TODO: Make scale relevant somehow?? + } + if (randomAngle) { + p.angle = FastMath.nextRandomFloat() * FastMath.TWO_PI; + } + if (rotateSpeed != 0) { + p.rotateSpeed = rotateSpeed * (0.2f + (FastMath.nextRandomFloat() * 2f - 1f) * .8f); + } + + temp.set(p.position).addLocal(p.size, p.size, p.size); + max.maxLocal(temp); + temp.set(p.position).subtractLocal(p.size, p.size, p.size); + min.minLocal(temp); + + ++lastUsed; + firstUnUsed = idx + 1; + return p; + } + + /** + * Instantly emits all the particles possible to be emitted. Any particles + * which are currently inactive will be spawned immediately. + */ + public void emitAllParticles() { + // Force world transform to update + this.getWorldTransform(); + + TempVars vars = TempVars.get(); + + BoundingBox bbox = (BoundingBox) this.getMesh().getBound(); + + Vector3f min = vars.vect1; + Vector3f max = vars.vect2; + + bbox.getMin(min); + bbox.getMax(max); + + if (!Vector3f.isValidVector(min)) { + min.set(Vector3f.POSITIVE_INFINITY); + } + if (!Vector3f.isValidVector(max)) { + max.set(Vector3f.NEGATIVE_INFINITY); + } + + while (emitParticle(min, max) != null); + + bbox.setMinMax(min, max); + this.setBoundRefresh(); + + vars.release(); + } + + /** + * Instantly kills all active particles, after this method is called, all + * particles will be dead and no longer visible. + */ + public void killAllParticles() { + for (int i = 0; i < particles.length; ++i) { + if (particles[i].life > 0) { + this.freeParticle(i); + } + } + } + + /** + * Kills the particle at the given index. + * + * @param index The index of the particle to kill + * @see #getParticles() + */ + public void killParticle(int index){ + freeParticle(index); + } + + private void freeParticle(int idx) { + Particle p = particles[idx]; + p.life = 0; + p.size = 0f; + p.color.set(0, 0, 0, 0); + p.imageIndex = 0; + p.angle = 0; + p.rotateSpeed = 0; + + if (idx == lastUsed) { + while (lastUsed >= 0 && particles[lastUsed].life == 0) { + lastUsed--; + } + } + if (idx < firstUnUsed) { + firstUnUsed = idx; + } + } + + private void swap(int idx1, int idx2) { + Particle p1 = particles[idx1]; + particles[idx1] = particles[idx2]; + particles[idx2] = p1; + } + + private void updateParticle(Particle p, float tpf, Vector3f min, Vector3f max){ + // applying gravity + p.velocity.x -= gravity.x * tpf; + p.velocity.y -= gravity.y * tpf; + p.velocity.z -= gravity.z * tpf; + temp.set(p.velocity).multLocal(tpf); + p.position.addLocal(temp); + + // affecting color, size and angle + float b = (p.startlife - p.life) / p.startlife; + p.color.interpolate(startColor, endColor, b); + p.size = FastMath.interpolateLinear(b, startSize, endSize); + p.angle += p.rotateSpeed * tpf; + + // Computing bounding volume + temp.set(p.position).addLocal(p.size, p.size, p.size); + max.maxLocal(temp); + temp.set(p.position).subtractLocal(p.size, p.size, p.size); + min.minLocal(temp); + + if (!selectRandomImage) { + p.imageIndex = (int) (b * imagesX * imagesY); + } + } + + private void updateParticleState(float tpf) { + // Force world transform to update + this.getWorldTransform(); + + TempVars vars = TempVars.get(); + + Vector3f min = vars.vect1.set(Vector3f.POSITIVE_INFINITY); + Vector3f max = vars.vect2.set(Vector3f.NEGATIVE_INFINITY); + + for (int i = 0; i < particles.length; ++i) { + Particle p = particles[i]; + if (p.life == 0) { // particle is dead +// assert i <= firstUnUsed; + continue; + } + + p.life -= tpf; + if (p.life <= 0) { + this.freeParticle(i); + continue; + } + + updateParticle(p, tpf, min, max); + + if (firstUnUsed < i) { + this.swap(firstUnUsed, i); + if (i == lastUsed) { + lastUsed = firstUnUsed; + } + firstUnUsed++; + } + } + + // Spawns particles within the tpf timeslot with proper age + float interval = 1f / particlesPerSec; + tpf += timeDifference; + while (tpf > interval){ + tpf -= interval; + Particle p = emitParticle(min, max); + if (p != null){ + p.life -= tpf; + if (p.life <= 0){ + freeParticle(lastUsed); + }else{ + updateParticle(p, tpf, min, max); + } + } + } + timeDifference = tpf; + + BoundingBox bbox = (BoundingBox) this.getMesh().getBound(); + bbox.setMinMax(min, max); + this.setBoundRefresh(); + + vars.release(); + } + + /** + * Set to enable or disable the particle emitter + * + *

    When a particle is + * disabled, it will be "frozen in time" and not update. + * + * @param enabled True to enable the particle emitter + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * Check if a particle emitter is enabled for update. + * + * @return True if a particle emitter is enabled for update. + * + * @see ParticleEmitter#setEnabled(boolean) + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Callback from Control.update(), do not use. + * @param tpf + */ + public void updateFromControl(float tpf) { + if (enabled) { + this.updateParticleState(tpf); + } + } + + /** + * Callback from Control.render(), do not use. + * + * @param rm + * @param vp + */ + private void renderFromControl(RenderManager rm, ViewPort vp) { + Camera cam = vp.getCamera(); + + if (meshType == ParticleMesh.Type.Point) { + float C = cam.getProjectionMatrix().m00; + C *= cam.getWidth() * 0.5f; + + // send attenuation params + this.getMaterial().setFloat("Quadratic", C); + } + + Matrix3f inverseRotation = Matrix3f.IDENTITY; + TempVars vars = null; + if (!worldSpace) { + vars = TempVars.get(); + + inverseRotation = this.getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal(); + } + particleMesh.updateParticleData(particles, cam, inverseRotation); + if (!worldSpace) { + vars.release(); + } + } + + public void preload(RenderManager rm, ViewPort vp) { + this.updateParticleState(0); + particleMesh.updateParticleData(particles, vp.getCamera(), Matrix3f.IDENTITY); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(shape, "shape", DEFAULT_SHAPE); + oc.write(meshType, "meshType", ParticleMesh.Type.Triangle); + oc.write(enabled, "enabled", true); + oc.write(particles.length, "numParticles", 0); + oc.write(particlesPerSec, "particlesPerSec", 0); + oc.write(lowLife, "lowLife", 0); + oc.write(highLife, "highLife", 0); + oc.write(gravity, "gravity", null); + oc.write(imagesX, "imagesX", 1); + oc.write(imagesY, "imagesY", 1); + + oc.write(startColor, "startColor", null); + oc.write(endColor, "endColor", null); + oc.write(startSize, "startSize", 0); + oc.write(endSize, "endSize", 0); + oc.write(worldSpace, "worldSpace", false); + oc.write(facingVelocity, "facingVelocity", false); + oc.write(faceNormal, "faceNormal", new Vector3f(Vector3f.NAN)); + oc.write(selectRandomImage, "selectRandomImage", false); + oc.write(randomAngle, "randomAngle", false); + oc.write(rotateSpeed, "rotateSpeed", 0); + + oc.write(particleInfluencer, "influencer", DEFAULT_INFLUENCER); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + shape = (EmitterShape) ic.readSavable("shape", DEFAULT_SHAPE); + + if (shape == DEFAULT_SHAPE) { + // Prevent reference to static + shape = shape.deepClone(); + } + + meshType = ic.readEnum("meshType", ParticleMesh.Type.class, ParticleMesh.Type.Triangle); + int numParticles = ic.readInt("numParticles", 0); + + + enabled = ic.readBoolean("enabled", true); + particlesPerSec = ic.readFloat("particlesPerSec", 0); + lowLife = ic.readFloat("lowLife", 0); + highLife = ic.readFloat("highLife", 0); + gravity = (Vector3f) ic.readSavable("gravity", null); + imagesX = ic.readInt("imagesX", 1); + imagesY = ic.readInt("imagesY", 1); + + startColor = (ColorRGBA) ic.readSavable("startColor", null); + endColor = (ColorRGBA) ic.readSavable("endColor", null); + startSize = ic.readFloat("startSize", 0); + endSize = ic.readFloat("endSize", 0); + worldSpace = ic.readBoolean("worldSpace", false); + this.setIgnoreTransform(worldSpace); + facingVelocity = ic.readBoolean("facingVelocity", false); + faceNormal = (Vector3f)ic.readSavable("faceNormal", new Vector3f(Vector3f.NAN)); + selectRandomImage = ic.readBoolean("selectRandomImage", false); + randomAngle = ic.readBoolean("randomAngle", false); + rotateSpeed = ic.readFloat("rotateSpeed", 0); + + switch (meshType) { + case Point: + particleMesh = new ParticlePointMesh(); + this.setMesh(particleMesh); + break; + case Triangle: + particleMesh = new ParticleTriMesh(); + this.setMesh(particleMesh); + break; + default: + throw new IllegalStateException("Unrecognized particle type: " + meshType); + } + this.setNumParticles(numParticles); +// particleMesh.initParticleData(this, particles.length); +// particleMesh.setImagesXY(imagesX, imagesY); + + particleInfluencer = (ParticleInfluencer) ic.readSavable("influencer", DEFAULT_INFLUENCER); + if (particleInfluencer == DEFAULT_INFLUENCER) { + particleInfluencer = particleInfluencer.clone(); + } + + if (im.getFormatVersion() == 0) { + // compatibility before the control inside particle emitter + // was changed: + // find it in the controls and take it out, then add the proper one in + for (int i = 0; i < controls.size(); i++) { + Object obj = controls.get(i); + if (obj instanceof ParticleEmitter) { + controls.remove(i); + // now add the proper one in + controls.add(new ParticleEmitterControl(this)); + break; + } + } + + // compatability before gravity was not a vector but a float + if (gravity == null) { + gravity = new Vector3f(); + gravity.y = ic.readFloat("gravity", 0); + } + } else { + // since the parentEmitter is not loaded, it must be + // loaded separately + control = getControl(ParticleEmitterControl.class); + control.parentEmitter = this; + + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleMesh.java b/jme3-core/src/main/java/com/jme3/effect/ParticleMesh.java new file mode 100644 index 000000000..ed1b80d2a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleMesh.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect; + +import com.jme3.material.RenderState; +import com.jme3.math.Matrix3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Mesh; + +/** + * The ParticleMesh is the underlying visual implementation of a + * {@link ParticleEmitter particle emitter}. + * + * @author Kirill Vainer + */ +public abstract class ParticleMesh extends Mesh { + + /** + * Type of particle mesh + */ + public enum Type { + /** + * The particle mesh is composed of points. Each particle is a point. + * This can be used in conjuction with {@link RenderState#setPointSprite(boolean) point sprites} + * to render particles the usual way. + */ + Point, + + /** + * The particle mesh is composed of triangles. Each particle is + * two triangles making a single quad. + */ + Triangle; + } + + /** + * Initialize mesh data. + * + * @param emitter The emitter which will use this ParticleMesh. + * @param numParticles The maxmimum number of particles to simulate + */ + public abstract void initParticleData(ParticleEmitter emitter, int numParticles); + + /** + * Set the images on the X and Y coordinates + * @param imagesX Images on the X coordinate + * @param imagesY Images on the Y coordinate + */ + public abstract void setImagesXY(int imagesX, int imagesY); + + /** + * Update the particle visual data. Typically called every frame. + */ + public abstract void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation); + +} diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticlePointMesh.java b/jme3-core/src/main/java/com/jme3/effect/ParticlePointMesh.java new file mode 100644 index 000000000..3f56e483b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/ParticlePointMesh.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect; + +import com.jme3.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); + + //if the buffer is already set only update the data + VertexBuffer buf = getBuffer(VertexBuffer.Type.Position); + if (buf != null) { + buf.updateData(pb); + } else { + 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); + + buf = getBuffer(VertexBuffer.Type.Color); + if (buf != null) { + buf.updateData(cb); + } else { + 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); + + buf = getBuffer(VertexBuffer.Type.Size); + if (buf != null) { + buf.updateData(sb); + } else { + 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); + + buf = getBuffer(VertexBuffer.Type.TexCoord); + if (buf != null) { + buf.updateData(tb); + } else { + VertexBuffer tvb = new VertexBuffer(VertexBuffer.Type.TexCoord); + tvb.setupData(Usage.Stream, 4, Format.Float, tb); + setBuffer(tvb); + } + + updateCounts(); + } + + @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/jme3-core/src/main/java/com/jme3/effect/ParticleTriMesh.java b/jme3-core/src/main/java/com/jme3/effect/ParticleTriMesh.java new file mode 100644 index 000000000..8002197b0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleTriMesh.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect; + +import com.jme3.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.TempVars; +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); + // if the buffer is already set only update the data + VertexBuffer buf = getBuffer(VertexBuffer.Type.Position); + if (buf != null) { + buf.updateData(pb); + } else { + 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); + buf = getBuffer(VertexBuffer.Type.Color); + if (buf != null) { + buf.updateData(cb); + } else { + VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color); + cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb); + cvb.setNormalized(true); + setBuffer(cvb); + } + + // set texcoords + 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(); + + buf = getBuffer(VertexBuffer.Type.TexCoord); + if (buf != null) { + buf.updateData(tb); + } else { + VertexBuffer tvb = new VertexBuffer(VertexBuffer.Type.TexCoord); + 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(); + + buf = getBuffer(VertexBuffer.Type.Index); + if (buf != null) { + buf.updateData(ib); + } else { + VertexBuffer ivb = new VertexBuffer(VertexBuffer.Type.Index); + ivb.setupData(Usage.Static, 3, Format.UnsignedShort, ib); + setBuffer(ivb); + } + + updateCounts(); + } + + @Override + public void setImagesXY(int imagesX, int imagesY) { + this.imagesX = imagesX; + this.imagesY = imagesY; + if (imagesX != 1 || imagesY != 1){ + uniqueTexCoords = true; + getBuffer(VertexBuffer.Type.TexCoord).setUsage(Usage.Stream); + } + } + + @Override + public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) { +// System.arraycopy(particles, 0, particlesCopy, 0, particlesCopy.length); +// comparator.setCamera(cam); +// Arrays.sort(particlesCopy, comparator); +// SortUtil.qsort(particlesCopy, comparator); +// SortUtil.msort(particles, particlesCopy, comparator); +// particles = particlesCopy; + + VertexBuffer pvb = getBuffer(VertexBuffer.Type.Position); + FloatBuffer positions = (FloatBuffer) pvb.getData(); + + VertexBuffer cvb = getBuffer(VertexBuffer.Type.Color); + ByteBuffer colors = (ByteBuffer) cvb.getData(); + + VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord); + FloatBuffer texcoords = (FloatBuffer) tvb.getData(); + + Vector3f camUp = cam.getUp(); + Vector3f camLeft = cam.getLeft(); + Vector3f camDir = cam.getDirection(); + + inverseRotation.multLocal(camUp); + inverseRotation.multLocal(camLeft); + inverseRotation.multLocal(camDir); + + boolean facingVelocity = emitter.isFacingVelocity(); + + Vector3f up = new Vector3f(), + left = new Vector3f(); + + if (!facingVelocity){ + up.set(camUp); + left.set(camLeft); + } + + // update data in vertex buffers + positions.clear(); + colors.clear(); + texcoords.clear(); + Vector3f faceNormal = emitter.getFaceNormal(); + + for (int i = 0; i < particles.length; i++){ + Particle p = particles[i]; + boolean dead = p.life == 0; + if (dead){ + positions.put(0).put(0).put(0); + positions.put(0).put(0).put(0); + positions.put(0).put(0).put(0); + positions.put(0).put(0).put(0); + continue; + } + + if (facingVelocity){ + left.set(p.velocity).normalizeLocal(); + camDir.cross(left, up); + up.multLocal(p.size); + left.multLocal(p.size); + }else if (faceNormal != null){ + up.set(faceNormal).crossLocal(Vector3f.UNIT_X); + faceNormal.cross(up, left); + up.multLocal(p.size); + left.multLocal(p.size); + if (p.angle != 0) { + TempVars vars = TempVars.get(); + vars.vect1.set(faceNormal).normalizeLocal(); + vars.quat1.fromAngleNormalAxis(p.angle, vars.vect1); + vars.quat1.multLocal(left); + vars.quat1.multLocal(up); + vars.release(); + } + }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/jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java new file mode 100644 index 000000000..b52d8df80 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect.influencers; + +import com.jme3.effect.Particle; +import com.jme3.effect.shapes.EmitterShape; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * This emitter influences the particles so that they move all in the same direction. + * The direction may vary a little if the velocity variation is non zero. + * This influencer is default for the particle emitter. + * @author Marcin Roguski (Kaelthas) + */ +public class DefaultParticleInfluencer implements ParticleInfluencer { + + //Version #1 : changed startVelocity to initialvelocity for consistency with accessors + //and also changed it in serialization + public static final int SAVABLE_VERSION = 1; + /** Temporary variable used to help with calculations. */ + protected transient Vector3f temp = new Vector3f(); + /** The initial velocity of the particles. */ + protected Vector3f initialVelocity = new Vector3f(); + /** The velocity's variation of the particles. */ + protected float velocityVariation = 0.2f; + + @Override + public void influenceParticle(Particle particle, EmitterShape emitterShape) { + emitterShape.getRandomPoint(particle.position); + this.applyVelocityVariation(particle); + } + + /** + * This method applies the variation to the particle with already set velocity. + * @param particle + * the particle to be affected + */ + protected void applyVelocityVariation(Particle particle) { + particle.velocity.set(initialVelocity); + temp.set(FastMath.nextRandomFloat(), FastMath.nextRandomFloat(), FastMath.nextRandomFloat()); + temp.multLocal(2f); + temp.subtractLocal(1f, 1f, 1f); + temp.multLocal(initialVelocity.length()); + particle.velocity.interpolateLocal(temp, velocityVariation); + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(initialVelocity, "initialVelocity", Vector3f.ZERO); + oc.write(velocityVariation, "variation", 0.2f); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + // NOTE: In previous versions of jME3, initialVelocity was called startVelocity + if (ic.getSavableVersion(DefaultParticleInfluencer.class) == 0){ + initialVelocity = (Vector3f) ic.readSavable("startVelocity", Vector3f.ZERO.clone()); + }else{ + initialVelocity = (Vector3f) ic.readSavable("initialVelocity", Vector3f.ZERO.clone()); + } + velocityVariation = ic.readFloat("variation", 0.2f); + } + + @Override + public ParticleInfluencer clone() { + try { + DefaultParticleInfluencer clone = (DefaultParticleInfluencer) super.clone(); + clone.initialVelocity = initialVelocity.clone(); + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + @Override + public void setInitialVelocity(Vector3f initialVelocity) { + this.initialVelocity.set(initialVelocity); + } + + @Override + public Vector3f getInitialVelocity() { + return initialVelocity; + } + + @Override + public void setVelocityVariation(float variation) { + this.velocityVariation = variation; + } + + @Override + public float getVelocityVariation() { + return velocityVariation; + } +} diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java new file mode 100644 index 000000000..ccbc7e5e6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect.influencers; + +import com.jme3.effect.Particle; +import com.jme3.effect.shapes.EmitterShape; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * This influencer does not influence particle at all. + * It makes particles not to move. + * @author Marcin Roguski (Kaelthas) + */ +public class EmptyParticleInfluencer implements ParticleInfluencer { + + @Override + public void write(JmeExporter ex) throws IOException { + } + + @Override + public void read(JmeImporter im) throws IOException { + } + + @Override + public void influenceParticle(Particle particle, EmitterShape emitterShape) { + } + + @Override + public void setInitialVelocity(Vector3f initialVelocity) { + } + + @Override + public Vector3f getInitialVelocity() { + return null; + } + + @Override + public void setVelocityVariation(float variation) { + } + + @Override + public float getVelocityVariation() { + return 0; + } + + @Override + public ParticleInfluencer clone() { + try { + return (ParticleInfluencer) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java new file mode 100644 index 000000000..b2f81f9a8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect.influencers; + +import com.jme3.effect.Particle; +import com.jme3.effect.shapes.EmitterShape; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import java.io.IOException; + +/** + * This influencer calculates initial velocity with the use of the emitter's shape. + * @author Marcin Roguski (Kaelthas) + */ +public class NewtonianParticleInfluencer extends DefaultParticleInfluencer { + + /** Normal to emitter's shape factor. */ + protected float normalVelocity; + /** Emitter's surface tangent factor. */ + protected float surfaceTangentFactor; + /** Emitters tangent rotation factor. */ + protected float surfaceTangentRotation; + + /** + * Constructor. Sets velocity variation to 0.0f. + */ + public NewtonianParticleInfluencer() { + this.velocityVariation = 0.0f; + } + + @Override + public void influenceParticle(Particle particle, EmitterShape emitterShape) { + emitterShape.getRandomPointAndNormal(particle.position, particle.velocity); + // influencing the particle's velocity + if (surfaceTangentFactor == 0.0f) { + particle.velocity.multLocal(normalVelocity); + } else { + // calculating surface tangent (velocity contains the 'normal' value) + temp.set(particle.velocity.z * surfaceTangentFactor, particle.velocity.y * surfaceTangentFactor, -particle.velocity.x * surfaceTangentFactor); + if (surfaceTangentRotation != 0.0f) {// rotating the tangent + Matrix3f m = new Matrix3f(); + m.fromAngleNormalAxis(FastMath.PI * surfaceTangentRotation, particle.velocity); + temp = m.multLocal(temp); + } + // applying normal factor (this must be done first) + particle.velocity.multLocal(normalVelocity); + // adding tangent vector + particle.velocity.addLocal(temp); + } + if (velocityVariation != 0.0f) { + this.applyVelocityVariation(particle); + } + } + + /** + * This method returns the normal velocity factor. + * @return the normal velocity factor + */ + public float getNormalVelocity() { + return normalVelocity; + } + + /** + * This method sets the normal velocity factor. + * @param normalVelocity + * the normal velocity factor + */ + public void setNormalVelocity(float normalVelocity) { + this.normalVelocity = normalVelocity; + } + + /** + * This method sets the surface tangent factor. + * @param surfaceTangentFactor + * the surface tangent factor + */ + public void setSurfaceTangentFactor(float surfaceTangentFactor) { + this.surfaceTangentFactor = surfaceTangentFactor; + } + + /** + * This method returns the surface tangent factor. + * @return the surface tangent factor + */ + public float getSurfaceTangentFactor() { + return surfaceTangentFactor; + } + + /** + * This method sets the surface tangent rotation factor. + * @param surfaceTangentRotation + * the surface tangent rotation factor + */ + public void setSurfaceTangentRotation(float surfaceTangentRotation) { + this.surfaceTangentRotation = surfaceTangentRotation; + } + + /** + * This method returns the surface tangent rotation factor. + * @return the surface tangent rotation factor + */ + public float getSurfaceTangentRotation() { + return surfaceTangentRotation; + } + + @Override + protected void applyVelocityVariation(Particle particle) { + temp.set(FastMath.nextRandomFloat() * velocityVariation, FastMath.nextRandomFloat() * velocityVariation, FastMath.nextRandomFloat() * velocityVariation); + particle.velocity.addLocal(temp); + } + + @Override + public ParticleInfluencer clone() { + NewtonianParticleInfluencer result = new NewtonianParticleInfluencer(); + result.normalVelocity = normalVelocity; + result.initialVelocity = initialVelocity; + result.velocityVariation = velocityVariation; + result.surfaceTangentFactor = surfaceTangentFactor; + result.surfaceTangentRotation = surfaceTangentRotation; + return result; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(normalVelocity, "normalVelocity", 0.0f); + oc.write(surfaceTangentFactor, "surfaceTangentFactor", 0.0f); + oc.write(surfaceTangentRotation, "surfaceTangentRotation", 0.0f); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + normalVelocity = ic.readFloat("normalVelocity", 0.0f); + surfaceTangentFactor = ic.readFloat("surfaceTangentFactor", 0.0f); + surfaceTangentRotation = ic.readFloat("surfaceTangentRotation", 0.0f); + } +} diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java new file mode 100644 index 000000000..5e3532bb0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect.influencers; + +import com.jme3.effect.Particle; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.shapes.EmitterShape; +import com.jme3.export.Savable; +import com.jme3.math.Vector3f; + +/** + * An interface that defines the methods to affect initial velocity of the particles. + * @author Marcin Roguski (Kaelthas) + */ +public interface ParticleInfluencer extends Savable, Cloneable { + + /** + * This method influences the particle. + * @param particle + * particle to be influenced + * @param emitterShape + * the shape of it emitter + */ + void influenceParticle(Particle particle, EmitterShape emitterShape); + + /** + * This method clones the influencer instance. + * @return cloned instance + */ + public ParticleInfluencer clone(); + + /** + * @param initialVelocity + * Set the initial velocity a particle is spawned with, + * the initial velocity given in the parameter will be varied according + * to the velocity variation set in {@link ParticleEmitter#setVelocityVariation(float) }. + * A particle will move toward its velocity unless it is effected by the + * gravity. + */ + void setInitialVelocity(Vector3f initialVelocity); + + /** + * This method returns the initial velocity. + * @return the initial velocity + */ + Vector3f getInitialVelocity(); + + /** + * @param variation + * Set the variation by which the initial velocity + * of the particle is determined. variation should be a value + * from 0 to 1, where 0 means particles are to spawn with exactly + * the velocity given in {@link ParticleEmitter#setInitialVelocity(com.jme3.math.Vector3f) }, + * and 1 means particles are to spawn with a completely random velocity. + */ + void setVelocityVariation(float variation); + + /** + * This method returns the velocity variation. + * @return the velocity variation + */ + float getVelocityVariation(); +} diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java new file mode 100644 index 000000000..87c5ce507 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect.influencers; + +import com.jme3.effect.Particle; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * an influencer to make blasts expanding on the ground. can be used for various other things + * @author Nehon + */ +public class RadialParticleInfluencer extends DefaultParticleInfluencer { + + private float radialVelocity = 0f; + private Vector3f origin = new Vector3f(0, 0, 0); + private boolean horizontal = false; + + /** + * This method applies the variation to the particle with already set velocity. + * @param particle + * the particle to be affected + */ + @Override + protected void applyVelocityVariation(Particle particle) { + particle.velocity.set(initialVelocity); + temp.set(particle.position).subtractLocal(origin).normalizeLocal().multLocal(radialVelocity); + if (horizontal) { + temp.y = 0; + } + particle.velocity.addLocal(temp); + + temp.set(FastMath.nextRandomFloat(), FastMath.nextRandomFloat(), FastMath.nextRandomFloat()); + temp.multLocal(2f); + temp.subtractLocal(1f, 1f, 1f); + temp.multLocal(initialVelocity.length()); + particle.velocity.interpolateLocal(temp, velocityVariation); + } + + /** + * the origin used for computing the radial velocity direction + * @return the origin + */ + public Vector3f getOrigin() { + return origin; + } + + /** + * the origin used for computing the radial velocity direction + * @param origin + */ + public void setOrigin(Vector3f origin) { + this.origin = origin; + } + + /** + * the radial velocity + * @return radialVelocity + */ + public float getRadialVelocity() { + return radialVelocity; + } + + /** + * the radial velocity + * @param radialVelocity + */ + public void setRadialVelocity(float radialVelocity) { + this.radialVelocity = radialVelocity; + } + + /** + * nullify y component of particle velocity to make the effect expand only on x and z axis + * @return + */ + public boolean isHorizontal() { + return horizontal; + } + + /** + * nullify y component of particle velocity to make the effect expand only on x and z axis + * @param horizontal + */ + public void setHorizontal(boolean horizontal) { + this.horizontal = horizontal; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(radialVelocity, "radialVelocity", 0f); + oc.write(origin, "origin", new Vector3f()); + oc.write(horizontal, "horizontal", false); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + radialVelocity = ic.readFloat("radialVelocity", 0f); + origin = (Vector3f) ic.readSavable("origin", new Vector3f()); + horizontal = ic.readBoolean("horizontal", false); + } +} diff --git a/jme3-core/src/main/java/com/jme3/effect/package.html b/jme3-core/src/main/java/com/jme3/effect/package.html new file mode 100644 index 000000000..dd16da7af --- /dev/null +++ b/jme3-core/src/main/java/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/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java new file mode 100644 index 000000000..63323db7b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import java.io.IOException; + +public class EmitterBoxShape implements EmitterShape { + + private Vector3f min, len; + + public EmitterBoxShape() { + } + + public EmitterBoxShape(Vector3f min, Vector3f max) { + if (min == null || max == null) { + throw new IllegalArgumentException("min or max cannot be null"); + } + + this.min = min; + this.len = new Vector3f(); + this.len.set(max).subtractLocal(min); + } + + @Override + public void getRandomPoint(Vector3f store) { + store.x = min.x + len.x * FastMath.nextRandomFloat(); + store.y = min.y + len.y * FastMath.nextRandomFloat(); + store.z = min.z + len.z * FastMath.nextRandomFloat(); + } + + /** + * This method fills the point with data. + * It does not fill the normal. + * @param store the variable to store the point data + * @param normal not used in this class + */ + @Override + public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { + this.getRandomPoint(store); + } + + @Override + public EmitterShape deepClone() { + try { + EmitterBoxShape clone = (EmitterBoxShape) super.clone(); + clone.min = min.clone(); + clone.len = len.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + public Vector3f getMin() { + return min; + } + + public void setMin(Vector3f min) { + this.min = min; + } + + public Vector3f getLen() { + return len; + } + + public void setLen(Vector3f len) { + this.len = len; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(min, "min", null); + oc.write(len, "length", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + min = (Vector3f) ic.readSavable("min", null); + len = (Vector3f) ic.readSavable("length", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java new file mode 100644 index 000000000..c4557a3b9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect.shapes; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import java.util.List; + +/** + * This emiter shape emits the particles from the given shape's interior constrained by its convex hull + * (a geometry that tightly wraps the mesh). So in case of multiple meshes some vertices may appear + * in a space between them. + * @author Marcin Roguski (Kaelthas) + */ +public class EmitterMeshConvexHullShape extends EmitterMeshFaceShape { + + /** + * Empty constructor. Sets nothing. + */ + public EmitterMeshConvexHullShape() { + } + + /** + * Constructor. It stores a copy of vertex list of all meshes. + * @param meshes + * a list of meshes that will form the emitter's shape + */ + public EmitterMeshConvexHullShape(List meshes) { + super(meshes); + } + + /** + * This method fills the point with coordinates of randomly selected point inside a convex hull + * of randomly selected mesh. + * @param store + * the variable to store with coordinates of randomly selected selected point inside a convex hull + * of randomly selected mesh + */ + @Override + public void getRandomPoint(Vector3f store) { + super.getRandomPoint(store); + // now move the point from the meshe's face towards the center of the mesh + // the center is in (0, 0, 0) in the local coordinates + store.multLocal(FastMath.nextRandomFloat()); + } + + /** + * This method fills the point with coordinates of randomly selected point inside a convex hull + * of randomly selected mesh. + * The normal param is not used. + * @param store + * the variable to store with coordinates of randomly selected selected point inside a convex hull + * of randomly selected mesh + * @param normal + * not used in this class + */ + @Override + public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { + super.getRandomPointAndNormal(store, normal); + // now move the point from the meshe's face towards the center of the mesh + // the center is in (0, 0, 0) in the local coordinates + store.multLocal(FastMath.nextRandomFloat()); + } +} diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshFaceShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshFaceShape.java new file mode 100644 index 000000000..f2cd9ecc7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshFaceShape.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect.shapes; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import java.util.ArrayList; +import java.util.List; + +/** + * This emiter shape emits the particles from the given shape's faces. + * @author Marcin Roguski (Kaelthas) + */ +public class EmitterMeshFaceShape extends EmitterMeshVertexShape { + + /** + * Empty constructor. Sets nothing. + */ + public EmitterMeshFaceShape() { + } + + /** + * Constructor. It stores a copy of vertex list of all meshes. + * @param meshes + * a list of meshes that will form the emitter's shape + */ + public EmitterMeshFaceShape(List meshes) { + super(meshes); + } + + @Override + public void setMeshes(List meshes) { + this.vertices = new ArrayList>(meshes.size()); + this.normals = new ArrayList>(meshes.size()); + for (Mesh mesh : meshes) { + Vector3f[] vertexTable = BufferUtils.getVector3Array(mesh.getFloatBuffer(Type.Position)); + int[] indices = new int[3]; + List vertices = new ArrayList(mesh.getTriangleCount() * 3); + List normals = new ArrayList(mesh.getTriangleCount()); + for (int i = 0; i < mesh.getTriangleCount(); ++i) { + mesh.getTriangle(i, indices); + vertices.add(vertexTable[indices[0]]); + vertices.add(vertexTable[indices[1]]); + vertices.add(vertexTable[indices[2]]); + normals.add(FastMath.computeNormal(vertexTable[indices[0]], vertexTable[indices[1]], vertexTable[indices[2]])); + } + this.vertices.add(vertices); + this.normals.add(normals); + } + } + + /** + * This method fills the point with coordinates of randomly selected point on a random face. + * @param store + * the variable to store with coordinates of randomly selected selected point on a random face + */ + @Override + public void getRandomPoint(Vector3f store) { + int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1); + // the index of the first vertex of a face (must be dividable by 3) + int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() / 3 - 1) * 3; + // put the point somewhere between the first and the second vertex of a face + float moveFactor = FastMath.nextRandomFloat(); + store.set(Vector3f.ZERO); + store.addLocal(vertices.get(meshIndex).get(vertIndex)); + store.addLocal((vertices.get(meshIndex).get(vertIndex + 1).x - vertices.get(meshIndex).get(vertIndex).x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).y - vertices.get(meshIndex).get(vertIndex).y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).z - vertices.get(meshIndex).get(vertIndex).z) * moveFactor); + // move the result towards the last face vertex + moveFactor = FastMath.nextRandomFloat(); + store.addLocal((vertices.get(meshIndex).get(vertIndex + 2).x - store.x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).y - store.y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).z - store.z) * moveFactor); + } + + /** + * This method fills the point with coordinates of randomly selected point on a random face. + * The normal param is filled with selected face's normal. + * @param store + * the variable to store with coordinates of randomly selected selected point on a random face + * @param normal + * filled with selected face's normal + */ + @Override + public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { + int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1); + // the index of the first vertex of a face (must be dividable by 3) + int faceIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() / 3 - 1); + int vertIndex = faceIndex * 3; + // put the point somewhere between the first and the second vertex of a face + float moveFactor = FastMath.nextRandomFloat(); + store.set(Vector3f.ZERO); + store.addLocal(vertices.get(meshIndex).get(vertIndex)); + store.addLocal((vertices.get(meshIndex).get(vertIndex + 1).x - vertices.get(meshIndex).get(vertIndex).x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).y - vertices.get(meshIndex).get(vertIndex).y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).z - vertices.get(meshIndex).get(vertIndex).z) * moveFactor); + // move the result towards the last face vertex + moveFactor = FastMath.nextRandomFloat(); + store.addLocal((vertices.get(meshIndex).get(vertIndex + 2).x - store.x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).y - store.y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).z - store.z) * moveFactor); + normal.set(normals.get(meshIndex).get(faceIndex)); + } +} diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java new file mode 100644 index 000000000..0a5ba128a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * This emiter shape emits the particles from the given shape's vertices + * @author Marcin Roguski (Kaelthas) + */ +public class EmitterMeshVertexShape implements EmitterShape { + + protected List> vertices; + protected List> normals; + + /** + * Empty constructor. Sets nothing. + */ + public EmitterMeshVertexShape() { + } + + /** + * Constructor. It stores a copy of vertex list of all meshes. + * @param meshes + * a list of meshes that will form the emitter's shape + */ + public EmitterMeshVertexShape(List meshes) { + this.setMeshes(meshes); + } + + /** + * This method sets the meshes that will form the emiter's shape. + * @param meshes + * a list of meshes that will form the emitter's shape + */ + public void setMeshes(List meshes) { + Map vertToNormalMap = new HashMap(); + + this.vertices = new ArrayList>(meshes.size()); + this.normals = new ArrayList>(meshes.size()); + for (Mesh mesh : meshes) { + // fetching the data + float[] vertexTable = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Position)); + float[] normalTable = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Normal)); + + // unifying normals + for (int i = 0; i < vertexTable.length; i += 3) {// the tables should have the same size and be dividable by 3 + Vector3f vert = new Vector3f(vertexTable[i], vertexTable[i + 1], vertexTable[i + 2]); + Vector3f norm = vertToNormalMap.get(vert); + if (norm == null) { + norm = new Vector3f(normalTable[i], normalTable[i + 1], normalTable[i + 2]); + vertToNormalMap.put(vert, norm); + } else { + norm.addLocal(normalTable[i], normalTable[i + 1], normalTable[i + 2]); + } + } + + // adding data to vertices and normals + List vertices = new ArrayList(vertToNormalMap.size()); + List normals = new ArrayList(vertToNormalMap.size()); + for (Entry entry : vertToNormalMap.entrySet()) { + vertices.add(entry.getKey()); + normals.add(entry.getValue().normalizeLocal()); + } + this.vertices.add(vertices); + this.normals.add(normals); + } + } + + /** + * This method fills the point with coordinates of randomly selected mesh vertex. + * @param store + * the variable to store with coordinates of randomly selected mesh vertex + */ + @Override + public void getRandomPoint(Vector3f store) { + int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1); + int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() - 1); + store.set(vertices.get(meshIndex).get(vertIndex)); + } + + /** + * This method fills the point with coordinates of randomly selected mesh vertex. + * The normal param is filled with selected vertex's normal. + * @param store + * the variable to store with coordinates of randomly selected mesh vertex + * @param normal + * filled with selected vertex's normal + */ + @Override + public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { + int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1); + int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() - 1); + store.set(vertices.get(meshIndex).get(vertIndex)); + normal.set(normals.get(meshIndex).get(vertIndex)); + } + + @Override + public EmitterShape deepClone() { + try { + EmitterMeshVertexShape clone = (EmitterMeshVertexShape) super.clone(); + if (this.vertices != null) { + clone.vertices = new ArrayList>(vertices.size()); + for (List list : vertices) { + List vectorList = new ArrayList(list.size()); + for (Vector3f vector : list) { + vectorList.add(vector.clone()); + } + clone.vertices.add(vectorList); + } + } + if (this.normals != null) { + clone.normals = new ArrayList>(normals.size()); + for (List list : normals) { + List vectorList = new ArrayList(list.size()); + for (Vector3f vector : list) { + vectorList.add(vector.clone()); + } + clone.normals.add(vectorList); + } + } + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.writeSavableArrayList((ArrayList>) vertices, "vertices", null); + oc.writeSavableArrayList((ArrayList>) normals, "normals", null); + } + + @Override + @SuppressWarnings("unchecked") + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + this.vertices = ic.readSavableArrayList("vertices", null); + + List> tmpNormals = ic.readSavableArrayList("normals", null); + if (tmpNormals != null){ + this.normals = tmpNormals; + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java new file mode 100644 index 000000000..9f7e71107 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect.shapes; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; + +public class EmitterPointShape implements EmitterShape { + + private Vector3f point; + + public EmitterPointShape() { + } + + public EmitterPointShape(Vector3f point) { + this.point = point; + } + + @Override + public EmitterShape deepClone() { + try { + EmitterPointShape clone = (EmitterPointShape) super.clone(); + clone.point = point.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void getRandomPoint(Vector3f store) { + store.set(point); + } + + /** + * This method fills the point with data. + * It does not fill the normal. + * @param store the variable to store the point data + * @param normal not used in this class + */ + @Override + public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { + store.set(point); + } + + public Vector3f getPoint() { + return point; + } + + public void setPoint(Vector3f point) { + this.point = point; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(point, "point", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + this.point = (Vector3f) im.getCapsule(this).readSavable("point", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java new file mode 100644 index 000000000..bdecd5b5f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect.shapes; + +import com.jme3.export.Savable; +import com.jme3.math.Vector3f; + +/** + * This interface declares methods used by all shapes that represent particle emitters. + * @author Kirill + */ +public interface EmitterShape extends Savable, Cloneable { + + /** + * This method fills in the initial position of the particle. + * @param store + * store variable for initial position + */ + public void getRandomPoint(Vector3f store); + + /** + * This method fills in the initial position of the particle and its normal vector. + * @param store + * store variable for initial position + * @param normal + * store variable for initial normal + */ + public void getRandomPointAndNormal(Vector3f store, Vector3f normal); + + /** + * This method creates a deep clone of the current instance of the emitter shape. + * @return deep clone of the current instance of the emitter shape + */ + public EmitterShape deepClone(); +} diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java new file mode 100644 index 000000000..770ba6c9d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.effect.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import java.io.IOException; + +public class EmitterSphereShape implements EmitterShape { + + private Vector3f center; + private float radius; + + public EmitterSphereShape() { + } + + public EmitterSphereShape(Vector3f center, float radius) { + if (center == null) { + throw new IllegalArgumentException("center cannot be null"); + } + + if (radius <= 0) { + throw new IllegalArgumentException("Radius must be greater than 0"); + } + + this.center = center; + this.radius = radius; + } + + @Override + public EmitterShape deepClone() { + try { + EmitterSphereShape clone = (EmitterSphereShape) super.clone(); + clone.center = center.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void getRandomPoint(Vector3f store) { + do { + store.x = (FastMath.nextRandomFloat() * 2f - 1f) * radius; + store.y = (FastMath.nextRandomFloat() * 2f - 1f) * radius; + store.z = (FastMath.nextRandomFloat() * 2f - 1f) * radius; + } while (store.distance(center) > radius); + } + + @Override + public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { + this.getRandomPoint(store); + } + + public Vector3f getCenter() { + return center; + } + + public void setCenter(Vector3f center) { + this.center = center; + } + + public float getRadius() { + return radius; + } + + public void setRadius(float radius) { + this.radius = radius; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(center, "center", null); + oc.write(radius, "radius", 0); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + center = (Vector3f) ic.readSavable("center", null); + radius = ic.readFloat("radius", 0); + } +} diff --git a/jme3-core/src/main/java/com/jme3/export/FormatVersion.java b/jme3-core/src/main/java/com/jme3/export/FormatVersion.java new file mode 100644 index 000000000..053b5066d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/export/FormatVersion.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export; + +/** + * Specifies the version of the format for jME3 object (j3o) files. + * + * @author Kirill Vainer + */ +public final class FormatVersion { + + /** + * Version number of the format + */ + public static final int VERSION = 2; + + /** + * Signature of the format. Currently "JME3" as ASCII + */ + public static final int SIGNATURE = 0x4A4D4533; + + private FormatVersion(){ + } +} diff --git a/jme3-core/src/main/java/com/jme3/export/InputCapsule.java b/jme3-core/src/main/java/com/jme3/export/InputCapsule.java new file mode 100644 index 000000000..133ea3467 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/export/InputCapsule.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export; + +import com.jme3.util.IntMap; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Map; + +/** + * @author Joshua Slack + */ +public interface InputCapsule { + + public int getSavableVersion(Class clazz); + + // byte primitive + + public byte readByte(String name, byte defVal) throws IOException; + public byte[] readByteArray(String name, byte[] defVal) throws IOException; + public byte[][] readByteArray2D(String name, byte[][] defVal) throws IOException; + + // int primitive + + public int readInt(String name, int defVal) throws IOException; + public int[] readIntArray(String name, int[] defVal) throws IOException; + public int[][] readIntArray2D(String name, int[][] defVal) throws IOException; + + + // float primitive + + public float readFloat(String name, float defVal) throws IOException; + public float[] readFloatArray(String name, float[] defVal) throws IOException; + public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException; + + + // double primitive + + public double readDouble(String name, double defVal) throws IOException; + public double[] readDoubleArray(String name, double[] defVal) throws IOException; + public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException; + + + // long primitive + + public long readLong(String name, long defVal) throws IOException; + public long[] readLongArray(String name, long[] defVal) throws IOException; + public long[][] readLongArray2D(String name, long[][] defVal) throws IOException; + + + // short primitive + + public short readShort(String name, short defVal) throws IOException; + public short[] readShortArray(String name, short[] defVal) throws IOException; + public short[][] readShortArray2D(String name, short[][] defVal) throws IOException; + + + // boolean primitive + + public boolean readBoolean(String name, boolean defVal) throws IOException; + public boolean[] readBooleanArray(String name, boolean[] defVal) throws IOException; + public boolean[][] readBooleanArray2D(String name, boolean[][] defVal) throws IOException; + + + // String + + public String readString(String name, String defVal) throws IOException; + public String[] readStringArray(String name, String[] defVal) throws IOException; + public String[][] readStringArray2D(String name, String[][] defVal) throws IOException; + + + // BitSet + + public BitSet readBitSet(String name, BitSet defVal) throws IOException; + + + // BinarySavable + + public Savable readSavable(String name, Savable defVal) throws IOException; + public Savable[] readSavableArray(String name, Savable[] defVal) throws IOException; + public Savable[][] readSavableArray2D(String name, Savable[][] defVal) throws IOException; + + + // ArrayLists + + public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException; + public ArrayList[] readSavableArrayListArray(String name, ArrayList[] defVal) throws IOException; + public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException; + + public ArrayList 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/jme3-core/src/main/java/com/jme3/export/JmeExporter.java b/jme3-core/src/main/java/com/jme3/export/JmeExporter.java new file mode 100644 index 000000000..0afd170ea --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/export/JmeExporter.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; + +/** + * JmeExporter specifies an export implementation for jME3 + * data. + */ +public interface JmeExporter { + + /** + * Export the {@link Savable} to an OutputStream. + * + * @param object The savable to export + * @param f The output stream + * @return Always returns true. If an error occurs during export, + * an exception is thrown + * @throws IOException If an io exception occurs during export + */ + public boolean save(Savable object, OutputStream f) throws IOException; + + /** + * Export the {@link Savable} to a file. + * + * @param object The savable to export + * @param f The file to export to + * @return Always returns true. If an error occurs during export, + * an exception is thrown + * @throws IOException If an io exception occurs during export + */ + public boolean save(Savable object, File f) throws IOException; + + /** + * Returns the {@link OutputCapsule} for the given savable object. + * + * @param object The object to retrieve an output capsule for. + * @return the {@link OutputCapsule} for the given savable object. + */ + public OutputCapsule getCapsule(Savable object); +} diff --git a/jme3-core/src/main/java/com/jme3/export/JmeImporter.java b/jme3-core/src/main/java/com/jme3/export/JmeImporter.java new file mode 100644 index 000000000..b556a9cd9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/export/JmeImporter.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export; + +import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetManager; + +public interface JmeImporter extends AssetLoader { + public InputCapsule getCapsule(Savable id); + public AssetManager getAssetManager(); + + /** + * Returns the version number written in the header of the J3O/XML + * file. + * + * @return Global version number for the file + */ + public int getFormatVersion(); +} diff --git a/jme3-core/src/main/java/com/jme3/export/NullSavable.java b/jme3-core/src/main/java/com/jme3/export/NullSavable.java new file mode 100644 index 000000000..a57dcc701 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/export/NullSavable.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export; + +import java.io.IOException; + +/** + * NullSavable is an implementation of Savable with no data. + * It is used for backward compatibility with versions of the J3O + * format that wrote Blender importer's "Properties" class. + * + * @author Kirill Vainer + */ +public class NullSavable implements Savable { + public void write(JmeExporter ex) throws IOException { + } + public void read(JmeImporter im) throws IOException { + } +} diff --git a/jme3-core/src/main/java/com/jme3/export/OutputCapsule.java b/jme3-core/src/main/java/com/jme3/export/OutputCapsule.java new file mode 100644 index 000000000..1612a61c1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/export/OutputCapsule.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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/jme3-core/src/main/java/com/jme3/export/ReadListener.java b/jme3-core/src/main/java/com/jme3/export/ReadListener.java new file mode 100644 index 000000000..24802160b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/export/ReadListener.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export; + +public interface ReadListener { + + public void readBytes(int bytes); + +} diff --git a/jme3-core/src/main/java/com/jme3/export/Savable.java b/jme3-core/src/main/java/com/jme3/export/Savable.java new file mode 100644 index 000000000..85957fc24 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/export/Savable.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export; + +import java.io.IOException; + +/** + * Savable is an interface for objects that can be serialized + * using jME's serialization system. + * + * @author Kirill Vainer + */ +public interface Savable { + void write(JmeExporter ex) throws IOException; + void read(JmeImporter im) throws IOException; +} diff --git a/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java new file mode 100644 index 000000000..f7230fd4e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export; + +import com.jme3.animation.Animation; +import com.jme3.effect.shapes.*; +import com.jme3.material.MatParamTexture; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * SavableClassUtil contains various utilities to handle + * Savable classes. The methods are general enough to not be specific to any + * particular implementation. + * Currently it will remap any classes from old paths to new paths + * so that old J3O models can still be loaded. + * + * @author mpowell + * @author Kirill Vainer + */ +public class SavableClassUtil { + + private final static HashMap classRemappings = new HashMap(); + + private static void addRemapping(String oldClass, Class newClass){ + classRemappings.put(oldClass, newClass.getName()); + } + + static { + addRemapping("com.jme3.effect.EmitterSphereShape", EmitterSphereShape.class); + addRemapping("com.jme3.effect.EmitterBoxShape", EmitterBoxShape.class); + addRemapping("com.jme3.effect.EmitterMeshConvexHullShape", EmitterMeshConvexHullShape.class); + addRemapping("com.jme3.effect.EmitterMeshFaceShape", EmitterMeshFaceShape.class); + addRemapping("com.jme3.effect.EmitterMeshVertexShape", EmitterMeshVertexShape.class); + addRemapping("com.jme3.effect.EmitterPointShape", EmitterPointShape.class); + addRemapping("com.jme3.material.Material$MatParamTexture", MatParamTexture.class); + addRemapping("com.jme3.animation.BoneAnimation", Animation.class); + addRemapping("com.jme3.animation.SpatialAnimation", Animation.class); + addRemapping("com.jme3.scene.plugins.blender.objects.Properties", NullSavable.class); + } + + private static String remapClass(String className) throws ClassNotFoundException { + String result = classRemappings.get(className); + if (result == null) { + return className; + } else { + return result; + } + } + + public static boolean isImplementingSavable(Class clazz){ + boolean result = Savable.class.isAssignableFrom(clazz); + return result; + } + + public static int[] getSavableVersions(Class clazz) throws IOException{ + ArrayList versionList = new ArrayList(); + Class superclass = clazz; + do { + versionList.add(getSavableVersion(superclass)); + superclass = superclass.getSuperclass(); + } while (superclass != null && SavableClassUtil.isImplementingSavable(superclass)); + + int[] versions = new int[versionList.size()]; + for (int i = 0; i < versionList.size(); i++){ + versions[i] = versionList.get(i); + } + return versions; + } + + public static int getSavableVersion(Class clazz) throws IOException{ + try { + Field field = clazz.getField("SAVABLE_VERSION"); + Class declaringClass = (Class) field.getDeclaringClass(); + if (declaringClass == clazz){ + return field.getInt(null); + }else{ + return 0; // This class doesn't declare this field, e.g. version == 0 + } + } catch (IllegalAccessException ex) { + IOException ioEx = new IOException(); + ioEx.initCause(ex); + throw ioEx; + } catch (IllegalArgumentException ex) { + throw ex; // can happen if SAVABLE_VERSION is not static + } catch (NoSuchFieldException ex) { + return 0; // not using versions + } + } + + public static int getSavedSavableVersion(Object savable, Class desiredClass, int[] versions, int formatVersion){ + Class thisClass = savable.getClass(); + int count = 0; + + while (thisClass != desiredClass) { + thisClass = thisClass.getSuperclass(); + if (thisClass != null && SavableClassUtil.isImplementingSavable(thisClass)){ + count ++; + }else{ + break; + } + } + + if (thisClass == null){ + throw new IllegalArgumentException(savable.getClass().getName() + + " does not extend " + + desiredClass.getName() + "!"); + }else if (count >= versions.length){ + if (formatVersion <= 1){ + return 0; // for buggy versions of j3o + }else{ + throw new IllegalArgumentException(savable.getClass().getName() + + " cannot access version of " + + desiredClass.getName() + + " because it doesn't implement Savable"); + } + } + return versions[count]; + } + + /** + * fromName creates a new Savable from the provided class name. First registered modules + * are checked to handle special cases, if the modules do not handle the class name, the + * class is instantiated directly. + * @param className the class name to create. + * @return the Savable instance of the class. + * @throws InstantiationException thrown if the class does not have an empty constructor. + * @throws IllegalAccessException thrown if the class is not accessable. + * @throws ClassNotFoundException thrown if the class name is not in the classpath. + * @throws IOException when loading ctor parameters fails + */ + public static Savable fromName(String className) throws InstantiationException, + IllegalAccessException, ClassNotFoundException, IOException { + + className = remapClass(className); + try { + return (Savable) Class.forName(className).newInstance(); + } catch (InstantiationException e) { + Logger.getLogger(SavableClassUtil.class.getName()).log( + Level.SEVERE, "Could not access constructor of class ''{0}" + "''! \n" + + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup.", className); + throw e; + } catch (IllegalAccessException e) { + Logger.getLogger(SavableClassUtil.class.getName()).log( + Level.SEVERE, "{0} \n" + + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup.", e.getMessage()); + throw e; + } + } + + public static Savable fromName(String className, List loaders) throws InstantiationException, + IllegalAccessException, ClassNotFoundException, IOException { + if (loaders == null) { + return fromName(className); + } + + String newClassName = remapClass(className); + synchronized(loaders) { + for (ClassLoader classLoader : loaders){ + try { + return (Savable) classLoader.loadClass(newClassName).newInstance(); + } catch (InstantiationException e) { + } catch (IllegalAccessException e) { + } + + } + } + + return fromName(className); + } +} diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapCharacter.java b/jme3-core/src/main/java/com/jme3/font/BitmapCharacter.java new file mode 100644 index 000000000..1fb13ab93 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/BitmapCharacter.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.font; + +import com.jme3.export.*; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.io.IOException; + +/** + * Represents a single bitmap character. + */ +public class BitmapCharacter implements Savable, Cloneable { + private char c; + private int x; + private int y; + private int width; + private int height; + private int xOffset; + private int yOffset; + private int xAdvance; + private IntMap 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/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java b/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java new file mode 100644 index 000000000..8760e0ec0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.font; + +import com.jme3.export.*; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.io.IOException; + +public class BitmapCharacterSet implements Savable { + + private int lineHeight; + private int base; + private int renderedSize; + private int width; + private int height; + private IntMap> 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/jme3-core/src/main/java/com/jme3/font/BitmapFont.java b/jme3-core/src/main/java/com/jme3/font/BitmapFont.java new file mode 100644 index 000000000..856268c37 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/BitmapFont.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.font; + +import com.jme3.export.*; +import com.jme3.material.Material; +import java.io.IOException; + +/** + * Represents a font within jME that is generated with the AngelCode Bitmap Font Generator + * @author dhdd + */ +public class BitmapFont implements Savable { + + /** + * Specifies horizontal alignment for text. + * + * @see BitmapText#setAlignment(com.jme3.font.BitmapFont.Align) + */ + public enum Align { + + /** + * Align text on the left of the text block + */ + Left, + + /** + * Align text in the center of the text block + */ + Center, + + /** + * Align text on the right of the text block + */ + Right + } + + /** + * Specifies vertical alignment for text. + * + * @see BitmapText#setVerticalAlignment(com.jme3.font.BitmapFont.VAlign) + */ + public enum VAlign { + /** + * Align text on the top of the text block + */ + Top, + + /** + * Align text in the center of the text block + */ + Center, + + /** + * Align text at the bottom of the text block + */ + Bottom + } + + private BitmapCharacterSet charSet; + private Material[] pages; + + public BitmapFont() { + } + + public BitmapText createLabel(String content){ + BitmapText label = new BitmapText(this); + label.setSize(getCharSet().getRenderedSize()); + label.setText(content); + return label; + } + + public float getPreferredSize(){ + return getCharSet().getRenderedSize(); + } + + public void setCharSet(BitmapCharacterSet charSet) { + this.charSet = charSet; + } + + public void setPages(Material[] pages) { + this.pages = pages; + charSet.setPageSize(pages.length); + } + + public Material getPage(int index) { + return pages[index]; + } + + public int getPageSize() { + return pages.length; + } + + public BitmapCharacterSet getCharSet() { + return charSet; + } + + /** + * Gets the line height of a StringBlock. + * @param sb + * @return the line height + */ + public float getLineHeight(StringBlock sb) { + return charSet.getLineHeight() * (sb.getSize() / charSet.getRenderedSize()); + } + + public float getCharacterAdvance(char curChar, char nextChar, float size){ + BitmapCharacter c = charSet.getCharacter(curChar); + if (c == null) + return 0f; + + float advance = size * c.getXAdvance(); + advance += c.getKerning(nextChar) * size; + return advance; + } + + private int findKerningAmount(int newLineLastChar, int nextChar) { + BitmapCharacter c = charSet.getCharacter(newLineLastChar); + if (c == null) + return 0; + return c.getKerning(nextChar); + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(charSet, "charSet", null); + oc.write(pages, "pages", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + charSet = (BitmapCharacterSet) ic.readSavable("charSet", null); + Savable[] pagesSavable = ic.readSavableArray("pages", null); + pages = new Material[pagesSavable.length]; + System.arraycopy(pagesSavable, 0, pages, 0, pages.length); + } + + public float getLineWidth(CharSequence text){ + + // This method will probably always be a bit of a maintenance + // nightmare since it basis its calculation on a different + // routine than the Letters class. The ideal situation would + // be to abstract out letter position and size into its own + // class that both BitmapFont and Letters could use for + // positioning. + // If getLineWidth() here ever again returns a different value + // than Letters does with the same text then it might be better + // just to create a Letters object for the sole purpose of + // getting a text size. It's less efficient but at least it + // would be accurate. + + // And here I am mucking around in here again... + // + // A font character has a few values that are pertinent to the + // line width: + // xOffset + // xAdvance + // kerningAmount(nextChar) + // + // The way BitmapText ultimately works is that the first character + // starts with xOffset included (ie: it is rendered at -xOffset). + // Its xAdvance is wider to accomodate that initial offset. + // The cursor position is advanced by xAdvance each time. + // + // So, a width should be calculated in a similar way. Start with + // -xOffset + xAdvance for the first character and then each subsequent + // character is just xAdvance more 'width'. + // + // The kerning amount from one character to the next affects the + // cursor position of that next character and thus the ultimate width + // and so must be factored in also. + + float lineWidth = 0f; + float maxLineWidth = 0f; + char lastChar = 0; + boolean firstCharOfLine = true; +// float sizeScale = (float) block.getSize() / charSet.getRenderedSize(); + float sizeScale = 1f; + for (int i = 0; i < text.length(); i++){ + char theChar = text.charAt(i); + if (theChar == '\n'){ + maxLineWidth = Math.max(maxLineWidth, lineWidth); + lineWidth = 0f; + firstCharOfLine = true; + continue; + } + BitmapCharacter c = charSet.getCharacter((int) theChar); + if (c != null){ + if (theChar == '\\' && 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. + * Clip : The text is hard-clipped at the border including showing only a partial letter if it goes beyond the text bound. + */ + 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; + } + + private ColorRGBA getColor( Material mat, String name ) { + MatParam mp = mat.getParam(name); + if( mp == null ) { + return null; + } + return (ColorRGBA)mp.getValue(); + } + + public void render(RenderManager rm, ColorRGBA color) { + for (BitmapTextPage page : textPages) { + Material mat = page.getMaterial(); + mat.setTexture("ColorMap", page.getTexture()); + //ColorRGBA original = getColor(mat, "Color"); + //mat.setColor("Color", color); + mat.render(page, rm); + + //if( original == null ) { + // mat.clearParam("Color"); + //} else { + // mat.setColor("Color", original); + //} + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java b/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java new file mode 100644 index 000000000..1edac967c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.font; + +import com.jme3.material.Material; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; +import java.util.LinkedList; + +/** + * One page per BitmapText Font Texture. + * @author Lim, YongHoon + */ +class BitmapTextPage extends Geometry { + + private final float[] pos; + private final float[] tc; + private final short[] idx; + private final byte[] color; + private final int page; + private final Texture2D texture; + private final LinkedList pageQuads = new LinkedList(); + + BitmapTextPage(BitmapFont font, boolean arrayBased, int page) { + super("BitmapFont", new Mesh()); + setBatchHint(BatchHint.Never); + if (font == null) { + throw new IllegalArgumentException("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; + + /* + * TODO: Since this is forced to true, should we just lose the conditional? + * - Skye (sbook) + */ + 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/jme3-core/src/main/java/com/jme3/font/ColorTags.java b/jme3-core/src/main/java/com/jme3/font/ColorTags.java new file mode 100644 index 000000000..83f46b3d0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/ColorTags.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.font; + +import com.jme3.math.ColorRGBA; +import java.util.LinkedList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Contains the color information tagged in a text string + * Format: \#rgb# + * \#rgba# + * \#rrggbb# + * \#rrggbbaa# + * @author YongHoon + */ +class ColorTags { + private static final Pattern colorPattern = Pattern.compile("\\\\#([0-9a-fA-F]{8})#|\\\\#([0-9a-fA-F]{6})#|" + + "\\\\#([0-9a-fA-F]{4})#|\\\\#([0-9a-fA-F]{3})#"); + private LinkedList colors = new LinkedList(); + private String text; + private String original; + private float baseAlpha = -1; + + ColorTags() { } + + ColorTags(String seq) { + setText(seq); + } + + /** + * @return text without color tags + */ + String getPlainText() { + return text; + } + + LinkedList getTags() { + return colors; + } + + void setText(final String charSeq) { + original = 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; + } + } + + void setBaseAlpha( float alpha ) { + this.baseAlpha = alpha; + if( alpha == -1 ) { + // Need to reinitialize from the original text + setText(original); + return; + } + + // Else set the alpha for all of them + for( Range r : colors ) { + r.color.a = alpha; + } + } + + /** + * Sets the colors of all ranges, overriding any color tags + * that were in the original text. + */ + void setBaseColor( ColorRGBA color ) { + // There are times when the alpha is directly modified + // and the caller may have passed a constant... so we + // should clone it. + color = color.clone(); + for( Range r : colors ) { + r.color = color; + } + } + + 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 (baseAlpha != -1) { + color.a = baseAlpha; + } + else 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 (baseAlpha != -1) { + color.a = baseAlpha; + } else if (colorStr.length() == 4) { + color.a = Integer.parseInt(Character.toString(colorStr.charAt(3)), 16) / 15f; + } + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/font/Kerning.java b/jme3-core/src/main/java/com/jme3/font/Kerning.java new file mode 100644 index 000000000..3a6daeed3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/Kerning.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.font; + +import com.jme3.export.*; +import java.io.IOException; + + +/** + * Represents kerning information for a character. + */ +public class Kerning implements Savable { + + private int second; + private int amount; + + public int getSecond() { + return second; + } + + public void setSecond(int second) { + this.second = second; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(second, "second", 0); + oc.write(amount, "amount", 0); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + second = ic.readInt("second", 0); + amount = ic.readInt("amount", 0); + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/font/LetterQuad.java b/jme3-core/src/main/java/com/jme3/font/LetterQuad.java new file mode 100644 index 000000000..7977a154e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/LetterQuad.java @@ -0,0 +1,554 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.font; + +import com.jme3.math.ColorRGBA; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + * LetterQuad contains the position, color, and 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 isRightToLeft(){ + return rightToLeft; + } + + 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(); + } + + void clip(StringBlock block) { + Rectangle bound = block.getTextBox(); + if (bound == null) + return; + + // Clip the right x position and texture coordinate + // to the string block + float x1 = Math.min(bound.x + bound.width, x0 + width); + float newWidth = x1 - x0; + if( newWidth == width ) + return; + + float rescale = newWidth / width; + u1 = u0 + (u1 - u0) * rescale; + width = newWidth; + } + + 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 setAlpha(float alpha) { + int i = (int)(alpha * 255) & 0xFF; + colorInt = (colorInt & 0xffffff00) | i; + invalidate(); + } + + void setBitmapChar(char c) { + BitmapCharacterSet charSet = font.getCharSet(); + BitmapCharacter bm = charSet.getCharacter(c, style); + setBitmapChar(bm); + } + + void setBitmapChar(BitmapCharacter bitmapChar) { + x0 = Integer.MIN_VALUE; + y0 = Integer.MIN_VALUE; + width = Integer.MIN_VALUE; + height = Integer.MIN_VALUE; + alignX = 0; + alignY = 0; + + BitmapCharacterSet charSet = font.getCharSet(); + this.bitmapChar = bitmapChar; + if (bitmapChar != null) { + u0 = (float) bitmapChar.getX() / charSet.getWidth(); + v0 = (float) bitmapChar.getY() / charSet.getHeight(); + u1 = u0 + (float) bitmapChar.getWidth() / charSet.getWidth(); + v1 = v0 + (float) bitmapChar.getHeight() / charSet.getHeight(); + } else { + u0 = 0; + v0 = 0; + u1 = 0; + v1 = 0; + } + } + + void setNext(LetterQuad next) { + this.next = next; + } + + void update(StringBlock block) { + final float[] tabs = block.getTabPosition(); + final float tabWidth = block.getTabWidth(); + final Rectangle bound = getBound(block); + sizeScale = block.getSize() / font.getCharSet().getRenderedSize(); + lineY = computeLineY(block); + + if (isHead()) { + x0 = getBound(block).x; + y0 = lineY; + width = 0; + height = 0; + xAdvance = 0; + } else if (isTab()) { + x0 = previous.getNextX(); + width = tabWidth; + y0 = lineY; + height = 0; + if (tabs != null && x0 < tabs[tabs.length-1]) { + for (int i = 0; i < tabs.length-1; i++) { + if (x0 > tabs[i] && x0 < tabs[i+1]) { + width = tabs[i+1] - x0; + } + } + } + xAdvance = width; + } else if (bitmapChar == null) { + x0 = getPrevious().getX1(); + y0 = lineY; + width = 0; + height = 0; + xAdvance = 0; + } else { + float xOffset = bitmapChar.getXOffset() * sizeScale; + float yOffset = bitmapChar.getYOffset() * sizeScale; + xAdvance = bitmapChar.getXAdvance() * sizeScale; + width = bitmapChar.getWidth() * sizeScale; + height = bitmapChar.getHeight() * sizeScale; + float incrScale = rightToLeft ? -1f : 1f; + float kernAmount = 0f; + + if (previous.isHead() || previous.eol) { + x0 = bound.x; + + // The first letter quad will be drawn right at the first + // position... but it does not offset by the characters offset + // amount. This means that we've potentially accumulated extra + // pixels and the next letter won't get drawn far enough unless + // we add this offset back into xAdvance.. by subtracting it. + // This is the same thing that's done below because we've + // technically baked the offset in just like below. It doesn't + // look like it at first glance so I'm keeping it separate with + // this comment. + xAdvance -= xOffset * incrScale; + + } else { + x0 = previous.getNextX() + xOffset * incrScale; + + // Since x0 will have offset baked into it then we + // need to counteract that in xAdvance. This is better + // than removing it in getNextX() because we also need + // to take kerning into account below... which will also + // get baked in. + // Without this, getNextX() will return values too far to + // the left, for example. + xAdvance -= xOffset * incrScale; + } + y0 = lineY + LINE_DIR*yOffset; + + // Adjust for kerning + BitmapCharacter lastChar = previous.getBitmapChar(); + if (lastChar != null && block.isKerning()) { + kernAmount = lastChar.getKerning(c) * sizeScale; + x0 += kernAmount * incrScale; + + // Need to unbake the kerning from xAdvance since it + // is baked into x0... see above. + //xAdvance -= kernAmount * incrScale; + // No, kerning is an inter-character spacing and _does_ affect + // all subsequent cursor positions. + } + } + if (isEndOfLine()) { + xAdvance = bound.x-x0; + } + } + + /** + * add temporary linewrap indicator + */ + void setEndOfLine() { + this.eol = true; + } + + boolean isEndOfLine() { + return eol; + } + + boolean isLineWrap() { + return !isHead() && !isTail() && bitmapChar == null && c == Character.MIN_VALUE; + } + + private float computeLineY(StringBlock block) { + if (isHead()) { + return getBound(block).y; + } else if (previous.eol) { + return previous.getNextLine(); + } else { + return previous.lineY; + } + } + + + boolean isLineStart() { + return x0 == 0 || (previous != null && previous.eol); + } + + boolean isBlank() { + return c == ' ' || isTab(); + } + + public void storeToArrays(float[] pos, float[] tc, short[] idx, byte[] colors, int quadIdx){ + float x = x0+alignX; + float y = y0-alignY; + float xpw = x+width; + float ymh = y-height; + + pos[0] = x; pos[1] = y; pos[2] = 0; + pos[3] = x; pos[4] = ymh; pos[5] = 0; + pos[6] = xpw; pos[7] = ymh; pos[8] = 0; + pos[9] = xpw; pos[10] = y; pos[11] = 0; + + float v0 = 1f - this.v0; + float v1 = 1f - this.v1; + + tc[0] = u0; tc[1] = v0; + tc[2] = u0; tc[3] = v1; + tc[4] = u1; tc[5] = v1; + tc[6] = u1; tc[7] = v0; + + colors[3] = (byte) (colorInt & 0xff); + colors[2] = (byte) ((colorInt >> 8) & 0xff); + colors[1] = (byte) ((colorInt >> 16) & 0xff); + colors[0] = (byte) ((colorInt >> 24) & 0xff); + System.arraycopy(colors, 0, colors, 4, 4); + System.arraycopy(colors, 0, colors, 8, 4); + System.arraycopy(colors, 0, colors, 12, 4); + + short i0 = (short) (quadIdx * 4); + short i1 = (short) (i0 + 1); + short i2 = (short) (i0 + 2); + short i3 = (short) (i0 + 3); + + idx[0] = i0; idx[1] = i1; idx[2] = i2; + idx[3] = i0; idx[4] = i2; idx[5] = i3; + } + + public void appendPositions(FloatBuffer fb){ + float sx = x0+alignX; + float sy = y0-alignY; + float ex = sx+width; + float ey = sy-height; + // NOTE: subtracting the height here + // because OGL's Ortho origin is at lower-left + fb.put(sx).put(sy).put(0f); + fb.put(sx).put(ey).put(0f); + fb.put(ex).put(ey).put(0f); + fb.put(ex).put(sy).put(0f); + } + + public void appendPositions(ShortBuffer sb){ + final float x1 = getX1(); + final float y1 = getY1(); + short x = (short) x0; + short y = (short) y0; + short xpw = (short) (x1); + short ymh = (short) (y1); + + sb.put(x).put(y).put((short)0); + sb.put(x).put(ymh).put((short)0); + sb.put(xpw).put(ymh).put((short)0); + sb.put(xpw).put(y).put((short)0); + } + + public void appendTexCoords(FloatBuffer fb){ + // flip coords to be compatible with OGL + float v0 = 1 - this.v0; + float v1 = 1 - this.v1; + + // upper left + fb.put(u0).put(v0); + // lower left + fb.put(u0).put(v1); + // lower right + fb.put(u1).put(v1); + // upper right + fb.put(u1).put(v0); + } + + public void appendColors(ByteBuffer bb){ + bb.putInt(colorInt); + bb.putInt(colorInt); + bb.putInt(colorInt); + bb.putInt(colorInt); + } + + public void appendIndices(ShortBuffer sb, int quadIndex){ + // each quad has 4 indices + short v0 = (short) (quadIndex * 4); + short v1 = (short) (v0 + 1); + short v2 = (short) (v0 + 2); + short v3 = (short) (v0 + 3); + + sb.put(v0).put(v1).put(v2); + sb.put(v0).put(v2).put(v3); +// sb.put(new short[]{ v0, v1, v2, +// v0, v2, v3 }); + } + + + @Override + public String toString() { + return String.valueOf(c); + } + + void setAlignment(float alignX, float alignY) { + this.alignX = alignX; + this.alignY = alignY; + } + + float getAlignX() { + return alignX; + } + + float getAlignY() { + return alignY; + } + + boolean isLineFeed() { + return c == '\n'; + } + + boolean isTab() { + return c == '\t'; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/font/Letters.java b/jme3-core/src/main/java/com/jme3/font/Letters.java new file mode 100644 index 000000000..e8b8c8270 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/Letters.java @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.font; + +import com.jme3.font.BitmapFont.Align; +import com.jme3.font.BitmapFont.VAlign; +import com.jme3.font.ColorTags.Range; +import com.jme3.math.ColorRGBA; +import java.util.LinkedList; + +/** + * Manage and align LetterQuads + * @author YongHoon + */ +class Letters { + private final LetterQuad head; + private final LetterQuad tail; + private final BitmapFont font; + private LetterQuad current; + private StringBlock block; + private float totalWidth; + private float totalHeight; + private ColorTags colorTags = new ColorTags(); + private ColorRGBA baseColor = null; + private float baseAlpha = -1; + private String plainText; + + 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); + plainText = colorTags.getPlainText(); + + head.setNext(tail); + tail.setPrevious(head); + current = head; + if (text != null && plainText.length() > 0) { + LetterQuad l = head; + for (int i = 0; i < plainText.length(); i++) { + l = l.addNextCharacter(plainText.charAt(i)); + if (baseColor != null) { + // Give the letter a default color if + // one has been provided. + l.setColor( baseColor ); + } + } + } + + LinkedList ranges = colorTags.getTags(); + if (!ranges.isEmpty()) { + for (int i = 0; i < ranges.size()-1; i++) { + Range start = ranges.get(i); + Range end = ranges.get(i+1); + setColor(start.start, end.start, start.color); + } + Range end = ranges.getLast(); + setColor(end.start, plainText.length(), end.color); + } + + invalidate(); + } + + LetterQuad getHead() { + return head; + } + + LetterQuad getTail() { + return tail; + } + + void update() { + LetterQuad l = head; + int lineCount = 1; + BitmapCharacter ellipsis = font.getCharSet().getCharacter(block.getEllipsisChar()); + float ellipsisWidth = ellipsis!=null? ellipsis.getWidth()*getScale(): 0; + + while (!l.isTail()) { + if (l.isInvalid()) { + l.update(block); + + if (l.isInvalid(block)) { + switch (block.getLineWrapMode()) { + case Character: + lineWrap(l); + lineCount++; + break; + case Word: + if (!l.isBlank()) { + // search last blank character before this word + LetterQuad blank = l; + while (!blank.isBlank()) { + if (blank.isLineStart() || blank.isHead()) { + lineWrap(l); + lineCount++; + blank = null; + break; + } + blank = blank.getPrevious(); + } + if (blank != null) { + blank.setEndOfLine(); + lineCount++; + while (blank != l) { + blank = blank.getNext(); + blank.invalidate(); + blank.update(block); + } + } + } + break; + case NoWrap: + 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; + case Clip: + // Clip the character that falls out of bounds + l.clip(block); + + // Clear the rest up to the next line feed. + for( LetterQuad q = l.getNext(); !q.isTail() && !q.isLineFeed(); q = q.getNext() ) { + q.setBitmapChar(null); + q.update(block); + } + break; + } + } + } else if (current.isInvalid(block)) { + invalidate(current); + } + if (l.isEndOfLine()) { + lineCount++; + } + l = l.getNext(); + } + + align(); + block.setLineCount(lineCount); + rewind(); + } + + private void align() { + final Align alignment = block.getAlignment(); + final VAlign valignment = block.getVerticalAlignment(); + if (block.getTextBox() == null || (alignment == Align.Left && valignment == VAlign.Top)) + return; + LetterQuad cursor = tail.getPrevious(); + cursor.setEndOfLine(); + final float width = block.getTextBox().width; + final float height = block.getTextBox().height; + float lineWidth = 0; + float gapX = 0; + float gapY = 0; + validateSize(); + if (totalHeight < height) { // align vertically only for no overflow + switch (valignment) { + case Top: + gapY = 0; + break; + case Center: + gapY = (height-totalHeight)*0.5f; + break; + case Bottom: + gapY = height-totalHeight; + break; + } + } + while (!cursor.isHead()) { + if (cursor.isEndOfLine()) { + lineWidth = cursor.getX1()-block.getTextBox().x; + if (alignment == Align.Center) { + gapX = (width-lineWidth)/2; + } else if (alignment == Align.Right) { + gapX = width-lineWidth; + } else { + gapX = 0; + } + } + cursor.setAlignment(gapX, gapY); + cursor = cursor.getPrevious(); + } + } + + private void lineWrap(LetterQuad l) { + if (l.isHead() || l.isBlank()) + return; + l.getPrevious().setEndOfLine(); + l.invalidate(); + l.update(block); // TODO: update from l + } + + float getCharacterX0() { + return current.getX0(); + } + + float getCharacterY0() { + return current.getY0(); + } + + float getCharacterX1() { + return current.getX1(); + } + + float getCharacterY1() { + return current.getY1(); + } + + float getCharacterAlignX() { + return current.getAlignX(); + } + + float getCharacterAlignY() { + return current.getAlignY(); + } + + float getCharacterWidth() { + return current.getWidth(); + } + + float getCharacterHeight() { + return current.getHeight(); + } + + public boolean nextCharacter() { + if (current.isTail()) + return false; + current = current.getNext(); + return true; + } + + public int getCharacterSetPage() { + return current.getBitmapChar().getPage(); + } + + public LetterQuad getQuad() { + return current; + } + + public void rewind() { + current = head; + } + + public void invalidate() { + invalidate(head); + } + + public void invalidate(LetterQuad cursor) { + totalWidth = -1; + totalHeight = -1; + + while (!cursor.isTail() && !cursor.isInvalid()) { + cursor.invalidate(); + cursor = cursor.getNext(); + } + } + + float getScale() { + return block.getSize() / font.getCharSet().getRenderedSize(); + } + + public boolean isPrintable() { + return current.getBitmapChar() != null; + } + + float getTotalWidth() { + validateSize(); + return totalWidth; + } + + float getTotalHeight() { + validateSize(); + return totalHeight; + } + + void validateSize() { + if (totalWidth < 0) { + LetterQuad l = head; + while (!l.isTail()) { + totalWidth = Math.max(totalWidth, l.getX1()); + l = l.getNext(); + totalHeight = Math.max(totalHeight, -l.getY1()); + } + } + } + + /** + * @param start start index to set style. inclusive. + * @param end end index to set style. EXCLUSIVE. + * @param style + */ + void setStyle(int start, int end, int style) { + LetterQuad cursor = head.getNext(); + while (!cursor.isTail()) { + if (cursor.getIndex() >= start && cursor.getIndex() < end) { + cursor.setStyle(style); + } + cursor = cursor.getNext(); + } + } + + /** + * Sets the base color for all new letter quads and resets + * the color of existing letter quads. + */ + void setColor( ColorRGBA color ) { + baseColor = color; + colorTags.setBaseColor(color); + setColor( 0, block.getText().length(), color ); + } + + ColorRGBA getBaseColor() { + return baseColor; + } + + /** + * @param start start index to set style. inclusive. + * @param end end index to set style. EXCLUSIVE. + * @param color + */ + void setColor(int start, int end, ColorRGBA color) { + LetterQuad cursor = head.getNext(); + while (!cursor.isTail()) { + if (cursor.getIndex() >= start && cursor.getIndex() < end) { + cursor.setColor(color); + } + cursor = cursor.getNext(); + } + } + + float getBaseAlpha() { + return baseAlpha; + } + + void setBaseAlpha( float alpha ) { this.baseAlpha = alpha; + colorTags.setBaseAlpha(alpha); + + if (alpha == -1) { + alpha = baseColor != null ? baseColor.a : 1; + } + + // Forward the new alpha to the letter quads + LetterQuad cursor = head.getNext(); + while (!cursor.isTail()) { + cursor.setAlpha(alpha); + cursor = cursor.getNext(); + } + + // If the alpha was reset to "default", ie: -1 + // then the color tags are potentially reset and + // we need to reapply them. This has to be done + // second since it may override any alpha values + // set above... but you still need to do the above + // since non-color tagged text is treated differently + // even if part of a color tagged string. + if (baseAlpha == -1) { + 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); + } + } + + invalidate(); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/font/LineWrapMode.java b/jme3-core/src/main/java/com/jme3/font/LineWrapMode.java new file mode 100644 index 000000000..eef029b35 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/LineWrapMode.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.font; + +/** + * Line-wrap type for BitmapText + * @author YongHoon + */ +public enum LineWrapMode { + NoWrap, + Character, + Word, + Clip +} diff --git a/jme3-core/src/main/java/com/jme3/font/Rectangle.java b/jme3-core/src/main/java/com/jme3/font/Rectangle.java new file mode 100644 index 000000000..e3bb90dd8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/Rectangle.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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(); + } + } + + public String toString() { + return getClass().getSimpleName() + "[x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]"; + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/font/StringBlock.java b/jme3-core/src/main/java/com/jme3/font/StringBlock.java new file mode 100644 index 000000000..85fc9ab70 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/StringBlock.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java b/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java new file mode 100644 index 000000000..5ced607ff --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import com.jme3.input.controls.JoyAxisTrigger; +import com.jme3.input.controls.JoyButtonTrigger; +import java.util.*; + +/** + * A joystick represents a single joystick that is installed in the system. + * + * @author Kirill Vainer, Paul Speed + */ +public abstract class AbstractJoystick implements Joystick { + + private InputManager inputManager; + private JoyInput joyInput; + private int joyId; + private String name; + + private List axes = new ArrayList(); + private List buttons = new ArrayList(); + + /** + * Creates a new joystick instance. Only used internally. + */ + protected AbstractJoystick(InputManager inputManager, JoyInput joyInput, + int joyId, String name) { + this.inputManager = inputManager; + this.joyInput = joyInput; + this.joyId = joyId; + this.name = name; + } + + protected InputManager getInputManager() { + return inputManager; + } + + protected JoyInput getJoyInput() { + return joyInput; + } + + protected void addAxis( JoystickAxis axis ) { + axes.add(axis); + } + + protected void addButton( JoystickButton button ) { + buttons.add(button); + } + + /** + * Rumbles the joystick for the given amount/magnitude. + * + * @param amount The amount to rumble. Should be between 0 and 1. + */ + @Override + public void rumble(float amount){ + joyInput.setJoyRumble(joyId, amount); + } + + /** + * Assign the mapping name to receive events from the given button index + * on the joystick. + * + * @param mappingName The mapping to receive joystick button events. + * @param buttonId The button index. + * + * @see Joystick#getButtonCount() + * @deprecated Use JoystickButton.assignButton() instead. + */ + @Override + public void assignButton(String mappingName, int buttonId){ + if (buttonId < 0 || buttonId >= getButtonCount()) + throw new IllegalArgumentException(); + + inputManager.addMapping(mappingName, new JoyButtonTrigger(joyId, buttonId)); + } + + /** + * Assign the mappings to receive events from the given joystick axis. + * + * @param positiveMapping The mapping to receive events when the axis is negative + * @param negativeMapping The mapping to receive events when the axis is positive + * @param axisId The axis index. + * + * @see Joystick#getAxisCount() + * @deprecated Use JoystickAxis.assignAxis() instead. + */ + @Override + public void assignAxis(String positiveMapping, String negativeMapping, int axisId){ + + // For backwards compatibility + if( axisId == JoyInput.AXIS_POV_X ) { + axisId = getPovXAxis().getAxisId(); + } else if( axisId == JoyInput.AXIS_POV_Y ) { + axisId = getPovYAxis().getAxisId(); + } + + inputManager.addMapping(positiveMapping, new JoyAxisTrigger(joyId, axisId, false)); + inputManager.addMapping(negativeMapping, new JoyAxisTrigger(joyId, axisId, true)); + } + + @Override + public JoystickAxis getAxis(String logicalId) { + for( JoystickAxis axis : axes ) { + if( axis.getLogicalId().equals(logicalId) ) + return axis; + } + return null; + } + + /** + * Returns a read-only list of all joystick axes for this Joystick. + */ + @Override + public List getAxes() { + return Collections.unmodifiableList(axes); + } + + /** + * Returns the number of axes on this joystick. + * + * @return the number of axes on this joystick. + */ + @Override + public int getAxisCount() { + return axes.size(); + } + + @Override + public JoystickButton getButton(String logicalId) { + for( JoystickButton b : buttons ) { + if( b.getLogicalId().equals(logicalId) ) + return b; + } + return null; + } + + /** + * Returns a read-only list of all joystick buttons for this Joystick. + */ + @Override + public List getButtons() { + return Collections.unmodifiableList(buttons); + } + + /** + * Returns the number of buttons on this joystick. + * + * @return the number of buttons on this joystick. + */ + @Override + public int getButtonCount() { + return buttons.size(); + } + + /** + * Returns the name of this joystick. + * + * @return the name of this joystick. + */ + @Override + public String getName() { + return name; + } + + /** + * Returns the joyId of this joystick. + * + * @return the joyId of this joystick. + */ + @Override + public int getJoyId() { + return joyId; + } + + /** + * Gets the index number for the X axis on the joystick. + * + *

    E.g. for most gamepads, the left control stick X axis will be returned. + * + * @return The axis index for the X axis for this joystick. + * + * @see Joystick#assignAxis(java.lang.String, java.lang.String, int) + */ + @Override + public int getXAxisIndex(){ + return getXAxis().getAxisId(); + } + + /** + * Gets the index number for the Y axis on the joystick. + * + *

    E.g. for most gamepads, the left control stick Y axis will be returned. + * + * @return The axis index for the Y axis for this joystick. + * + * @see Joystick#assignAxis(java.lang.String, java.lang.String, int) + */ + @Override + public int getYAxisIndex(){ + return getYAxis().getAxisId(); + } + + @Override + public String toString(){ + return "Joystick[name=" + name + ", id=" + joyId + ", buttons=" + getButtonCount() + + ", axes=" + getAxisCount() + "]"; + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java new file mode 100644 index 000000000..b4bcf9d3f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java @@ -0,0 +1,942 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.input.controls.*; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.Control; +import java.io.IOException; + +/** + * A camera that follows a spatial and can turn around it by dragging the mouse + * @author nehon + */ +public class ChaseCamera implements ActionListener, AnalogListener, Control { + + protected Spatial target = null; + protected float minVerticalRotation = 0.00f; + protected float maxVerticalRotation = FastMath.PI / 2; + protected float minDistance = 1.0f; + protected float maxDistance = 40.0f; + protected float distance = 20; + protected float rotationSpeed = 1.0f; + protected float rotation = 0; + protected float trailingRotationInertia = 0.05f; + protected float zoomSensitivity = 2f; + protected float rotationSensitivity = 5f; + protected float chasingSensitivity = 5f; + protected float trailingSensitivity = 0.5f; + protected float vRotation = FastMath.PI / 6; + protected boolean smoothMotion = false; + protected boolean trailingEnabled = true; + protected float rotationLerpFactor = 0; + protected float trailingLerpFactor = 0; + protected boolean rotating = false; + protected boolean vRotating = false; + protected float targetRotation = rotation; + protected InputManager inputManager; + protected Vector3f initialUpVec; + protected float targetVRotation = vRotation; + protected float vRotationLerpFactor = 0; + protected float targetDistance = distance; + protected float distanceLerpFactor = 0; + protected boolean zooming = false; + protected boolean trailing = false; + protected boolean chasing = false; + protected boolean veryCloseRotation = true; + protected boolean canRotate; + protected float offsetDistance = 0.002f; + protected Vector3f prevPos; + protected boolean targetMoves = false; + protected boolean enabled = true; + protected Camera cam = null; + protected final Vector3f targetDir = new Vector3f(); + protected float previousTargetRotation; + protected final Vector3f pos = new Vector3f(); + protected Vector3f targetLocation = new Vector3f(0, 0, 0); + protected boolean dragToRotate = true; + protected Vector3f lookAtOffset = new Vector3f(0, 0, 0); + protected boolean leftClickRotate = true; + protected boolean rightClickRotate = true; + protected Vector3f temp = new Vector3f(0, 0, 0); + protected boolean invertYaxis = false; + protected boolean invertXaxis = false; + public final static String ChaseCamDown = "ChaseCamDown"; + public final static String ChaseCamUp = "ChaseCamUp"; + public final static String ChaseCamZoomIn = "ChaseCamZoomIn"; + public final static String ChaseCamZoomOut = "ChaseCamZoomOut"; + public final static String ChaseCamMoveLeft = "ChaseCamMoveLeft"; + public final static String ChaseCamMoveRight = "ChaseCamMoveRight"; + public final static String ChaseCamToggleRotate = "ChaseCamToggleRotate"; + protected boolean zoomin; + protected boolean hideCursorOnRotate = true; + + /** + * 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; + if (hideCursorOnRotate) { + inputManager.setCursorVisible(false); + } + } else { + canRotate = false; + if (hideCursorOnRotate) { + inputManager.setCursorVisible(true); + } + } + } + } + + } + + + 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); + } + + protected 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 + protected void rotateCamera(float value) { + if (!canRotate || !enabled) { + return; + } + rotating = true; + targetRotation += value * rotationSpeed; + + + } + + //move the camera toward or away the target + protected void zoomCamera(float value) { + if (!enabled) { + return; + } + + zooming = true; + targetDistance += value * zoomSensitivity; + if (targetDistance > maxDistance) { + targetDistance = maxDistance; + } + if (targetDistance < minDistance) { + targetDistance = minDistance; + } + if (veryCloseRotation) { + if ((targetVRotation < minVerticalRotation) && (targetDistance > (minDistance + 1.0f))) { + targetVRotation = minVerticalRotation; + } + } + } + + //rotate the camera around the target on the vertical plane + protected void vRotateCamera(float value) { + if (!canRotate || !enabled) { + return; + } + vRotating = true; + float lastGoodRot = targetVRotation; + targetVRotation += value * rotationSpeed; + if (targetVRotation > maxVerticalRotation) { + targetVRotation = lastGoodRot; + } + if (veryCloseRotation) { + if ((targetVRotation < minVerticalRotation) && (targetDistance > (minDistance + 1.0f))) { + targetVRotation = minVerticalRotation; + } else if (targetVRotation < -FastMath.DEG_TO_RAD * 90) { + targetVRotation = lastGoodRot; + } + } else { + if ((targetVRotation < minVerticalRotation)) { + targetVRotation = lastGoodRot; + } + } + } + + /** + * 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; + if (maxDistance < distance) { + zoomCamera(maxDistance - distance); + } + } + + /** + * 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) + */ + public void setMinDistance(float minDistance) { + this.minDistance = minDistance; + if (minDistance > distance) { + zoomCamera(distance - minDistance); + } + } + + /** + * clone this camera for a spatial + * @param spatial + * @return + */ + public Control cloneForSpatial(Spatial spatial) { + ChaseCamera cc = new ChaseCamera(cam, spatial, inputManager); + cc.setMaxDistance(getMaxDistance()); + cc.setMinDistance(getMinDistance()); + return cc; + } + + /** + * Sets the spacial for the camera control, should only be used internally + * @param spatial + */ + public void setSpatial(Spatial spatial) { + target = spatial; + if (spatial == null) { + return; + } + computePosition(); + prevPos = new Vector3f(target.getWorldTranslation()); + cam.setLocation(pos); + } + + /** + * update the camera control, should only be used internally + * @param tpf + */ + public void update(float tpf) { + updateCamera(tpf); + } + + /** + * renders the camera control, should only be used internally + * @param rm + * @param vp + */ + public void render(RenderManager rm, ViewPort vp) { + //nothing to render + } + + /** + * Write the camera + * @param ex the exporter + * @throws IOException + */ + public void write(JmeExporter ex) throws IOException { + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(maxDistance, "maxDistance", 40); + capsule.write(minDistance, "minDistance", 1); + } + + /** + * Read the camera + * @param im + * @throws IOException + */ + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + maxDistance = ic.readFloat("maxDistance", 40); + minDistance = ic.readFloat("minDistance", 1); + } + + /** + * @return The maximal vertical rotation angle in radian of the camera around the target + */ + public float getMaxVerticalRotation() { + return maxVerticalRotation; + } + + /** + * Sets the maximal vertical rotation angle in radian of the camera around the target. Default is Pi/2; + * @param maxVerticalRotation + */ + public void setMaxVerticalRotation(float maxVerticalRotation) { + this.maxVerticalRotation = maxVerticalRotation; + } + + /** + * + * @return The minimal vertical rotation angle in radian of the camera around the target + */ + public float getMinVerticalRotation() { + return minVerticalRotation; + } + + /** + * Sets the minimal vertical rotation angle in radian of the camera around the target default is 0; + * @param minHeight + */ + public void setMinVerticalRotation(float minHeight) { + this.minVerticalRotation = minHeight; + } + + /** + * @return True is smooth motion is enabled for this chase camera + */ + public boolean isSmoothMotion() { + return smoothMotion; + } + + /** + * Enables smooth motion for this chase camera + * @param smoothMotion + */ + public void setSmoothMotion(boolean smoothMotion) { + this.smoothMotion = smoothMotion; + } + + /** + * returns the chasing sensitivity + * @return + */ + public float getChasingSensitivity() { + return chasingSensitivity; + } + + /** + * + * Sets the chasing sensitivity, the lower the value the slower the camera will follow the target when it moves + * default is 5 + * Only has an effect if smoothMotion is set to true and trailing is enabled + * @param chasingSensitivity + */ + public void setChasingSensitivity(float chasingSensitivity) { + this.chasingSensitivity = chasingSensitivity; + } + + /** + * Returns the rotation sensitivity + * @return + */ + public float getRotationSensitivity() { + return rotationSensitivity; + } + + /** + * Sets the rotation sensitivity, the lower the value the slower the camera will rotates around the target when draging with the mouse + * default is 5, values over 5 should have no effect. + * If you want a significant slow down try values below 1. + * Only has an effect if smoothMotion is set to true + * @param rotationSensitivity + */ + public void setRotationSensitivity(float rotationSensitivity) { + this.rotationSensitivity = rotationSensitivity; + } + + /** + * returns true if the trailing is enabled + * @return + */ + public boolean isTrailingEnabled() { + return trailingEnabled; + } + + /** + * Enable the camera trailing : The camera smoothly go in the targets trail when it moves. + * Only has an effect if smoothMotion is set to true + * @param trailingEnabled + */ + public void setTrailingEnabled(boolean trailingEnabled) { + this.trailingEnabled = trailingEnabled; + } + + /** + * + * returns the trailing rotation inertia + * @return + */ + public float getTrailingRotationInertia() { + return trailingRotationInertia; + } + + /** + * Sets the trailing rotation inertia : default is 0.1. This prevent the camera to roughtly stop when the target stops moving + * before the camera reached the trail position. + * Only has an effect if smoothMotion is set to true and trailing is enabled + * @param trailingRotationInertia + */ + public void setTrailingRotationInertia(float trailingRotationInertia) { + this.trailingRotationInertia = trailingRotationInertia; + } + + /** + * returns the trailing sensitivity + * @return + */ + public float getTrailingSensitivity() { + return trailingSensitivity; + } + + /** + * Only has an effect if smoothMotion is set to true and trailing is enabled + * Sets the trailing sensitivity, the lower the value, the slower the camera will go in the target trail when it moves. + * default is 0.5; + * @param trailingSensitivity + */ + public void setTrailingSensitivity(float trailingSensitivity) { + this.trailingSensitivity = trailingSensitivity; + } + + /** + * returns the zoom sensitivity + * @return + */ + public float getZoomSensitivity() { + return zoomSensitivity; + } + + /** + * Sets the zoom sensitivity, the lower the value, the slower the camera will zoom in and out. + * default is 5. + * @param zoomSensitivity + */ + public void setZoomSensitivity(float zoomSensitivity) { + this.zoomSensitivity = zoomSensitivity; + } + + /** + * Returns the rotation speed when the mouse is moved. + * + * @return the rotation speed when the mouse is moved. + */ + public float getRotationSpeed() { + return rotationSpeed; + } + + /** + * Sets the rotate amount when user moves his mouse, the lower the value, + * the slower the camera will rotate. default is 1. + * + * @param rotationSpeed Rotation speed on mouse movement, default is 1. + */ + public void setRotationSpeed(float rotationSpeed) { + this.rotationSpeed = rotationSpeed; + } + + /** + * Sets the default distance at start of applicaiton + * @param defaultDistance + */ + public void setDefaultDistance(float defaultDistance) { + distance = defaultDistance; + targetDistance = distance; + } + + /** + * sets the default horizontal rotation in radian of the camera at start of the application + * @param angleInRad + */ + public void setDefaultHorizontalRotation(float angleInRad) { + rotation = angleInRad; + targetRotation = angleInRad; + } + + /** + * sets the default vertical rotation in radian of the camera at start of the application + * @param angleInRad + */ + public void setDefaultVerticalRotation(float angleInRad) { + vRotation = angleInRad; + targetVRotation = angleInRad; + } + + /** + * @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); + } + + /** + * @param rotateOnlyWhenClose When this flag is set to false the chase + * camera will always rotate around its spatial independently of their + * distance to one another. If set to true, the chase camera will only + * be allowed to rotated below the "horizon" when the distance is smaller + * than minDistance + 1.0f (when fully zoomed-in). + */ + public void setDownRotateOnCloseViewOnly(boolean rotateOnlyWhenClose) { + veryCloseRotation = rotateOnlyWhenClose; + } + + /** + * @return True if rotation below the vertical plane of the spatial tied + * to the camera is allowed only when zoomed in at minDistance + 1.0f. + * False if vertical rotation is always allowed. + */ + public boolean getDownRotateOnCloseViewOnly() { + return veryCloseRotation; + } + + /** + * return the current distance from the camera to the target + * @return + */ + public float getDistanceToTarget() { + return distance; + } + + /** + * returns the current horizontal rotation around the target in radians + * @return + */ + public float getHorizontalRotation() { + return rotation; + } + + /** + * returns the current vertical rotation around the target in radians. + * @return + */ + public float getVerticalRotation() { + return vRotation; + } + + /** + * returns the offset from the target's position where the camera looks at + * @return + */ + public Vector3f getLookAtOffset() { + return lookAtOffset; + } + + /** + * Sets the offset from the target's position where the camera looks at + * @param lookAtOffset + */ + public void setLookAtOffset(Vector3f lookAtOffset) { + this.lookAtOffset = lookAtOffset; + } + + /** + * Sets the up vector of the camera used for the lookAt on the target + * @param up + */ + public void setUpVector(Vector3f up) { + initialUpVec = up; + } + + /** + * Returns the up vector of the camera used for the lookAt on the target + * @return + */ + public Vector3f getUpVector() { + return initialUpVec; + } + + public boolean isHideCursorOnRotate() { + return hideCursorOnRotate; + } + + public void setHideCursorOnRotate(boolean hideCursorOnRotate) { + this.hideCursorOnRotate = hideCursorOnRotate; + } + + /** + * invert the vertical axis movement of the mouse + * @param invertYaxis + */ + public void setInvertVerticalAxis(boolean invertYaxis) { + this.invertYaxis = invertYaxis; + inputManager.deleteMapping(ChaseCamDown); + inputManager.deleteMapping(ChaseCamUp); + if (!invertYaxis) { + inputManager.addMapping(ChaseCamDown, new MouseAxisTrigger(MouseInput.AXIS_Y, true)); + inputManager.addMapping(ChaseCamUp, new MouseAxisTrigger(MouseInput.AXIS_Y, false)); + } else { + inputManager.addMapping(ChaseCamDown, new MouseAxisTrigger(MouseInput.AXIS_Y, false)); + inputManager.addMapping(ChaseCamUp, new MouseAxisTrigger(MouseInput.AXIS_Y, true)); + } + inputManager.addListener(this, ChaseCamDown, ChaseCamUp); + } + + /** + * invert the Horizontal axis movement of the mouse + * @param invertXaxis + */ + public void setInvertHorizontalAxis(boolean invertXaxis) { + this.invertXaxis = invertXaxis; + inputManager.deleteMapping(ChaseCamMoveLeft); + inputManager.deleteMapping(ChaseCamMoveRight); + if (!invertXaxis) { + inputManager.addMapping(ChaseCamMoveLeft, new MouseAxisTrigger(MouseInput.AXIS_X, true)); + inputManager.addMapping(ChaseCamMoveRight, new MouseAxisTrigger(MouseInput.AXIS_X, false)); + } else { + inputManager.addMapping(ChaseCamMoveLeft, new MouseAxisTrigger(MouseInput.AXIS_X, false)); + inputManager.addMapping(ChaseCamMoveRight, new MouseAxisTrigger(MouseInput.AXIS_X, true)); + } + inputManager.addListener(this, ChaseCamMoveLeft, ChaseCamMoveRight); + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java new file mode 100644 index 000000000..954451145 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import com.jme3.input.controls.JoyAxisTrigger; + +/** + * Default implementation of the JoystickAxis interface. + * + * @author Paul Speed + */ +public class DefaultJoystickAxis implements JoystickAxis { + + private InputManager inputManager; + private Joystick parent; + private int axisIndex; + private String name; + private String logicalId; + private boolean isAnalog; + private boolean isRelative; + private float deadZone; + + /** + * Creates a new joystick axis instance. Only used internally. + */ + public DefaultJoystickAxis(InputManager inputManager, Joystick parent, + int axisIndex, String name, String logicalId, + boolean isAnalog, boolean isRelative, float deadZone ) { + this.inputManager = inputManager; + this.parent = parent; + this.axisIndex = axisIndex; + this.name = name; + this.logicalId = logicalId; + this.isAnalog = isAnalog; + this.isRelative = isRelative; + this.deadZone = deadZone; + } + + /** + * Assign the mappings to receive events from the given joystick axis. + * + * @param positiveMapping The mapping to receive events when the axis is negative + * @param negativeMapping The mapping to receive events when the axis is positive + */ + public void assignAxis(String positiveMapping, String negativeMapping){ + inputManager.addMapping(positiveMapping, new JoyAxisTrigger(parent.getJoyId(), axisIndex, false)); + inputManager.addMapping(negativeMapping, new JoyAxisTrigger(parent.getJoyId(), axisIndex, true)); + } + + /** + * Returns the joystick to which this axis object belongs. + */ + public Joystick getJoystick() { + return parent; + } + + /** + * Returns the name of this joystick. + * + * @return the name of this joystick. + */ + public String getName() { + return name; + } + + /** + * Returns the logical identifier of this joystick axis. + * + * @return the logical identifier of this joystick. + */ + public String getLogicalId() { + return logicalId; + } + + /** + * Returns the axisId of this joystick axis. + * + * @return the axisId of this joystick axis. + */ + public int getAxisId() { + return axisIndex; + } + + /** + * Returns true if this is an analog axis, meaning the values + * are a continuous range instead of 1, 0, and -1. + */ + public boolean isAnalog() { + return isAnalog; + } + + /** + * Returns true if this axis presents relative values. + */ + public boolean isRelative() { + return isRelative; + } + + /** + * Returns the suggested dead zone for this axis. Values less than this + * can be safely ignored. + */ + public float getDeadZone() { + return deadZone; + } + + @Override + public String toString(){ + return "JoystickAxis[name=" + name + ", parent=" + parent.getName() + ", id=" + axisIndex + + ", logicalId=" + logicalId + ", isAnalog=" + isAnalog + + ", isRelative=" + isRelative + ", deadZone=" + deadZone + "]"; + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java new file mode 100644 index 000000000..827f33c6e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import com.jme3.input.controls.JoyButtonTrigger; + +/** + * Default implementation of the JoystickButton interface. + * + * @author Paul Speed + */ +public class DefaultJoystickButton implements JoystickButton { + + private InputManager inputManager; + private Joystick parent; + private int buttonIndex; + private String name; + private String logicalId; + + public DefaultJoystickButton( InputManager inputManager, Joystick parent, int buttonIndex, + String name, String logicalId ) { + this.inputManager = inputManager; + this.parent = parent; + this.buttonIndex = buttonIndex; + this.name = name; + this.logicalId = logicalId; + } + + /** + * Assign the mapping name to receive events from the given button index + * on the joystick. + * + * @param mappingName The mapping to receive joystick button events. + */ + public void assignButton(String mappingName) { + inputManager.addMapping(mappingName, new JoyButtonTrigger(parent.getJoyId(), buttonIndex)); + } + + /** + * Returns the joystick to which this axis object belongs. + */ + public Joystick getJoystick() { + return parent; + } + + /** + * Returns the name of this joystick. + * + * @return the name of this joystick. + */ + public String getName() { + return name; + } + + /** + * Returns the logical identifier of this joystick axis. + * + * @return the logical identifier of this joystick. + */ + public String getLogicalId() { + return logicalId; + } + + /** + * Returns the unique buttonId of this joystick axis within a given + * InputManager context. + * + * @return the buttonId of this joystick axis. + */ + public int getButtonId() { + return buttonIndex; + } + + @Override + public String toString(){ + return "JoystickButton[name=" + getName() + ", parent=" + parent.getName() + ", id=" + getButtonId() + + ", logicalId=" + getLogicalId() + "]"; + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java new file mode 100644 index 000000000..04b323f95 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import com.jme3.collision.MotionAllowedListener; +import com.jme3.input.controls.*; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; + +/** + * A first person view camera controller. + * After creation, you must register the camera controller with the + * dispatcher using #registerWithDispatcher(). + * + * Controls: + * - Move the mouse to rotate the camera + * - Mouse wheel for zooming in or out + * - WASD keys for moving forward/backward and strafing + * - QZ keys raise or lower the camera + */ +public class FlyByCamera implements AnalogListener, ActionListener { + + private static String[] mappings = new String[]{ + "FLYCAM_Left", + "FLYCAM_Right", + "FLYCAM_Up", + "FLYCAM_Down", + + "FLYCAM_StrafeLeft", + "FLYCAM_StrafeRight", + "FLYCAM_Forward", + "FLYCAM_Backward", + + "FLYCAM_ZoomIn", + "FLYCAM_ZoomOut", + "FLYCAM_RotateDrag", + + "FLYCAM_Rise", + "FLYCAM_Lower", + + "FLYCAM_InvertY" + }; + + protected Camera cam; + protected Vector3f initialUpVec; + protected float rotationSpeed = 1f; + protected float moveSpeed = 3f; + protected float zoomSpeed = 1f; + protected MotionAllowedListener motionAllowed = null; + protected boolean enabled = true; + protected boolean dragToRotate = false; + protected boolean canRotate = false; + protected boolean invertY = 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; + } + + /** + * Gets the move speed. The speed is given in world units per second. + * @return moveSpeed + */ + public float getMoveSpeed(){ + return moveSpeed; + } + + /** + * Sets the rotation speed. + * @param rotationSpeed + */ + public void setRotationSpeed(float rotationSpeed){ + this.rotationSpeed = rotationSpeed; + } + + /** + * Gets the move speed. The speed is given in world units per second. + * @return rotationSpeed + */ + public float getRotationSpeed(){ + return rotationSpeed; + } + + /** + * Sets the zoom speed. + * @param zoomSpeed + */ + public void setZoomSpeed(float zoomSpeed) { + this.zoomSpeed = zoomSpeed; + } + + /** + * Gets the zoom speed. The speed is a multiplier to increase/decrease + * the zoom rate. + * @return zoomSpeed + */ + public float getZoomSpeed() { + return zoomSpeed; + } + + /** + * @param enable If false, the camera will ignore input. + */ + public void setEnabled(boolean enable){ + if (enabled && !enable){ + if (inputManager!= null && (!dragToRotate || (dragToRotate && canRotate))){ + inputManager.setCursorVisible(true); + } + } + enabled = enable; + } + + /** + * @return If enabled + * @see FlyByCamera#setEnabled(boolean) + */ + public boolean isEnabled(){ + return enabled; + } + + /** + * @return If drag to rotate feature is enabled. + * + * @see FlyByCamera#setDragToRotate(boolean) + */ + public boolean isDragToRotate() { + return dragToRotate; + } + + /** + * Set if drag to rotate mode is enabled. + * + * When true, the user must hold the mouse button + * and drag over the screen to rotate the camera, and the cursor is + * visible until dragged. Otherwise, the cursor is invisible at all times + * and holding the mouse button is not needed to rotate the camera. + * This feature is disabled by default. + * + * @param dragToRotate True if drag to rotate mode is enabled. + */ + public void setDragToRotate(boolean dragToRotate) { + this.dragToRotate = dragToRotate; + if (inputManager != null) { + inputManager.setCursorVisible(dragToRotate); + } + } + + /** + * Registers the FlyByCamera to receive input events from the provided + * Dispatcher. + * @param inputManager + */ + public void registerWithInput(InputManager inputManager){ + this.inputManager = inputManager; + + // both mouse and button - rotation of cam + inputManager.addMapping("FLYCAM_Left", new MouseAxisTrigger(MouseInput.AXIS_X, true), + new KeyTrigger(KeyInput.KEY_LEFT)); + + inputManager.addMapping("FLYCAM_Right", new MouseAxisTrigger(MouseInput.AXIS_X, false), + new KeyTrigger(KeyInput.KEY_RIGHT)); + + inputManager.addMapping("FLYCAM_Up", new MouseAxisTrigger(MouseInput.AXIS_Y, false), + new KeyTrigger(KeyInput.KEY_UP)); + + inputManager.addMapping("FLYCAM_Down", new MouseAxisTrigger(MouseInput.AXIS_Y, true), + new KeyTrigger(KeyInput.KEY_DOWN)); + + // mouse only - zoom in/out with wheel, and rotate drag + inputManager.addMapping("FLYCAM_ZoomIn", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); + inputManager.addMapping("FLYCAM_ZoomOut", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); + inputManager.addMapping("FLYCAM_RotateDrag", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + + // keyboard only WASD for movement and WZ for rise/lower height + inputManager.addMapping("FLYCAM_StrafeLeft", new KeyTrigger(KeyInput.KEY_A)); + inputManager.addMapping("FLYCAM_StrafeRight", new KeyTrigger(KeyInput.KEY_D)); + inputManager.addMapping("FLYCAM_Forward", new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping("FLYCAM_Backward", new KeyTrigger(KeyInput.KEY_S)); + inputManager.addMapping("FLYCAM_Rise", new KeyTrigger(KeyInput.KEY_Q)); + inputManager.addMapping("FLYCAM_Lower", new KeyTrigger(KeyInput.KEY_Z)); + + inputManager.addListener(this, mappings); + inputManager.setCursorVisible(dragToRotate || !isEnabled()); + + Joystick[] joysticks = inputManager.getJoysticks(); + if (joysticks != null && joysticks.length > 0){ + for (Joystick j : joysticks) { + mapJoystick(j); + } + } + } + + protected void mapJoystick( Joystick joystick ) { + + // Map it differently if there are Z axis + if( joystick.getAxis( JoystickAxis.Z_ROTATION ) != null && joystick.getAxis( JoystickAxis.Z_AXIS ) != null ) { + + // Make the left stick move + joystick.getXAxis().assignAxis( "FLYCAM_StrafeRight", "FLYCAM_StrafeLeft" ); + joystick.getYAxis().assignAxis( "FLYCAM_Backward", "FLYCAM_Forward" ); + + // And the right stick control the camera + joystick.getAxis( JoystickAxis.Z_ROTATION ).assignAxis( "FLYCAM_Down", "FLYCAM_Up" ); + joystick.getAxis( JoystickAxis.Z_AXIS ).assignAxis( "FLYCAM_Right", "FLYCAM_Left" ); + + // And let the dpad be up and down + joystick.getPovYAxis().assignAxis("FLYCAM_Rise", "FLYCAM_Lower"); + + if( joystick.getButton( "Button 8" ) != null ) { + // Let the stanard select button be the y invert toggle + joystick.getButton( "Button 8" ).assignButton( "FLYCAM_InvertY" ); + } + + } else { + joystick.getPovXAxis().assignAxis("FLYCAM_StrafeRight", "FLYCAM_StrafeLeft"); + joystick.getPovYAxis().assignAxis("FLYCAM_Forward", "FLYCAM_Backward"); + joystick.getXAxis().assignAxis("FLYCAM_Right", "FLYCAM_Left"); + joystick.getYAxis().assignAxis("FLYCAM_Down", "FLYCAM_Up"); + } + } + + /** + * Registers the FlyByCamera to receive input events from the provided + * Dispatcher. + * @param inputManager + */ + public void unregisterInput(){ + + if (inputManager == null) { + return; + } + + for (String s : mappings) { + if (inputManager.hasMapping(s)) { + inputManager.deleteMapping( s ); + } + } + + inputManager.removeListener(this); + inputManager.setCursorVisible(!dragToRotate); + + Joystick[] joysticks = inputManager.getJoysticks(); + if (joysticks != null && joysticks.length > 0){ + Joystick joystick = joysticks[0]; + + // No way to unassing axis + } + } + + protected void rotateCamera(float value, Vector3f axis){ + if (dragToRotate){ + if (canRotate){ +// value = -value; + }else{ + return; + } + } + + Matrix3f mat = new Matrix3f(); + mat.fromAngleNormalAxis(rotationSpeed * value, axis); + + Vector3f up = cam.getUp(); + Vector3f left = cam.getLeft(); + Vector3f dir = cam.getDirection(); + + mat.mult(up, up); + mat.mult(left, left); + mat.mult(dir, dir); + + Quaternion q = new Quaternion(); + q.fromAxes(left, up, dir); + q.normalizeLocal(); + + 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 * zoomSpeed; + + 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 * (invertY ? -1 : 1), cam.getLeft()); + }else if (name.equals("FLYCAM_Down")){ + rotateCamera(value * (invertY ? -1 : 1), 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); + } else if (name.equals("FLYCAM_InvertY")) { + // Toggle on the up. + if( !value ) { + invertY = !invertY; + } + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/Input.java b/jme3-core/src/main/java/com/jme3/input/Input.java new file mode 100644 index 000000000..924065932 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/Input.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +/** + * 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/jme3-core/src/main/java/com/jme3/input/InputManager.java b/jme3-core/src/main/java/com/jme3/input/InputManager.java new file mode 100644 index 000000000..f9667a2d7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/InputManager.java @@ -0,0 +1,925 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import com.jme3.app.Application; +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.controls.*; +import com.jme3.input.event.*; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The 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 an InputManager 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. + *

    + * The input manager has two concepts, a {@link Trigger} and a mapping. + * A trigger represents a specific input trigger, such as a key button, + * or a mouse axis. A mapping represents a link onto one or several triggers, + * when the appropriate trigger is activated (e.g. a key is pressed), the + * mapping will be invoked. Any listeners registered to receive an event + * from the mapping will have an event raised. + *

    + * There are two types of events that {@link InputListener input listeners} + * can receive, one is {@link ActionListener#onAction(java.lang.String, boolean, float) action} + * events and another is {@link AnalogListener#onAnalog(java.lang.String, float, float) analog} + * events. + *

    + * onAction events are raised when the specific input + * activates or deactivates. For a digital input such as key press, the onAction() + * event will be raised with the isPressed argument equal to true, + * when the key is released, onAction is called again but this time + * with the isPressed argument set to false. + * For analog inputs, the onAction method will be called any time + * the input is non-zero, however an exception to this is for joystick axis inputs, + * which are only called when the input is above the {@link InputManager#setAxisDeadZone(float) dead zone}. + *

    + * onAnalog events are raised every frame while the input is activated. + * For digital inputs, every frame that the input is active will cause the + * onAnalog method to be called, the argument value + * argument will equal to the frame's time per frame (TPF) value but only + * for digital inputs. For analog inputs however, the value argument + * will equal the actual analog value. + */ +public class InputManager implements RawInputListener { + + private static final Logger logger = Logger.getLogger(InputManager.class.getName()); + private final KeyInput keys; + private final MouseInput mouse; + private final JoyInput joystick; + private final TouchInput touch; + private float frameTPF; + private long lastLastUpdateTime = 0; + private long lastUpdateTime = 0; + private long frameDelta = 0; + private long firstTime = 0; + private boolean eventsPermitted = false; + private boolean mouseVisible = true; + private boolean safeMode = false; + private float axisDeadZone = 0.05f; + private Vector2f cursorPos = new Vector2f(); + private Joystick[] joysticks; + private final IntMap> bindings = new IntMap>(); + private final HashMap mappings = new HashMap(); + private final IntMap pressedButtons = new IntMap(); + private final IntMap axisValues = new IntMap(); + private ArrayList rawListeners = new ArrayList(); + private RawInputListener[] rawListenerArray = null; + private ArrayList inputQueue = new ArrayList(); + + private 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. + * + *

    This should only be called internally in {@link Application}. + * + * @param mouse + * @param keys + * @param joystick + * @param touch + * @throws IllegalArgumentException If either mouseInput or keyInput are null. + */ + public InputManager(MouseInput mouse, KeyInput keys, JoyInput joystick, TouchInput touch) { + if (keys == null || mouse == null) { + throw new IllegalArgumentException("Mouse or keyboard cannot be null"); + } + + this.keys = keys; + this.mouse = mouse; + this.joystick = joystick; + this.touch = touch; + + keys.setInputListener(this); + mouse.setInputListener(this); + if (joystick != null) { + joystick.setInputListener(this); + joysticks = joystick.loadJoysticks(this); + } + if (touch != null) { + touch.setInputListener(this); + } + + firstTime = keys.getInputTimeNanos(); + } + + private void invokeActions(int hash, boolean pressed) { + ArrayList 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); + } + + } + } + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void beginInput() { + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void endInput() { + } + + private void onJoyAxisEventQueued(JoyAxisEvent evt) { +// for (int i = 0; i < rawListeners.size(); i++){ +// rawListeners.get(i).onJoyAxisEvent(evt); +// } + + int joyId = evt.getJoyIndex(); + int axis = evt.getAxisIndex(); + float value = evt.getValue(); + if (value < axisDeadZone && value > -axisDeadZone) { + int hash1 = JoyAxisTrigger.joyAxisHash(joyId, axis, true); + int hash2 = JoyAxisTrigger.joyAxisHash(joyId, axis, false); + + Float val1 = axisValues.get(hash1); + Float val2 = axisValues.get(hash2); + + if (val1 != null && val1.floatValue() > axisDeadZone) { + invokeActions(hash1, false); + } + if (val2 != null && val2.floatValue() > axisDeadZone) { + invokeActions(hash2, false); + } + + axisValues.remove(hash1); + axisValues.remove(hash2); + + } else if (value < 0) { + int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, true); + int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, false); + + // Clear the reverse direction's actions in case we + // crossed center too quickly + Float otherVal = axisValues.get(otherHash); + if (otherVal != null && otherVal.floatValue() > axisDeadZone) { + invokeActions(otherHash, false); + } + + invokeAnalogsAndActions(hash, -value, true); + axisValues.put(hash, -value); + axisValues.remove(otherHash); + } else { + int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, false); + int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, true); + + // Clear the reverse direction's actions in case we + // crossed center too quickly + Float otherVal = axisValues.get(otherHash); + if (otherVal != null && otherVal.floatValue() > axisDeadZone) { + invokeActions(otherHash, false); + } + + invokeAnalogsAndActions(hash, value, true); + axisValues.put(hash, value); + axisValues.remove(otherHash); + } + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void onJoyAxisEvent(JoyAxisEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time."); + } + + inputQueue.add(evt); + } + + private void onJoyButtonEventQueued(JoyButtonEvent evt) { +// for (int i = 0; i < rawListeners.size(); i++){ +// rawListeners.get(i).onJoyButtonEvent(evt); +// } + + int hash = JoyButtonTrigger.joyButtonHash(evt.getJoyIndex(), evt.getButtonIndex()); + invokeActions(hash, evt.isPressed()); + invokeTimedActions(hash, evt.getTime(), evt.isPressed()); + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void onJoyButtonEvent(JoyButtonEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time."); + } + + inputQueue.add(evt); + } + + private void onMouseMotionEventQueued(MouseMotionEvent evt) { +// for (int i = 0; i < rawListeners.size(); i++){ +// rawListeners.get(i).onMouseMotionEvent(evt); +// } + + if (evt.getDX() != 0) { + float val = Math.abs(evt.getDX()) / 1024f; + invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_X, evt.getDX() < 0), val, false); + } + if (evt.getDY() != 0) { + float val = Math.abs(evt.getDY()) / 1024f; + invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_Y, evt.getDY() < 0), val, false); + } + if (evt.getDeltaWheel() != 0) { + float val = Math.abs(evt.getDeltaWheel()) / 100f; + invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_WHEEL, evt.getDeltaWheel() < 0), val, false); + } + } + + /** + * Sets the mouse cursor image or animation. + * Set cursor to null to show default system cursor. + * To hide the cursor completely, use {@link #setCursorVisible(boolean) }. + * + * @param jmeCursor The cursor to set, or null to reset to system cursor. + * + * @see JmeCursor + */ + public void setMouseCursor(JmeCursor jmeCursor) { + mouse.setNativeCursor(jmeCursor); + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void onMouseMotionEvent(MouseMotionEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time."); + } + + cursorPos.set(evt.getX(), evt.getY()); + inputQueue.add(evt); + } + + private void onMouseButtonEventQueued(MouseButtonEvent evt) { + int hash = MouseButtonTrigger.mouseButtonHash(evt.getButtonIndex()); + invokeActions(hash, evt.isPressed()); + invokeTimedActions(hash, evt.getTime(), evt.isPressed()); + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void onMouseButtonEvent(MouseButtonEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time."); + } + //updating cursor pos on click, so that non android touch events can properly update cursor position. + cursorPos.set(evt.getX(), evt.getY()); + inputQueue.add(evt); + } + + private void onKeyEventQueued(KeyInputEvent evt) { + if (evt.isRepeating()) { + return; // repeat events not used for bindings + } + + int hash = KeyTrigger.keyHash(evt.getKeyCode()); + invokeActions(hash, evt.isPressed()); + invokeTimedActions(hash, evt.getTime(), evt.isPressed()); + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void onKeyEvent(KeyInputEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("KeyInput has raised an event at an illegal time."); + } + + inputQueue.add(evt); + } + + /** + * Set the deadzone for joystick axes. + * + *

    {@link ActionListener#onAction(java.lang.String, boolean, float) } + * events will only be raised if the joystick axis value is greater than + * the deadZone. + * + * @param deadZone the deadzone for joystick axes. + */ + public void setAxisDeadZone(float deadZone) { + this.axisDeadZone = deadZone; + } + + /** + * Returns the deadzone for joystick axes. + * + * @return the deadzone for joystick axes. + */ + public float getAxisDeadZone() { + return axisDeadZone; + } + + /** + * Adds a new listener to receive events on the given mappings. + * + *

    The given InputListener will be registered to receive events + * on the specified mapping names. When a mapping raises an event, the + * listener will have its appropriate method invoked, either + * {@link ActionListener#onAction(java.lang.String, boolean, float) } + * or {@link AnalogListener#onAnalog(java.lang.String, float, float) } + * depending on which interface the listener implements. + * If the listener implements both interfaces, then it will receive the + * appropriate event for each method. + * + * @param listener The listener to register to receive input events. + * @param mappingNames The mapping names which the listener will receive + * events from. + * + * @see InputManager#removeListener(com.jme3.input.controls.InputListener) + */ + public void addListener(InputListener listener, String... mappingNames) { + for (String mappingName : mappingNames) { + Mapping mapping = mappings.get(mappingName); + if (mapping == null) { + mapping = new Mapping(mappingName); + mappings.put(mappingName, mapping); + } + if (!mapping.listeners.contains(listener)) { + mapping.listeners.add(listener); + } + } + } + + /** + * Removes a listener from receiving events. + * + *

    This will unregister the listener from any mappings that it + * was previously registered with via + * {@link InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[]) }. + * + * @param listener The listener to unregister. + * + * @see InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[]) + */ + public void removeListener(InputListener listener) { + for (Mapping mapping : mappings.values()) { + mapping.listeners.remove(listener); + } + } + + /** + * Create a new mapping to the given triggers. + * + *

    + * The given mapping will be assigned to the given triggers, when + * any of the triggers given raise an event, the listeners + * registered to the mappings will receive appropriate events. + * + * @param mappingName The mapping name to assign. + * @param triggers The triggers to which the mapping is to be registered. + * + * @see InputManager#deleteMapping(java.lang.String) + */ + public void addMapping(String mappingName, Trigger... triggers) { + Mapping mapping = mappings.get(mappingName); + if (mapping == null) { + mapping = new Mapping(mappingName); + mappings.put(mappingName, mapping); + } + + for (Trigger trigger : triggers) { + int hash = trigger.triggerHashCode(); + ArrayList 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); + } + } + } + + /** + * Returns true if this InputManager has a mapping registered + * for the given mappingName. + * + * @param mappingName The mapping name to check. + * + * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[]) + * @see InputManager#deleteMapping(java.lang.String) + */ + public boolean hasMapping(String mappingName) { + return mappings.containsKey(mappingName); + } + + /** + * Deletes a mapping from receiving trigger events. + * + *

    + * The given mapping will no longer be assigned to receive trigger + * events. + * + * @param mappingName The mapping name to unregister. + * + * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[]) + */ + public void deleteMapping(String mappingName) { + Mapping mapping = mappings.remove(mappingName); + if (mapping == null) { + //throw new IllegalArgumentException("Cannot find mapping: " + mappingName); + logger.log(Level.WARNING, "Cannot find mapping to be removed, skipping: {0}", mappingName); + return; + } + + 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); + } + } + + /** + * Deletes a specific trigger registered to a mapping. + * + *

    + * The given mapping will no longer receive events raised by the + * trigger. + * + * @param mappingName The mapping name to cease receiving events from the + * trigger. + * @param trigger The trigger to no longer invoke events on the mapping. + */ + public void deleteTrigger(String mappingName, Trigger trigger) { + Mapping mapping = mappings.get(mappingName); + if (mapping == null) { + throw new IllegalArgumentException("Cannot find mapping: " + mappingName); + } + + ArrayList maps = bindings.get(trigger.triggerHashCode()); + maps.remove(mapping); + + } + + /** + * Clears all the input mappings from this InputManager. + * Consequently, also clears all of the + * InputListeners as well. + */ + public void clearMappings() { + mappings.clear(); + bindings.clear(); + reset(); + } + + /** + * Do not use. + * Called to reset pressed keys or buttons when focus is restored. + */ + public void reset() { + pressedButtons.clear(); + axisValues.clear(); + } + + /** + * Returns whether the mouse cursor is visible or not. + * + *

    By default the cursor is visible. + * + * @return whether the mouse cursor is visible or not. + * + * @see InputManager#setCursorVisible(boolean) + */ + public boolean isCursorVisible() { + return mouseVisible; + } + + /** + * Set whether the mouse cursor should be visible or not. + * + * @param visible whether the mouse cursor should be visible or not. + */ + public void setCursorVisible(boolean visible) { + if (mouseVisible != visible) { + mouseVisible = visible; + mouse.setCursorVisible(mouseVisible); + } + } + + /** + * Returns the current cursor position. The position is relative to the + * bottom-left of the screen and is in pixels. + * + * @return the current cursor position + */ + public Vector2f getCursorPosition() { + return cursorPos; + } + + /** + * Returns an array of all joysticks installed on the system. + * + * @return an array of all joysticks installed on the system. + */ + public Joystick[] getJoysticks() { + return joysticks; + } + + /** + * Adds a {@link RawInputListener} to receive raw input events. + * + *

    + * Any raw input listeners registered to this InputManager + * will receive raw input events first, before they get handled + * by the InputManager itself. The listeners are + * each processed in the order they were added, e.g. FIFO. + *

    + * If a raw input listener has handled the event and does not wish + * other listeners down the list to process the event, it may set the + * {@link InputEvent#setConsumed() consumed flag} to indicate the + * event was consumed and shouldn't be processed any further. + * The listener may do this either at each of the event callbacks + * or at the {@link RawInputListener#endInput() } method. + * + * @param listener A listener to receive raw input events. + * + * @see RawInputListener + */ + public void addRawInputListener(RawInputListener listener) { + rawListeners.add(listener); + rawListenerArray = null; + } + + /** + * Removes a {@link RawInputListener} so that it no longer + * receives raw input events. + * + * @param listener The listener to cease receiving raw input events. + * + * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener) + */ + public void removeRawInputListener(RawInputListener listener) { + rawListeners.remove(listener); + rawListenerArray = null; + } + + /** + * Clears all {@link RawInputListener}s. + * + * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener) + */ + public void clearRawInputListeners() { + rawListeners.clear(); + rawListenerArray = null; + } + + private RawInputListener[] getRawListenerArray() { + if (rawListenerArray == null) + rawListenerArray = rawListeners.toArray(new RawInputListener[rawListeners.size()]); + return rawListenerArray; + } + + /** + * Enable simulation of mouse events. Used for touchscreen input only. + * + * @param value True to enable simulation of mouse events + */ + public void setSimulateMouse(boolean value) { + if (touch != null) { + touch.setSimulateMouse(value); + } + } + /** + * Returns state of simulation of mouse events. Used for touchscreen input only. + * + */ + public boolean getSimulateMouse() { + if (touch != null) { + return touch.getSimulateMouse(); + } else { + return false; + } + } + + /** + * Enable simulation of keyboard events. Used for touchscreen input only. + * + * @param value True to enable simulation of keyboard events + */ + public void setSimulateKeyboard(boolean value) { + if (touch != null) { + touch.setSimulateKeyboard(value); + } + } + + private void processQueue() { + int queueSize = inputQueue.size(); + RawInputListener[] array = getRawListenerArray(); + + for (RawInputListener listener : array) { + listener.beginInput(); + + for (int j = 0; j < queueSize; j++) { + InputEvent event = inputQueue.get(j); + if (event.isConsumed()) { + continue; + } + + if (event instanceof MouseMotionEvent) { + listener.onMouseMotionEvent((MouseMotionEvent) event); + } else if (event instanceof KeyInputEvent) { + listener.onKeyEvent((KeyInputEvent) event); + } else if (event instanceof MouseButtonEvent) { + listener.onMouseButtonEvent((MouseButtonEvent) event); + } else if (event instanceof JoyAxisEvent) { + listener.onJoyAxisEvent((JoyAxisEvent) event); + } else if (event instanceof JoyButtonEvent) { + listener.onJoyButtonEvent((JoyButtonEvent) event); + } else if (event instanceof TouchEvent) { + listener.onTouchEvent((TouchEvent) event); + } else { + assert false; + } + } + + listener.endInput(); + } + + for (int i = 0; i < queueSize; i++) { + InputEvent event = inputQueue.get(i); + if (event.isConsumed()) { + continue; + } + + if (event instanceof MouseMotionEvent) { + onMouseMotionEventQueued((MouseMotionEvent) event); + } else if (event instanceof KeyInputEvent) { + onKeyEventQueued((KeyInputEvent) event); + } else if (event instanceof MouseButtonEvent) { + onMouseButtonEventQueued((MouseButtonEvent) event); + } else if (event instanceof JoyAxisEvent) { + onJoyAxisEventQueued((JoyAxisEvent) event); + } else if (event instanceof JoyButtonEvent) { + onJoyButtonEventQueued((JoyButtonEvent) event); + } else if (event instanceof TouchEvent) { + onTouchEventQueued((TouchEvent) event); + } else { + assert false; + } + // larynx, 2011.06.10 - flag event as reusable because + // the android input uses a non-allocating ringbuffer which + // needs to know when the event is not anymore in inputQueue + // and therefor can be reused. + event.setConsumed(); + } + + inputQueue.clear(); + } + + /** + * Updates the InputManager. + * This will query current input devices and send + * appropriate events to registered listeners. + * + * @param tpf Time per frame value. + */ + public void update(float tpf) { + frameTPF = tpf; + + // Activate safemode if the TPF value is so small + // that rounding errors are inevitable + safeMode = tpf < 0.015f; + + long currentTime = keys.getInputTimeNanos(); + frameDelta = currentTime - lastUpdateTime; + + eventsPermitted = true; + + keys.update(); + mouse.update(); + if (joystick != null) { + joystick.update(); + } + if (touch != null) { + touch.update(); + } + + eventsPermitted = false; + + processQueue(); + invokeUpdateActions(); + + lastLastUpdateTime = lastUpdateTime; + lastUpdateTime = currentTime; + } + + /** + * Dispatches touch events to touch listeners + * @param evt The touch event to be dispatched to all onTouch listeners + */ + public void onTouchEventQueued(TouchEvent evt) { + ArrayList maps = bindings.get(TouchTrigger.touchHash(evt.getKeyCode())); + if (maps == null) { + return; + } + + int size = maps.size(); + for (int i = size - 1; i >= 0; i--) { + Mapping mapping = maps.get(i); + ArrayList listeners = mapping.listeners; + int listenerSize = listeners.size(); + for (int j = listenerSize - 1; j >= 0; j--) { + InputListener listener = listeners.get(j); + if (listener instanceof TouchListener) { + ((TouchListener) listener).onTouch(mapping.name, evt, frameTPF); + } + } + } + } + + /** + * Callback from RawInputListener. Do not use. + */ + @Override + public void onTouchEvent(TouchEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("TouchInput has raised an event at an illegal time."); + } + cursorPos.set(evt.getX(), evt.getY()); + inputQueue.add(evt); + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/JoyInput.java b/jme3-core/src/main/java/com/jme3/input/JoyInput.java new file mode 100644 index 000000000..0041b8694 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/JoyInput.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +/** + * A specific API for interfacing with joysticks or gaming controllers. + */ +public interface JoyInput extends Input { + + /** + * The X axis for POV (point of view hat). + */ + public static final int AXIS_POV_X = 254; + + /** + * The Y axis for POV (point of view hat). + */ + public static final int AXIS_POV_Y = 255; + + /** + * Causes the joystick at joyId index to rumble with + * the given amount. + * + * @param joyId The joystick index + * @param amount Rumble amount. Should be between 0 and 1. + */ + public void setJoyRumble(int joyId, float amount); + + /** + * Loads a list of joysticks from the system. + * + * @param inputManager The input manager requesting to load joysticks + * @return A list of joysticks that are installed. + */ + public Joystick[] loadJoysticks(InputManager inputManager); +} diff --git a/jme3-core/src/main/java/com/jme3/input/Joystick.java b/jme3-core/src/main/java/com/jme3/input/Joystick.java new file mode 100644 index 000000000..ced1d962a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/Joystick.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import java.util.List; + +/** + * A joystick represents a single joystick that is installed in the system. + * + * @author Paul Speed, Kirill Vainer + */ +public interface Joystick { + + /** + * Rumbles the joystick for the given amount/magnitude. + * + * @param amount The amount to rumble. Should be between 0 and 1. + */ + public void rumble(float amount); + + /** + * Assign the mapping name to receive events from the given button index + * on the joystick. + * + * @param mappingName The mapping to receive joystick button events. + * @param buttonId The button index. + * + * @see Joystick#getButtonCount() + * @deprecated Use JoystickButton.assignButton() instead. + */ + public void assignButton(String mappingName, int buttonId); + + /** + * Assign the mappings to receive events from the given joystick axis. + * + * @param positiveMapping The mapping to receive events when the axis is negative + * @param negativeMapping The mapping to receive events when the axis is positive + * @param axisId The axis index. + * + * @see Joystick#getAxisCount() + * @deprecated Use JoystickAxis.assignAxis() instead. + */ + public void assignAxis(String positiveMapping, String negativeMapping, int axisId); + + /** + * Returns the JoystickAxis with the specified logical ID. + * + * @param logicalId The id of the axis to search for as returned by JoystickAxis.getLogicalId(). + */ + public JoystickAxis getAxis(String logicalId); + + /** + * Returns a read-only list of all joystick axes for this Joystick. + */ + public List getAxes(); + + /** + * Returns the JoystickButton with the specified logical ID. + * + * @param logicalId The id of the axis to search for as returned by JoystickButton.getLogicalId(). + */ + public JoystickButton getButton(String logicalId); + + /** + * Returns a read-only list of all joystick buttons for this Joystick. + */ + public List getButtons(); + + /** + * Returns the X axis for this joystick. + * + *

    E.g. for most gamepads, the left control stick X axis will be returned. + * + * @see JoystickAxis#assignAxis(java.lang.String, java.lang.String) + */ + public JoystickAxis getXAxis(); + + /** + * Returns the Y axis for this joystick. + * + *

    E.g. for most gamepads, the left control stick Y axis will be returned. + * + * @see JoystickAxis#assignAxis(java.lang.String, java.lang.String) + */ + public JoystickAxis getYAxis(); + + /** + * Returns the POV X axis for this joystick. This is a convenience axis + * providing an x-axis subview of the HAT axis. + * + * @see JoystickAxis#assignAxis(java.lang.String, java.lang.String) + */ + public JoystickAxis getPovXAxis(); + + /** + * Returns the POV Y axis for this joystick. This is a convenience axis + * providing an y-axis subview of the HAT axis. + * + * @see JoystickAxis#assignAxis(java.lang.String, java.lang.String) + */ + public JoystickAxis getPovYAxis(); + + /** + * Gets the index number for the X axis on the joystick. + * + *

    E.g. for most gamepads, the left control stick X axis will be returned. + * + * @return The axis index for the X axis for this joystick. + * + * @see Joystick#assignAxis(java.lang.String, java.lang.String, int) + */ + public int getXAxisIndex(); + + /** + * Gets the index number for the Y axis on the joystick. + * + *

    E.g. for most gamepads, the left control stick Y axis will be returned. + * + * @return The axis index for the Y axis for this joystick. + * + * @see Joystick#assignAxis(java.lang.String, java.lang.String, int) + */ + public int getYAxisIndex(); + + /** + * Returns the number of axes on this joystick. + * + * @return the number of axes on this joystick. + */ + public int getAxisCount(); + + /** + * Returns the number of buttons on this joystick. + * + * @return the number of buttons on this joystick. + */ + public int getButtonCount(); + + /** + * Returns the name of this joystick. + * + * @return the name of this joystick. + */ + public String getName(); + + /** + * Returns the joyId of this joystick. + * + * @return the joyId of this joystick. + */ + public int getJoyId(); + +} diff --git a/jme3-core/src/main/java/com/jme3/input/JoystickAxis.java b/jme3-core/src/main/java/com/jme3/input/JoystickAxis.java new file mode 100644 index 000000000..c951ef321 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/JoystickAxis.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +/** + * Represents a single axis of a Joystick. + * + * @author Paul Speed + */ +public interface JoystickAxis { + + public static final String X_AXIS = "x"; + public static final String Y_AXIS = "y"; + public static final String Z_AXIS = "z"; + public static final String Z_ROTATION = "rz"; + + public static final String POV_X = "pov_x"; + public static final String POV_Y = "pov_y"; + + /** + * Assign the mappings to receive events from the given joystick axis. + * + * @param positiveMapping The mapping to receive events when the axis is negative + * @param negativeMapping The mapping to receive events when the axis is positive + */ + public void assignAxis(String positiveMapping, String negativeMapping); + + /** + * Returns the joystick to which this axis object belongs. + */ + public Joystick getJoystick(); + + /** + * Returns the name of this joystick. + * + * @return the name of this joystick. + */ + public String getName(); + + /** + * Returns the logical identifier of this joystick axis. + * + * @return the logical identifier of this joystick. + */ + public String getLogicalId(); + + /** + * Returns the unique axisId of this joystick axis within a given + * InputManager context. + * + * @return the axisId of this joystick axis. + */ + public int getAxisId(); + + /** + * Returns true if this is an analog axis, meaning the values + * are a continuous range instead of 1, 0, and -1. + */ + public boolean isAnalog(); + + /** + * Returns true if this axis presents relative values. + */ + public boolean isRelative(); + + /** + * Returns the suggested dead zone for this axis. Values less than this + * can be safely ignored. + */ + public float getDeadZone(); +} diff --git a/jme3-core/src/main/java/com/jme3/input/JoystickButton.java b/jme3-core/src/main/java/com/jme3/input/JoystickButton.java new file mode 100644 index 000000000..5e7d3706a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/JoystickButton.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +/** + * Represents a single button of a Joystick. + * + * @author Paul Speed + */ +public interface JoystickButton { + + public static final String BUTTON_0 = "0"; + public static final String BUTTON_1 = "1"; + public static final String BUTTON_2 = "2"; + public static final String BUTTON_3 = "3"; + public static final String BUTTON_4 = "4"; + public static final String BUTTON_5 = "5"; + public static final String BUTTON_6 = "6"; + public static final String BUTTON_7 = "7"; + public static final String BUTTON_8 = "8"; + public static final String BUTTON_9 = "9"; + public static final String BUTTON_10 = "10"; + public static final String BUTTON_11 = "11"; + + /** + * Assign the mapping name to receive events from the given button index + * on the joystick. + * + * @param mappingName The mapping to receive joystick button events. + */ + public void assignButton(String mappingName); + + /** + * Returns the joystick to which this axis object belongs. + */ + public Joystick getJoystick(); + + /** + * Returns the name of this joystick. + * + * @return the name of this joystick. + */ + public String getName(); + + /** + * Returns the logical identifier of this joystick axis. + * + * @return the logical identifier of this joystick. + */ + public String getLogicalId(); + + /** + * Returns the unique buttonId of this joystick axis within a given + * InputManager context. + * + * @return the buttonId of this joystick axis. + */ + public int getButtonId(); +} diff --git a/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java b/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java new file mode 100644 index 000000000..60a4e126f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Provides compatibility mapping to different joysticks + * that both report their name in a unique way and require + * remapping to achieve a proper default layout. + * + *

    All mappings MUST be defined before the joystick support + * has been initialized in the InputManager.

    + * + * @author Paul Speed + */ +public class JoystickCompatibilityMappings { + + private static final Logger logger = Logger.getLogger(JoystickCompatibilityMappings.class.getName()); + + // List of resource paths to check for the joystick-mapping.properties + // files. + private static String[] searchPaths = { "joystick-mapping.properties" }; + + private static Map> joystickMappings = new HashMap>(); + + static { + loadDefaultMappings(); + } + + protected static Map getMappings( String joystickName, boolean create ) { + Map result = joystickMappings.get(joystickName); + if( result == null && create ) { + result = new HashMap(); + joystickMappings.put(joystickName,result); + } + return result; + } + + /** + * Returns the remapped version of the axis/button name if there + * is a mapping for it otherwise it returns the original name. + */ + public static String remapComponent( String joystickName, String componentId ) { + Map map = getMappings(joystickName.trim(), false); + if( map == null ) + return componentId; + if( !map.containsKey(componentId) ) + return componentId; + return map.get(componentId); + } + + /** + * Returns a set of Joystick axis/button name remappings if they exist otherwise + * it returns an empty map. + */ + public static Map getJoystickMappings( String joystickName ) { + Map result = getMappings(joystickName, false); + if( result == null ) + return Collections.emptyMap(); + return Collections.unmodifiableMap(result); + } + + /** + * Adds a single Joystick axis or button remapping based on the + * joystick's name and axis/button name. The "remap" value will be + * used instead. + */ + public static void addMapping( String stickName, String sourceComponentId, String remapId ) { + logger.log(Level.FINE, "addMapping(" + stickName + ", " + sourceComponentId + ", " + remapId + ")" ); + getMappings(stickName, true).put( sourceComponentId, remapId ); + } + + /** + * Adds a preconfigured set of mappings in Properties object + * form where the names are dot notation "joystick"."axis/button" + * and the values are the remapped component name. This calls + * addMapping(stickName, sourceComponent, remap) for every property + * that it is able to parse. + */ + public static void addMappings( Properties p ) { + for( Map.Entry e : p.entrySet() ) { + String key = String.valueOf(e.getKey()).trim(); + + int split = key.indexOf( '.' ); + if( split < 0 ) { + logger.log(Level.WARNING, "Skipping mapping:{0}", e); + continue; + } + + String stick = key.substring(0, split).trim(); + String component = key.substring(split+1).trim(); + String value = String.valueOf(e.getValue()).trim(); + addMapping(stick, component, value); + } + } + + /** + * Loads a set of compatibility mappings from the property file + * specified by the given URL. + */ + public static void loadMappingProperties( URL u ) throws IOException { + logger.log(Level.FINE, "Loading mapping properties:{0}", u); + InputStream in = u.openStream(); + try { + Properties p = new Properties(); + p.load(in); + addMappings(p); + } finally { + in.close(); + } + } + + protected static void loadMappings( ClassLoader cl, String path ) throws IOException { + logger.log(Level.FINE, "Searching for mappings for path:{0}", path); + for( Enumeration en = cl.getResources(path); en.hasMoreElements(); ) { + URL u = en.nextElement(); + try { + loadMappingProperties(u); + } catch( IOException e ) { + logger.log(Level.SEVERE, "Error loading:" + u, e); + } + } + + } + + /** + * Loads the default compatibility mappings by looking for + * joystick-mapping.properties files on the classpath. + */ + protected static void loadDefaultMappings() { + for( String s : searchPaths ) { + try { + loadMappings(JoystickCompatibilityMappings.class.getClassLoader(), s); + } catch( IOException e ) { + logger.log(Level.SEVERE, "Error searching resource path:{0}", s); + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/KeyInput.java b/jme3-core/src/main/java/com/jme3/input/KeyInput.java new file mode 100644 index 000000000..f9191c6a7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/KeyInput.java @@ -0,0 +1,542 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +/** + * 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; + + /** + * Left "Windows" key on PC keyboards, left "Option" key on Mac keyboards. + */ + public static final int KEY_LMETA = 0xDB; + + /** + * Right "Windows" key on PC keyboards, right "Option" key on Mac keyboards. + */ + public static final int KEY_RMETA = 0xDC; + + public static final int KEY_APPS = 0xDD; + /** + * power key. + */ + public static final int KEY_POWER = 0xDE; + /** + * sleep key. + */ + public static final int KEY_SLEEP = 0xDF; + +} diff --git a/jme3-core/src/main/java/com/jme3/input/KeyNames.java b/jme3-core/src/main/java/com/jme3/input/KeyNames.java new file mode 100644 index 000000000..cca929d83 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/KeyNames.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import static com.jme3.input.KeyInput.*; + +public class KeyNames { + + private static final String[] KEY_NAMES = new String[0xFF]; + + static { + KEY_NAMES[KEY_0] = "0"; + KEY_NAMES[KEY_1] = "1"; + KEY_NAMES[KEY_2] = "2"; + KEY_NAMES[KEY_3] = "3"; + KEY_NAMES[KEY_4] = "4"; + KEY_NAMES[KEY_5] = "5"; + KEY_NAMES[KEY_6] = "6"; + KEY_NAMES[KEY_7] = "7"; + KEY_NAMES[KEY_8] = "8"; + KEY_NAMES[KEY_9] = "9"; + + KEY_NAMES[KEY_Q] = "Q"; + KEY_NAMES[KEY_W] = "W"; + KEY_NAMES[KEY_E] = "E"; + KEY_NAMES[KEY_R] = "R"; + KEY_NAMES[KEY_T] = "T"; + KEY_NAMES[KEY_Y] = "Y"; + KEY_NAMES[KEY_U] = "U"; + KEY_NAMES[KEY_I] = "I"; + KEY_NAMES[KEY_O] = "O"; + KEY_NAMES[KEY_P] = "P"; + KEY_NAMES[KEY_A] = "A"; + KEY_NAMES[KEY_S] = "S"; + KEY_NAMES[KEY_D] = "D"; + KEY_NAMES[KEY_F] = "F"; + KEY_NAMES[KEY_G] = "G"; + KEY_NAMES[KEY_H] = "H"; + KEY_NAMES[KEY_J] = "J"; + KEY_NAMES[KEY_K] = "K"; + KEY_NAMES[KEY_L] = "L"; + KEY_NAMES[KEY_Z] = "Z"; + KEY_NAMES[KEY_X] = "X"; + KEY_NAMES[KEY_C] = "C"; + KEY_NAMES[KEY_V] = "V"; + KEY_NAMES[KEY_B] = "B"; + KEY_NAMES[KEY_N] = "N"; + KEY_NAMES[KEY_M] = "M"; + + KEY_NAMES[KEY_F1] = "F1"; + KEY_NAMES[KEY_F2] = "F2"; + KEY_NAMES[KEY_F3] = "F3"; + KEY_NAMES[KEY_F4] = "F4"; + KEY_NAMES[KEY_F5] = "F5"; + KEY_NAMES[KEY_F6] = "F6"; + KEY_NAMES[KEY_F7] = "F7"; + KEY_NAMES[KEY_F8] = "F8"; + KEY_NAMES[KEY_F9] = "F9"; + KEY_NAMES[KEY_F10] = "F10"; + KEY_NAMES[KEY_F11] = "F11"; + KEY_NAMES[KEY_F12] = "F12"; + KEY_NAMES[KEY_F13] = "F13"; + KEY_NAMES[KEY_F14] = "F14"; + KEY_NAMES[KEY_F15] = "F15"; + + KEY_NAMES[KEY_NUMPAD0] = "Numpad 0"; + KEY_NAMES[KEY_NUMPAD1] = "Numpad 1"; + KEY_NAMES[KEY_NUMPAD2] = "Numpad 2"; + KEY_NAMES[KEY_NUMPAD3] = "Numpad 3"; + KEY_NAMES[KEY_NUMPAD4] = "Numpad 4"; + KEY_NAMES[KEY_NUMPAD5] = "Numpad 5"; + KEY_NAMES[KEY_NUMPAD6] = "Numpad 6"; + KEY_NAMES[KEY_NUMPAD7] = "Numpad 7"; + KEY_NAMES[KEY_NUMPAD8] = "Numpad 8"; + KEY_NAMES[KEY_NUMPAD9] = "Numpad 9"; + + KEY_NAMES[KEY_NUMPADEQUALS] = "Numpad ="; + KEY_NAMES[KEY_NUMPADENTER] = "Numpad Enter"; + KEY_NAMES[KEY_NUMPADCOMMA] = "Numpad ."; + KEY_NAMES[KEY_DIVIDE] = "Numpad /"; + + + KEY_NAMES[KEY_LMENU] = "Left Alt"; + KEY_NAMES[KEY_RMENU] = "Right Alt"; + + KEY_NAMES[KEY_LCONTROL] = "Left Ctrl"; + KEY_NAMES[KEY_RCONTROL] = "Right Ctrl"; + + KEY_NAMES[KEY_LSHIFT] = "Left Shift"; + KEY_NAMES[KEY_RSHIFT] = "Right Shift"; + + KEY_NAMES[KEY_LMETA] = "Left Option"; + KEY_NAMES[KEY_RMETA] = "Right Option"; + + KEY_NAMES[KEY_MINUS] = "-"; + KEY_NAMES[KEY_EQUALS] = "="; + KEY_NAMES[KEY_LBRACKET] = "["; + KEY_NAMES[KEY_RBRACKET] = "]"; + KEY_NAMES[KEY_SEMICOLON] = ";"; + KEY_NAMES[KEY_APOSTROPHE] = "'"; + KEY_NAMES[KEY_GRAVE] = "`"; + KEY_NAMES[KEY_BACKSLASH] = "\\"; + KEY_NAMES[KEY_COMMA] = ","; + KEY_NAMES[KEY_PERIOD] = "."; + KEY_NAMES[KEY_SLASH] = "/"; + KEY_NAMES[KEY_MULTIPLY] = "*"; + KEY_NAMES[KEY_ADD] = "+"; + KEY_NAMES[KEY_COLON] = ":"; + KEY_NAMES[KEY_UNDERLINE] = "_"; + KEY_NAMES[KEY_AT] = "@"; + + KEY_NAMES[KEY_APPS] = "Apps"; + KEY_NAMES[KEY_POWER] = "Power"; + KEY_NAMES[KEY_SLEEP] = "Sleep"; + + KEY_NAMES[KEY_STOP] = "Stop"; + KEY_NAMES[KEY_ESCAPE] = "Esc"; + KEY_NAMES[KEY_RETURN] = "Enter"; + KEY_NAMES[KEY_SPACE] = "Space"; + KEY_NAMES[KEY_BACK] = "Backspace"; + KEY_NAMES[KEY_TAB] = "Tab"; + + KEY_NAMES[KEY_SYSRQ] = "SysEq"; + KEY_NAMES[KEY_PAUSE] = "Pause"; + + KEY_NAMES[KEY_HOME] = "Home"; + KEY_NAMES[KEY_PGUP] = "Page Up"; + KEY_NAMES[KEY_PGDN] = "Page Down"; + KEY_NAMES[KEY_END] = "End"; + KEY_NAMES[KEY_INSERT] = "Insert"; + KEY_NAMES[KEY_DELETE] = "Delete"; + + KEY_NAMES[KEY_UP] = "Up"; + KEY_NAMES[KEY_LEFT] = "Left"; + KEY_NAMES[KEY_RIGHT] = "Right"; + KEY_NAMES[KEY_DOWN] = "Down"; + + KEY_NAMES[KEY_NUMLOCK] = "Num Lock"; + KEY_NAMES[KEY_CAPITAL] = "Caps Lock"; + KEY_NAMES[KEY_SCROLL] = "Scroll Lock"; + + KEY_NAMES[KEY_KANA] = "Kana"; + KEY_NAMES[KEY_CONVERT] = "Convert"; + KEY_NAMES[KEY_NOCONVERT] = "No Convert"; + KEY_NAMES[KEY_YEN] = "Yen"; + KEY_NAMES[KEY_CIRCUMFLEX] = "Circumflex"; + KEY_NAMES[KEY_KANJI] = "Kanji"; + KEY_NAMES[KEY_AX] = "Ax"; + KEY_NAMES[KEY_UNLABELED] = "Unlabeled"; + } + + public String getName(int keyId){ + return KEY_NAMES[keyId]; + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/MouseInput.java b/jme3-core/src/main/java/com/jme3/input/MouseInput.java new file mode 100644 index 000000000..d2c959c92 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/MouseInput.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import com.jme3.cursors.plugins.JmeCursor; + +/** + * A specific API for interfacing with the mouse. + */ +public interface MouseInput extends Input { + + /** + * Mouse X axis. + */ + public static final int AXIS_X = 0; + + /** + * Mouse Y axis. + */ + public static final int AXIS_Y = 1; + + /** + * Mouse wheel axis. + */ + public static final int AXIS_WHEEL = 2; + + /** + * Left mouse button. + */ + public static final int BUTTON_LEFT = 0; + + /** + * Right mouse button. + */ + public static final int BUTTON_RIGHT = 1; + + /** + * Middle mouse button. + */ + public static final int BUTTON_MIDDLE = 2; + + /** + * Set whether the mouse cursor should be visible or not. + * + * @param visible Whether the mouse cursor should be visible or not. + */ + public void setCursorVisible(boolean visible); + + /** + * Returns the number of buttons the mouse has. Typically 3 for most mice. + * + * @return the number of buttons the mouse has. + */ + public int getButtonCount(); + + /** + * Sets the cursor to use. + * @param cursor The cursor to use. + */ + public void setNativeCursor(JmeCursor cursor); +} diff --git a/jme3-core/src/main/java/com/jme3/input/RawInputListener.java b/jme3-core/src/main/java/com/jme3/input/RawInputListener.java new file mode 100644 index 000000000..6ba37ec2e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/RawInputListener.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import com.jme3.input.event.*; + +/** + * An interface used for receiving raw input from devices. + */ +public interface RawInputListener { + + /** + * Called before a batch of input will be sent to this + * RawInputListener. + */ + public void beginInput(); + + /** + * Called after a batch of input was sent to this + * RawInputListener. + * + * The listener should set the {@link InputEvent#setConsumed() consumed flag} + * on any events that have been consumed either at this call or previous calls. + */ + public void endInput(); + + /** + * Invoked on joystick axis events. + * + * @param evt + */ + public void onJoyAxisEvent(JoyAxisEvent evt); + + /** + * Invoked on joystick button presses. + * + * @param evt + */ + public void onJoyButtonEvent(JoyButtonEvent evt); + + /** + * Invoked on mouse movement/motion events. + * + * @param evt + */ + public void onMouseMotionEvent(MouseMotionEvent evt); + + /** + * Invoked on mouse button events. + * + * @param evt + */ + public void onMouseButtonEvent(MouseButtonEvent evt); + + /** + * Invoked on keyboard key press or release events. + * + * @param evt + */ + public void onKeyEvent(KeyInputEvent evt); + + + /** + * Invoked on touchscreen touch events. + * + * @param evt + */ + public void onTouchEvent(TouchEvent evt); + +} diff --git a/jme3-core/src/main/java/com/jme3/input/SensorJoystickAxis.java b/jme3-core/src/main/java/com/jme3/input/SensorJoystickAxis.java new file mode 100644 index 000000000..d508ac2a0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/SensorJoystickAxis.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +/** + * Represents a joystick axis based on an external sensor + * (ie. Android Device Orientation sensors) + * Sensor joystick axes can be calibrated to + * set the zero position dynamically + * + * @author iwgeric + */ +public interface SensorJoystickAxis { + public static String ORIENTATION_X = "Orientation_X"; + public static String ORIENTATION_Y = "Orientation_Y"; + public static String ORIENTATION_Z = "Orientation_Z"; + + + /** + * Calibrates the axis to the current value. Future axis values will be + * sent as a delta from the calibratation value. + */ + public void calibrateCenter(); + + /** + * Method to allow users to set the raw sensor value that represents + * the maximum joystick axis value. Values sent to InputManager are scaled + * using the maxRawValue. + * + * @param maxRawValue Raw sensor value that will be used to scale joystick axis value + */ + public void setMaxRawValue(float maxRawValue); + + /** + * Returns the current maximum raw sensor value that is being used to scale + * the joystick axis value. + * + * @return maxRawValue The current maximum raw sensor value used for scaling the joystick axis value + */ + public float getMaxRawValue(); +} diff --git a/jme3-core/src/main/java/com/jme3/input/SoftTextDialogInput.java b/jme3-core/src/main/java/com/jme3/input/SoftTextDialogInput.java new file mode 100644 index 000000000..884a75076 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/SoftTextDialogInput.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import com.jme3.input.controls.SoftTextDialogInputListener; + +public interface SoftTextDialogInput { + + public static int TEXT_ENTRY_DIALOG = 0; + public static int NUMERIC_ENTRY_DIALOG = 1; + public static int NUMERIC_KEYPAD_DIALOG = 2; + + public void requestDialog(int id, String title, String initialValue, SoftTextDialogInputListener listener); + +} diff --git a/jme3-core/src/main/java/com/jme3/input/TouchInput.java b/jme3-core/src/main/java/com/jme3/input/TouchInput.java new file mode 100644 index 000000000..38803c480 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/TouchInput.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +/** + * A specific API for interfacing with smartphone touch devices + */ +public interface TouchInput extends Input { + + /** + * No filter, get all events + */ + public static final int ALL = 0x00; + /** + * Home key + */ + public static final int KEYCODE_HOME = 0x03; + /** + * Escape key. + */ + public static final int KEYCODE_BACK = 0x04; + /** + * Context Menu key. + */ + public static final int KEYCODE_MENU = 0x52; + /** + * Search key. + */ + public static final int KEYCODE_SEARCH = 0x54; + /** + * Volume up key. + */ + public static final int KEYCODE_VOLUME_UP = 0x18; + /** + * Volume down key. + */ + public static final int KEYCODE_VOLUME_DOWN = 0x19; + + + /** + * Set if mouse events should be generated + * + * @param simulate if mouse events should be generated + */ + public void setSimulateMouse(boolean simulate); + + /** + * Get if mouse events are generated + * @deprecated Use {@link #isSimulateMouse() }. + */ + @Deprecated + public boolean getSimulateMouse(); + + /** + * @return true if mouse event simulation is enabled, false otherwise. + */ + public boolean isSimulateMouse(); + + /** + * Set if keyboard events should be generated + * + * @param simulate if keyboard events should be generated + */ + public void setSimulateKeyboard(boolean simulate); + + /** + * Set if historic android events should be transmitted, can be used to get better performance and less mem + * @see + * http://developer.android.com/reference/android/view/MotionEvent.html#getHistoricalX%28int,%20int%29 + * @param dontSendHistory turn of historic events if true, false else and default + */ + public void setOmitHistoricEvents(boolean dontSendHistory); + + /** + * Displays or hides the onscreen soft keyboard + * @param visible + */ + public void showVirtualKeyboard (final boolean visible); + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/input/controls/ActionListener.java b/jme3-core/src/main/java/com/jme3/input/controls/ActionListener.java new file mode 100644 index 000000000..d57ef2e90 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/ActionListener.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.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 { + + /** + * Called when an input to which this listener is registered to is invoked. + * + * @param name The name of the mapping that was invoked + * @param isPressed True if the action is "pressed", false otherwise + * @param tpf The time per frame value. + */ + public void onAction(String name, boolean isPressed, float tpf); +} diff --git a/jme3-core/src/main/java/com/jme3/input/controls/AnalogListener.java b/jme3-core/src/main/java/com/jme3/input/controls/AnalogListener.java new file mode 100644 index 000000000..4df7e6fbf --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/AnalogListener.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.controls; + +/** + * AnalogListener is used to receive events of inputs + * in analog format. + * + * @author Kirill Vainer + */ +public interface AnalogListener extends InputListener { + /** + * Called to notify the implementation that an analog event has occurred. + * + * The results of KeyTrigger and MouseButtonTrigger events will have tpf + * == value. + * + * @param name The name of the mapping that was invoked + * @param value Value of the axis, from 0 to 1. + * @param tpf The time per frame value. + */ + public void onAnalog(String name, float value, float tpf); +} diff --git a/jme3-core/src/main/java/com/jme3/input/controls/InputListener.java b/jme3-core/src/main/java/com/jme3/input/controls/InputListener.java new file mode 100644 index 000000000..4aae017ca --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/InputListener.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.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/jme3-core/src/main/java/com/jme3/input/controls/JoyAxisTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/JoyAxisTrigger.java new file mode 100644 index 000000000..846c207dc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/JoyAxisTrigger.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.controls; + +import com.jme3.input.Joystick; + +public class JoyAxisTrigger implements Trigger { + + private final int joyId, axisId; + private final boolean negative; + + /** + * Use {@link Joystick#assignAxis(java.lang.String, java.lang.String, int) } + * instead. + */ + public JoyAxisTrigger(int joyId, int axisId, boolean negative) { + this.joyId = joyId; + this.axisId = axisId; + this.negative = negative; + } + + public static int joyAxisHash(int joyId, int joyAxis, boolean negative){ + assert joyAxis >= 0 && joyAxis <= 255; + return (2048 * joyId) | (negative ? 1280 : 1024) | (joyAxis & 0xff); + } + + public int getAxisId() { + return axisId; + } + + public int getJoyId() { + return joyId; + } + + public boolean isNegative() { + return negative; + } + + public String getName() { + return "JoyAxis[joyId="+joyId+", axisId="+axisId+", neg="+negative+"]"; + } + + public int triggerHashCode() { + return joyAxisHash(joyId, axisId, negative); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/controls/JoyButtonTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/JoyButtonTrigger.java new file mode 100644 index 000000000..e1b2eb160 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/JoyButtonTrigger.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.controls; + +import com.jme3.input.Joystick; + +public class JoyButtonTrigger implements Trigger { + + private final int joyId, buttonId; + + /** + * Use {@link Joystick#assignButton(java.lang.String, int) } instead. + * + * @param joyId + * @param axisId + */ + public JoyButtonTrigger(int joyId, int axisId) { + this.joyId = joyId; + this.buttonId = axisId; + } + + public static int joyButtonHash(int joyId, int joyButton){ + assert joyButton >= 0 && joyButton <= 255; + return (2048 * joyId) | 1536 | (joyButton & 0xff); + } + + public int getAxisId() { + return buttonId; + } + + public int getJoyId() { + return joyId; + } + + public String getName() { + return "JoyButton[joyId="+joyId+", axisId="+buttonId+"]"; + } + + public int triggerHashCode() { + return joyButtonHash(joyId, buttonId); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/controls/KeyTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/KeyTrigger.java new file mode 100644 index 000000000..eaf10aa6a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/KeyTrigger.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.controls; + +import com.jme3.input.KeyInput; + +/** + * A KeyTrigger is used as a mapping to keyboard keys. + * + * @author Kirill Vainer + */ +public class KeyTrigger implements Trigger { + + private final int keyCode; + + /** + * Create a new KeyTrigger for the given keycode. + * + * @param keyCode the code for the key, see constants in {@link KeyInput}. + */ + public KeyTrigger(int keyCode){ + this.keyCode = keyCode; + } + + public String getName() { + return "KeyCode " + keyCode; + } + + public int getKeyCode(){ + return keyCode; + } + + public static int keyHash(int keyCode){ + assert keyCode >= 0 && keyCode <= 255; + return keyCode & 0xff; + } + + public int triggerHashCode() { + return keyHash(keyCode); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/controls/MouseAxisTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/MouseAxisTrigger.java new file mode 100644 index 000000000..72c231d1f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/MouseAxisTrigger.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.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; + + /** + * Create a new MouseAxisTrigger. + *

    + * @param mouseAxis Mouse axis. See AXIS_*** constants in {@link MouseInput} + * @param negative True if listen to negative axis events, false if + * listen to positive axis events. + */ + public MouseAxisTrigger(int mouseAxis, boolean negative){ + if (mouseAxis < 0 || mouseAxis > 2) + throw new IllegalArgumentException("Mouse Axis must be between 0 and 2"); + + this.mouseAxis = mouseAxis; + this.negative = negative; + } + + public int getMouseAxis(){ + return mouseAxis; + } + + public boolean isNegative() { + return negative; + } + + public String getName() { + String sign = negative ? "Negative" : "Positive"; + switch (mouseAxis){ + case MouseInput.AXIS_X: return "Mouse X Axis " + sign; + case MouseInput.AXIS_Y: return "Mouse Y Axis " + sign; + case MouseInput.AXIS_WHEEL: return "Mouse Wheel " + sign; + default: throw new AssertionError(); + } + } + + public static int mouseAxisHash(int mouseAxis, boolean negative){ + assert mouseAxis >= 0 && mouseAxis <= 255; + return (negative ? 768 : 512) | (mouseAxis & 0xff); + } + + public int triggerHashCode() { + return mouseAxisHash(mouseAxis, negative); + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/controls/MouseButtonTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/MouseButtonTrigger.java new file mode 100644 index 000000000..4badddf20 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/MouseButtonTrigger.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.controls; + +import com.jme3.input.MouseInput; + +/** + * 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; + + /** + * Create a new MouseButtonTrigger to receive mouse button events. + * + * @param mouseButton Mouse button index. See BUTTON_*** constants in + * {@link MouseInput}. + */ + public MouseButtonTrigger(int mouseButton) { + if (mouseButton < 0) + throw new IllegalArgumentException("Mouse Button cannot be negative"); + + this.mouseButton = mouseButton; + } + + public int getMouseButton() { + return mouseButton; + } + + public String getName() { + return "Mouse Button " + mouseButton; + } + + public static int mouseButtonHash(int mouseButton){ + assert mouseButton >= 0 && mouseButton <= 255; + return 256 | (mouseButton & 0xff); + } + + public int triggerHashCode() { + return mouseButtonHash(mouseButton); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/controls/SoftTextDialogInputListener.java b/jme3-core/src/main/java/com/jme3/input/controls/SoftTextDialogInputListener.java new file mode 100644 index 000000000..d1b7753d4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/SoftTextDialogInputListener.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.controls; + +/** + * + * @author potterec (aka iwgeric) + */ +public interface SoftTextDialogInputListener { + + public static int COMPLETE = 0; + public static int CANCEL = 1; + + public void onSoftText(int action, String text); +} diff --git a/jme3-core/src/main/java/com/jme3/input/controls/TouchListener.java b/jme3-core/src/main/java/com/jme3/input/controls/TouchListener.java new file mode 100644 index 000000000..dafc81884 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/TouchListener.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.controls; + +import com.jme3.input.event.TouchEvent; + +/** + * TouchListener is used to receive events of inputs from smartphone touch devices + * + * @author larynx + */ +public interface TouchListener extends InputListener { + /** + * @param name the name of the event + * @param event the touch event + * @param tpf how much time has passed since the last frame + */ + public void onTouch(String name, TouchEvent event, float tpf); +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/input/controls/TouchTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/TouchTrigger.java new file mode 100644 index 000000000..2592249be --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/TouchTrigger.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.controls; + +/** + * Class to trigger TouchEvents, keycode can be TouchInput.ALL(=0) or TouchInput.KEYCODE_* + * @author larynx + * + */ +public class TouchTrigger implements Trigger { + + private final int keyCode; + + /** + * Constructor + * @param keyCode can be zero to get all events or TouchInput.KEYCODE_* + */ + public TouchTrigger(int keyCode) { + super(); + this.keyCode = keyCode; + } + + @Override + public String getName() { + if (keyCode != 0) + return "TouchInput"; + else + return "TouchInput KeyCode " + keyCode; + } + + public static int touchHash(int keyCode){ + assert keyCode >= 0 && keyCode <= 255; + return 0xfedcba98 + keyCode; + } + + public int triggerHashCode() { + return touchHash(keyCode); + } + + public int getKeyCode(){ + return keyCode; + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/controls/Trigger.java b/jme3-core/src/main/java/com/jme3/input/controls/Trigger.java new file mode 100644 index 000000000..68f1487cc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/Trigger.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.controls; + +/** + * A trigger represents a physical input, such as a keyboard key, a mouse + * button, or joystick axis. + */ +public interface Trigger { + + /** + * @return A user friendly name for the trigger. + */ + public String getName(); + + /** + * Returns the hash code for the trigger. + * + * @return the hash code for the trigger. + */ + public int triggerHashCode(); +} diff --git a/jme3-core/src/main/java/com/jme3/input/controls/package.html b/jme3-core/src/main/java/com/jme3/input/controls/package.html new file mode 100644 index 000000000..3eb3d251d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/controls/package.html @@ -0,0 +1,19 @@ + + + + + + + + + +The com.jme3.input.controls package allows user code to listen +to input events regardless of the type of input used. +

    +Users will receive input in one of two forms, either +{@link com.jme3.input.controls.AnalogListener} (analog input) or +{@link com.jme3.input.controls.ActionListener} (digital/action input). + + + + diff --git a/jme3-core/src/main/java/com/jme3/input/dummy/DummyInput.java b/jme3-core/src/main/java/com/jme3/input/dummy/DummyInput.java new file mode 100644 index 000000000..6525ac365 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/dummy/DummyInput.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.dummy; + +import com.jme3.input.Input; +import com.jme3.input.RawInputListener; + +/** + * DummyInput as an implementation of Input that raises no + * input events. + * + * @author Kirill Vainer. + */ +public class DummyInput implements Input { + + protected boolean inited = false; + + public void initialize() { + if (inited) + throw new IllegalStateException("Input already initialized."); + + inited = true; + } + + public void update() { + if (!inited) + throw new IllegalStateException("Input not initialized."); + } + + public void destroy() { + if (!inited) + throw new IllegalStateException("Input not initialized."); + + inited = false; + } + + public boolean isInitialized() { + return inited; + } + + public void setInputListener(RawInputListener listener) { + } + + public long getInputTimeNanos() { + return System.currentTimeMillis() * 1000000; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/dummy/DummyKeyInput.java b/jme3-core/src/main/java/com/jme3/input/dummy/DummyKeyInput.java new file mode 100644 index 000000000..92ed94031 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/dummy/DummyKeyInput.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.dummy; + +import com.jme3.input.KeyInput; + +/** + * DummyKeyInput as an implementation of KeyInput that raises no + * input events. + * + * @author Kirill Vainer. + */ +public class DummyKeyInput extends DummyInput implements KeyInput { + + public int getKeyCount() { + if (!inited) + throw new IllegalStateException("Input not initialized."); + + return 0; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/dummy/DummyMouseInput.java b/jme3-core/src/main/java/com/jme3/input/dummy/DummyMouseInput.java new file mode 100644 index 000000000..497490e39 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/dummy/DummyMouseInput.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.dummy; + +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.MouseInput; + +/** + * DummyMouseInput as an implementation of MouseInput that raises no + * input events. + * + * @author Kirill Vainer. + */ +public class DummyMouseInput extends DummyInput implements MouseInput { + + public void setCursorVisible(boolean visible) { + if (!inited) + throw new IllegalStateException("Input not initialized."); + } + + public int getButtonCount() { + return 0; + } + + public void setNativeCursor(JmeCursor cursor) { + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/dummy/package.html b/jme3-core/src/main/java/com/jme3/input/dummy/package.html new file mode 100644 index 000000000..13f5c5507 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/dummy/package.html @@ -0,0 +1,14 @@ + + + + + + + + + +The com.jme3.input.dummy package provides "dummy" or "null" +implementations of the input interfaces. + + + diff --git a/jme3-core/src/main/java/com/jme3/input/event/InputEvent.java b/jme3-core/src/main/java/com/jme3/input/event/InputEvent.java new file mode 100644 index 000000000..761abc1aa --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/event/InputEvent.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.event; + +import com.jme3.input.Input; + +/** + * An abstract input event. + */ +public abstract class InputEvent { + + protected long time; + + + protected boolean consumed = false; + + /** + * The time when the event occurred. This is relative to + * {@link Input#getInputTimeNanos() }. + * + * @return time when the event occured + */ + public long getTime(){ + return time; + } + + /** + * Set the time when the event occurred. + * + * @param time time when the event occurred. + */ + public void setTime(long time){ + this.time = time; + } + + /** + * Returns true if the input event has been consumed, meaning it is no longer valid + * and should not be forwarded to input listeners. + * + * @return true if the input event has been consumed + */ + public boolean isConsumed() { + return consumed; + } + + /** + * Call to mark this input event as consumed, meaning it is no longer valid + * and should not be forwarded to input listeners. + */ + public void setConsumed() { + this.consumed = true; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/event/JoyAxisEvent.java b/jme3-core/src/main/java/com/jme3/input/event/JoyAxisEvent.java new file mode 100644 index 000000000..5ba56c43b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/event/JoyAxisEvent.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.event; + +import com.jme3.input.InputManager; +import com.jme3.input.Joystick; +import com.jme3.input.JoystickAxis; + +/** + * Joystick axis event. + * + * @author Kirill Vainer, Paul Speed + */ +public class JoyAxisEvent extends InputEvent { + + private JoystickAxis axis; + private float value; + + public JoyAxisEvent(JoystickAxis axis, float value) { + this.axis = axis; + this.value = value; + } + + /** + * Returns the JoystickAxis that triggered this event. + * + * @see JoystickAxis#assignAxis(java.lang.String, java.lang.String, int) + */ + public JoystickAxis getAxis() { + return axis; + } + + /** + * Returns the joystick axis index. + * + * @return joystick axis index. + * + * @see Joystick#assignAxis(java.lang.String, java.lang.String, int) + */ + public int getAxisIndex() { + return axis.getAxisId(); + } + + /** + * The joystick index. + * + * @return joystick index. + * + * @see InputManager#getJoysticks() + */ + public int getJoyIndex() { + return axis.getJoystick().getJoyId(); + } + + /** + * The value of the axis. + * + * @return value of the axis. + */ + public float getValue() { + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java b/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java new file mode 100644 index 000000000..4a2c701d7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.event; + +import com.jme3.input.Joystick; +import com.jme3.input.JoystickButton; + +/** + * Joystick button event. + * + * @author Kirill Vainer, Paul Speed + */ +public class JoyButtonEvent extends InputEvent { + + private JoystickButton button; + private boolean pressed; + + public JoyButtonEvent(JoystickButton button, boolean pressed) { + this.button = button; + this.pressed = pressed; + } + + /** + * Returns the JoystickButton that triggered this event. + * + * @see JoystickAxis#assignAxis(java.lang.String, java.lang.String, int) + */ + public JoystickButton getButton() { + return button; + } + + /** + * The button index. + * + * @return button index. + * + * @see Joystick#assignButton(java.lang.String, int) + */ + public int getButtonIndex() { + return button.getButtonId(); + } + + /** + * The joystick index. + * + * @return joystick index. + * + * @see com.jme3.input.InputManager#getJoysticks() + */ + public int getJoyIndex() { + return button.getJoystick().getJoyId(); + } + + /** + * Returns true if the event was a button press, + * returns false if the event was a button release. + * + * @return true if the event was a button press, + * false if the event was a button release. + */ + public boolean isPressed() { + return pressed; + } + + + +} diff --git a/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java b/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java new file mode 100644 index 000000000..a804be2f9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.event; + +import com.jme3.input.KeyInput; + +/** + * Keyboard key event. + * + * @author Kirill Vainer + */ +public class KeyInputEvent extends InputEvent { + + private int keyCode; + private char keyChar; + private boolean pressed; + private boolean repeating; + + public KeyInputEvent(int keyCode, char keyChar, boolean pressed, boolean repeating) { + this.keyCode = keyCode; + this.keyChar = keyChar; + this.pressed = pressed; + this.repeating = repeating; + } + + /** + * Returns the key character. Returns 0 if the key has no character. + * + * @return the key character. 0 if the key has no character. + */ + public char getKeyChar() { + return keyChar; + } + + /** + * The key code. + *

    + * See KEY_*** constants in {@link KeyInput}. + * + * @return key code. + */ + public int getKeyCode() { + return keyCode; + } + + /** + * Returns true if this event is key press, false is it was a key release. + * + * @return true if this event is key press, false is it was a key release. + */ + public boolean isPressed() { + return pressed; + } + + /** + * Returns true if this event is a repeat event. Not used anymore. + * + * @return true if this event is a repeat event + */ + public boolean isRepeating() { + return repeating; + } + + /** + * Returns true if this event is a key release, false if it was a key press. + * + * @return true if this event is a key release, false if it was a key press. + */ + public boolean isReleased() { + return !pressed; + } + + @Override + public String toString(){ + String str = "Key(CODE="+keyCode; + if (keyChar != '\0') + str = str + ", CHAR=" + keyChar; + + if (repeating){ + return str + ", REPEATING)"; + }else if (pressed){ + return str + ", PRESSED)"; + }else{ + return str + ", RELEASED)"; + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java b/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java new file mode 100644 index 000000000..104957b2e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.event; + +import com.jme3.input.MouseInput; + +/** + * Mouse button press/release event. + * + * @author Kirill Vainer + */ +public class MouseButtonEvent extends InputEvent { + + private int x; + private int y; + private int btnIndex; + private boolean pressed; + + public MouseButtonEvent(int btnIndex, boolean pressed, int x, int y) { + this.btnIndex = btnIndex; + this.pressed = pressed; + this.x = x; + this.y = y; + } + + /** + * Returns the mouse button index. + *

    + * See constants in {@link MouseInput}. + * + * @return the mouse button index. + */ + public int getButtonIndex() { + return btnIndex; + } + + /** + * Returns true if the mouse button was pressed, false if it was released. + * + * @return true if the mouse button was pressed, false if it was released. + */ + public boolean isPressed() { + return pressed; + } + + /** + * Returns true if the mouse button was released, false if it was pressed. + * + * @return true if the mouse button was released, false if it was pressed. + */ + public boolean isReleased() { + return !pressed; + } + + /** + * The X coordinate of the mouse when the event was generated. + * @return X coordinate of the mouse when the event was generated. + */ + public int getX() { + return x; + } + + /** + * The Y coordinate of the mouse when the event was generated. + * @return Y coordinate of the mouse when the event was generated. + */ + public int getY() { + return y; + } + + @Override + public String toString(){ + String str = "MouseButton(BTN="+btnIndex; + if (pressed){ + return str + ", PRESSED)"; + }else{ + return str + ", RELEASED)"; + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java b/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java new file mode 100644 index 000000000..141077f5e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.event; + +/** + * Mouse movement event. + *

    + * Movement events are only generated if the mouse is on-screen. + * + * @author Kirill Vainer + */ +public class MouseMotionEvent extends InputEvent { + + private int x, y, dx, dy, wheel, deltaWheel; + + public MouseMotionEvent(int x, int y, int dx, int dy, int wheel, int deltaWheel) { + this.x = x; + this.y = y; + this.dx = dx; + this.dy = dy; + this.wheel = wheel; + this.deltaWheel = deltaWheel; + } + + /** + * The change in wheel rotation. + * + * @return change in wheel rotation. + */ + public int getDeltaWheel() { + return deltaWheel; + } + + /** + * The change in X coordinate + * @return change in X coordinate + */ + public int getDX() { + return dx; + } + + /** + * The change in Y coordinate + * + * @return change in Y coordinate + */ + public int getDY() { + return dy; + } + + /** + * Current mouse wheel value + * @return Current mouse wheel value + */ + public int getWheel() { + return wheel; + } + + /** + * Current X coordinate + * @return Current X coordinate + */ + public int getX() { + return x; + } + + /** + * Current Y coordinate + * @return Current Y coordinate + */ + public int getY() { + return y; + } + + @Override + public String toString(){ + return "MouseMotion(X="+x+", Y="+y+", DX="+dx+", DY="+dy+ + ", Wheel="+wheel+", dWheel="+deltaWheel+")"; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/event/TouchEvent.java b/jme3-core/src/main/java/com/jme3/input/event/TouchEvent.java new file mode 100644 index 000000000..f8463d48c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/event/TouchEvent.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.event; + +/** + * TouchEvent represents a single event from multi-touch input devices + * @author larynx + */ +public class TouchEvent extends InputEvent { + + public enum Type { + + /** + * Touch down event, fields: posX, posY, pressure + */ + DOWN, + /** + * Move/Drag event, fields: posX, posY, deltaX, deltaY, pressure + */ + MOVE, + /** + * Touch up event, fields: posX, posY, pressure + */ + UP, + /** + * Virtual keyboard or hardware key event down, fields: keyCode, characters + */ + KEY_DOWN, + /** + * Virtual keyboard or hardware key event up, fields: keyCode, characters + */ + KEY_UP, + // Single finger gestures + FLING, + TAP, + DOUBLETAP, + LONGPRESSED, + /** + * Finger started hovering over the screen without touching + * Requires Android OS rev 14 or higher (Android 4.0 or higher) + */ + HOVER_START, + /** + * Finger moved while still hovering over the screen without touching + * Requires Android OS rev 14 or higher (Android 4.0 or higher) + */ + HOVER_MOVE, + /** + * Finger was pulled away from the screen or touched the screen + * Requires Android OS rev 14 or higher (Android 4.0 or higher) + */ + HOVER_END, + + // Two finger scale events + /** + * Two finger scale event start, fields: posX/posY = getFocusX/Y, scaleFactor, scaleSpan + */ + SCALE_START, + /** + * Two finger scale event, fields: posX/posY = getFocusX/Y, scaleFactor, scaleSpan + */ + SCALE_MOVE, + /** + * Two finger scale event end, fields: posX/posY = getFocusX/Y, scaleFactor, scaleSpan + */ + SCALE_END, + /** + * Scroll event + */ + SCROLL, + /** + * The user has performed a down MotionEvent and not performed a move or up yet. This event is commonly used to provide visual feedback to the user to let them know that their action has been recognized i.e. highlight an element. + */ + SHOWPRESS, + // Others + OUTSIDE, + IDLE + } + private Type type = Type.IDLE; + private int pointerId; + private float posX; + private float posY; + private float deltaX; + private float deltaY; + private float pressure; + + // Used only with KEY* events + private int keyCode; + private String characters; + // Used only with SCALE* events + private float scaleFactor; + private float scaleSpan; + private float deltaScaleSpan; + private boolean scaleSpanInProgress; + + public TouchEvent() { + set(Type.IDLE, 0f, 0f, 0f, 0f); + } + + public TouchEvent(Type type, float x, float y, float deltax, float deltay) { + set(type, x, y, deltax, deltay); + } + + public void set(Type type) { + set(type, 0f, 0f, 0f, 0f); + } + + public void set(Type type, float x, float y, float deltax, float deltay) { + this.type = type; + this.posX = x; + this.posY = y; + this.deltaX = deltax; + this.deltaY = deltay; + pointerId = 0; + pressure = 0; + keyCode = 0; + scaleFactor = 0; + scaleSpan = 0; + deltaScaleSpan = 0; + scaleSpanInProgress = false; + characters = ""; + consumed = false; + } + + /** + * Returns the type of touch event. + * + * @return the type of touch event. + */ + public Type getType() { + return type; + } + + public float getX() { + return posX; + } + + public float getY() { + return posY; + } + + public float getDeltaX() { + return deltaX; + } + + public float getDeltaY() { + return deltaY; + } + + public float getPressure() + { + return pressure; + } + + public void setPressure(float pressure) + { + this.pressure = pressure; + } + + public int getPointerId() + { + return pointerId; + } + + public void setPointerId(int pointerId) { + this.pointerId = pointerId; + } + + public int getKeyCode() { + return keyCode; + } + + public void setKeyCode(int keyCode) { + this.keyCode = keyCode; + } + + public String getCharacters() { + return characters; + } + + public void setCharacters(String characters) { + this.characters = characters; + } + + public float getScaleFactor() { + return scaleFactor; + } + + public void setScaleFactor(float scaleFactor) { + this.scaleFactor = scaleFactor; + } + + public float getScaleSpan() { + return scaleSpan; + } + + public void setScaleSpan(float scaleSpan) { + this.scaleSpan = scaleSpan; + } + + public float getDeltaScaleSpan() { + return deltaScaleSpan; + } + + public void setDeltaScaleSpan(float deltaScaleSpan) { + this.deltaScaleSpan = deltaScaleSpan; + } + + public boolean isScaleSpanInProgress() { + return scaleSpanInProgress; + } + + public void setScaleSpanInProgress(boolean scaleSpanInProgress) { + this.scaleSpanInProgress = scaleSpanInProgress; + } + + @Override + public String toString(){ + return "TouchEvent(PointerId="+pointerId+", Type="+type+ + ", X="+posX+", Y="+posY+", DX="+deltaX+", DY="+deltaY+ + ", ScaleSpan="+scaleSpan+", dScaleSpan="+deltaScaleSpan+")"; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/event/package.html b/jme3-core/src/main/java/com/jme3/input/event/package.html new file mode 100644 index 000000000..eaa96fb33 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/event/package.html @@ -0,0 +1,13 @@ + + + + + + + + + +The com.jme3.input.event package contains low-level input events. + + + diff --git a/jme3-core/src/main/java/com/jme3/input/package.html b/jme3-core/src/main/java/com/jme3/input/package.html new file mode 100644 index 000000000..998a22a4b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/package.html @@ -0,0 +1,38 @@ + + + + + + + + + +The com.jme3.input package is used for all input handling in +jMonkeyEngine. User code should use the {@link com.jme3.input.InputManager} to register +for and receive input events. The InputManager can be +retrieved for an application by using {@link com.jme3.app.Application#getInputManager()}. + +

    Usage

    + +

    +Using ActionListener:
    + +// Retrieve an input manager for the application "app"
    +InputManager inputManager = app.getInputManager();
    +
    +// Adds a new mapping "PrintHello" that will be invoked when the Return/Enter key is pressed
    +inputManager.addMapping("PrintHello", new KeyTrigger(KeyInput.KEY_RETURN));
    +// Adds a new ActionListener to get an event when enter is pressed.
    +inputManager.addListener(new ActionListener() {
    + public void onAction(String name, boolean isPressed, float tpf) {
    + // Only invoke the event when the mapping is "PrintHello"
    + // and isPressed is true, meaning it was a key press and not release.
    + if (name.equals("PrintHello") && isPressed){
    + System.out.println("Hello!");
    + }
    + }
    +}, "PrintHello");
    +
    + + + diff --git a/jme3-core/src/main/java/com/jme3/light/AmbientLight.java b/jme3-core/src/main/java/com/jme3/light/AmbientLight.java new file mode 100644 index 000000000..dc23ebb12 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/AmbientLight.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.light; + +import com.jme3.scene.Spatial; + +/** + * An ambient light adds a constant color to the scene. + *

    + * Ambient lights are unaffected by the surface normal, and are constant + * regardless of the model's location. The material's ambient color is + * multiplied by the ambient light color to get the final ambient color of + * an object. + * + * @author Kirill Vainer + */ +public class AmbientLight extends Light { + + @Override + public void computeLastDistance(Spatial owner) { + } + + @Override + public Type getType() { + return Type.Ambient; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java b/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java new file mode 100644 index 000000000..cb65258e7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.light; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.io.IOException; + +/** + * DirectionalLight is a light coming from a certain direction in world space. + * E.g sun or moon light. + *

    + * Directional lights have no specific position in the scene, they always + * come from their direction regardless of where an object is placed. + */ +public class DirectionalLight extends Light { + + protected Vector3f direction = new Vector3f(0f, -1f, 0f); + + @Override + public void computeLastDistance(Spatial owner) { + lastDistance = 0; // directional lights are always closest to their owner + } + + /** + * Returns the direction vector of the light. + * + * @return The direction vector of the light. + * + * @see DirectionalLight#setDirection(com.jme3.math.Vector3f) + */ + public Vector3f getDirection() { + return direction; + } + + /** + * Sets the direction of the light. + *

    + * Represents the direction the light is shining. + * (1, 0, 0) would represent light shining in the +X direction. + * + * @param dir the direction of the light. + */ + public void setDirection(Vector3f dir){ + direction.set(dir); + if (!direction.isUnitVector()) { + direction.normalizeLocal(); + } + } + + @Override + public Type getType() { + return Type.Directional; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(direction, "direction", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + direction = (Vector3f) ic.readSavable("direction", null); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/light/Light.java b/jme3-core/src/main/java/com/jme3/light/Light.java new file mode 100644 index 000000000..58e62cf34 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/Light.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.light; + +import com.jme3.export.*; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Spatial; +import java.io.IOException; + +/** + * Abstract class for representing a light source. + *

    + * All light source types have a color. + */ +public abstract class Light implements Savable, Cloneable { + + /** + * Describes the light type. + */ + public enum Type { + + /** + * Directional light + * + * @see DirectionalLight + */ + Directional(0), + + /** + * Point light + * + * @see PointLight + */ + Point(1), + + /** + * Spot light. + *

    + * Not supported by jMonkeyEngine + */ + Spot(2), + + /** + * Ambient light + * + * @see AmbientLight + */ + Ambient(3); + + private int typeId; + + Type(int type){ + this.typeId = type; + } + + /** + * Returns an index for the light type + * @return an index for the light type + */ + public int getId(){ + return typeId; + } + } + + protected ColorRGBA color = new ColorRGBA(1f,1f,1f,1f); + + /** + * Used in LightList for caching the distance + * to the owner spatial. Should be reset after the sorting. + */ + protected transient float lastDistance = -1; + + /** + * If light is disabled, it will not have any + */ + protected boolean enabled = true; + + /** + * The light name. + */ + protected String name; + + /** + * Returns the color of the light. + * + * @return The color of the light. + */ + public ColorRGBA getColor() { + return color; + } + + /** + * This method sets the light name. + * + * @param name the light name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Return the light name. + * + * @return the light name + */ + public String getName() { + return name; + } + + /* + public void setLastDistance(float lastDistance){ + this.lastDistance = lastDistance; + } + + public float getLastDistance(){ + return lastDistance; + } + */ + + /** + * Sets the light color. + * + * @param color the light color. + */ + public void setColor(ColorRGBA color){ + this.color.set(color); + } + + + /* + * Returns true if the light is enabled + * + * @return true if the light is enabled + * + * @see Light#setEnabled(boolean) + */ + /* + public boolean isEnabled() { + return enabled; + } + */ + + @Override + public Light clone(){ + try { + return (Light) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(color, "color", null); + oc.write(enabled, "enabled", true); + oc.write(name, "name", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + color = (ColorRGBA) ic.readSavable("color", null); + enabled = ic.readBoolean("enabled", true); + name = ic.readString("name", null); + } + + /** + * Used internally to compute the last distance value. + */ + protected abstract void computeLastDistance(Spatial owner); + + /** + * Returns the light type + * + * @return the light type + * + * @see Type + */ + public abstract Type getType(); + +} diff --git a/jme3-core/src/main/java/com/jme3/light/LightList.java b/jme3-core/src/main/java/com/jme3/light/LightList.java new file mode 100644 index 000000000..ebb97a2a8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/LightList.java @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.light; + +import com.jme3.export.*; +import com.jme3.scene.Spatial; +import com.jme3.util.SortUtil; +import java.io.IOException; +import java.util.*; + +/** + * LightList is used internally by {@link Spatial}s to manage + * lights that are attached to them. + * + * @author Kirill Vainer + */ +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(){ + } + + /** + * Creates a LightList for the given {@link Spatial}. + * + * @param owner The spatial owner + */ + public LightList(Spatial owner) { + listSize = 0; + list = new Light[DEFAULT_SIZE]; + distToOwner = new float[DEFAULT_SIZE]; + Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY); + this.owner = owner; + } + + /** + * Set the owner of the LightList. Only used for cloning. + * @param owner + */ + public void setOwner(Spatial owner){ + this.owner = owner; + } + + private void doubleSize(){ + Light[] temp = new Light[list.length * 2]; + float[] temp2 = new float[list.length * 2]; + System.arraycopy(list, 0, temp, 0, list.length); + System.arraycopy(distToOwner, 0, temp2, 0, list.length); + list = temp; + distToOwner = temp2; + } + + /** + * Adds a light to the list. List size is doubled if there is no room. + * + * @param l + * The light to add. + */ + public void add(Light l) { + if (listSize == list.length) { + doubleSize(); + } + list[listSize] = l; + distToOwner[listSize++] = Float.NEGATIVE_INFINITY; + } + + /** + * Remove the light at the given index. + * + * @param index + */ + public void remove(int index){ + if (index >= listSize || index < 0) + throw new IndexOutOfBoundsException(); + + listSize --; + if (index == listSize){ + list[listSize] = null; + return; + } + + for (int i = index; i < listSize; i++){ + list[i] = list[i+1]; + } + list[listSize] = null; + } + + /** + * Removes the given light from the LightList. + * + * @param l the light to remove + */ + public void remove(Light l){ + for (int i = 0; i < listSize; i++){ + if (list[i] == l){ + remove(i); + return; + } + } + } + + /** + * @return The size of the list. + */ + public int size(){ + return listSize; + } + + /** + * @return the light at the given index. + * @throws IndexOutOfBoundsException If the given index is outside bounds. + */ + public Light get(int num){ + if (num >= listSize || num < 0) + throw new IndexOutOfBoundsException(); + + return list[num]; + } + + /** + * Resets list size to 0. + */ + public void clear() { + if (listSize == 0) + return; + + for (int i = 0; i < listSize; i++) + list[i] = null; + + if (tlist != null) + Arrays.fill(tlist, null); + + listSize = 0; + } + + /** + * Sorts the elements in the list acording to their Comparator. + * There are two reasons why lights should be resorted. + * First, if the lights have moved, that means their distance to + * the spatial changed. + * Second, if the spatial itself moved, it means the distance from it to + * the individual lights might have changed. + * + * + * @param transformChanged Whether the spatial's transform has changed + */ + public void sort(boolean transformChanged) { + if (listSize > 1) { + // resize or populate our temporary array as necessary + if (tlist == null || tlist.length != list.length) { + tlist = list.clone(); + } else { + System.arraycopy(list, 0, tlist, 0, list.length); + } + + if (transformChanged){ + // check distance of each light + for (int i = 0; i < listSize; i++){ + list[i].computeLastDistance(owner); + } + } + + // now merge sort tlist into list + SortUtil.msort(tlist, list, 0, listSize - 1, c); + } + } + + /** + * Updates a "world-space" light list, using the spatial's local-space + * light list and its parent's world-space light list. + * + * @param local + * @param parent + */ + public void update(LightList local, LightList parent){ + // clear the list as it will be reconstructed + // using the arguments + clear(); + + while (list.length <= local.listSize){ + doubleSize(); + } + + // add the lights from the local list + System.arraycopy(local.list, 0, list, 0, local.listSize); + for (int i = 0; i < local.listSize; i++){ +// list[i] = local.list[i]; + distToOwner[i] = Float.NEGATIVE_INFINITY; + } + + // if the spatial has a parent node, add the lights + // from the parent list as well + if (parent != null){ + int sz = local.listSize + parent.listSize; + while (list.length <= sz) + doubleSize(); + + for (int i = 0; i < parent.listSize; i++){ + int p = i + local.listSize; + list[p] = parent.list[i]; + distToOwner[p] = Float.NEGATIVE_INFINITY; + } + + listSize = local.listSize + parent.listSize; + }else{ + listSize = local.listSize; + } + } + + /** + * Returns an iterator that can be used to iterate over this LightList. + * + * @return an iterator that can be used to iterate over this LightList. + */ + public Iterator 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/jme3-core/src/main/java/com/jme3/light/PointLight.java b/jme3-core/src/main/java/com/jme3/light/PointLight.java new file mode 100644 index 000000000..a86b92f1f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/PointLight.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.light; + +import com.jme3.bounding.BoundingVolume; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.io.IOException; + +/** + * Represents a point light. + * A point light emits light from a given position into all directions in space. + * E.g a lamp or a bright effect. Point light positions are in world space. + *

    + * In addition to a position, point lights also have a radius which + * can be used to attenuate the influence of the light depending on the + * distance between the light and the effected object. + * + */ +public class PointLight extends Light { + + protected Vector3f position = new Vector3f(); + protected float radius = 0; + protected float invRadius = 0; + + @Override + public void computeLastDistance(Spatial owner) { + if (owner.getWorldBound() != null) { + BoundingVolume bv = owner.getWorldBound(); + lastDistance = bv.distanceSquaredTo(position); + } else { + lastDistance = owner.getWorldTranslation().distanceSquared(position); + } + } + + /** + * Returns the world space position of the light. + * + * @return the world space position of the light. + * + * @see PointLight#setPosition(com.jme3.math.Vector3f) + */ + public Vector3f getPosition() { + return position; + } + + /** + * Set the world space position of the light. + * + * @param position the world space position of the light. + */ + public void setPosition(Vector3f position) { + this.position.set(position); + } + + /** + * Returns the radius of the light influence. A radius of 0 means + * the light has no attenuation. + * + * @return the radius of the light + */ + public float getRadius() { + return radius; + } + + /** + * Set the radius of the light influence. + *

    + * Setting a non-zero radius indicates the light should use attenuation. + * If a pixel's distance to this light's position + * is greater than the light's radius, then the pixel will not be + * effected by this light, if the distance is less than the radius, then + * the magnitude of the influence is equal to distance / radius. + * + * @param radius the radius of the light influence. + * + * @throws IllegalArgumentException If radius is negative + */ + public void setRadius(float radius) { + if (radius < 0) { + throw new IllegalArgumentException("Light radius cannot be negative"); + } + this.radius = radius; + if(radius!=0){ + this.invRadius = 1 / radius; + }else{ + this.invRadius = 0; + } + } + + /** + * for internal use only + * @return the inverse of the radius + */ + public float getInvRadius() { + return invRadius; + } + + @Override + public Light.Type getType() { + return Light.Type.Point; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(position, "position", null); + oc.write(radius, "radius", 0f); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + position = (Vector3f) ic.readSavable("position", null); + radius = ic.readFloat("radius", 0f); + if(radius!=0){ + this.invRadius = 1 / radius; + }else{ + this.invRadius = 0; + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/light/SpotLight.java b/jme3-core/src/main/java/com/jme3/light/SpotLight.java new file mode 100644 index 000000000..f566f0dc7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/SpotLight.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.light; + +import com.jme3.bounding.BoundingVolume; +import com.jme3.export.*; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.io.IOException; + +/** + * Represents a spot light. + * A spot light emmit a cone of light from a position and in a direction. + * It can be used to fake torch lights or car's lights. + *

    + * In addition to a position and a direction, spot lights also have a range which + * can be used to attenuate the influence of the light depending on the + * distance between the light and the effected object. + * Also the angle of the cone can be tweaked by changing the spot inner angle and the spot outer angle. + * the spot inner angle determin the cone of light where light has full influence. + * the spot outer angle determin the cone global cone of light of the spot light. + * the light intensity slowly decrease between the inner cone and the outer cone. + * @author Nehon + */ +public class SpotLight extends Light implements Savable { + + protected Vector3f position = new Vector3f(); + protected Vector3f direction = new Vector3f(0,-1,0); + protected float spotInnerAngle = FastMath.QUARTER_PI / 8; + protected float spotOuterAngle = FastMath.QUARTER_PI / 6; + protected float spotRange = 100; + protected float invSpotRange = 1 / 100; + protected float packedAngleCos=0; + + public SpotLight() { + super(); + computePackedCos(); + } + + private void computePackedCos() { + float innerCos=FastMath.cos(spotInnerAngle); + float outerCos=FastMath.cos(spotOuterAngle); + packedAngleCos=(int)(innerCos*1000); + //due to approximations, very close angles can give the same cos + //here we make sure outer cos is bellow inner cos. + if(((int)packedAngleCos)== ((int)(outerCos*1000)) ){ + outerCos -= 0.001f; + } + packedAngleCos+=outerCos; + } + + @Override + protected void computeLastDistance(Spatial owner) { + if (owner.getWorldBound() != null) { + BoundingVolume bv = owner.getWorldBound(); + lastDistance = bv.distanceSquaredTo(position); + } else { + lastDistance = owner.getWorldTranslation().distanceSquared(position); + } + } + + @Override + public Type getType() { + return Type.Spot; + } + + public Vector3f getDirection() { + return direction; + } + + public void setDirection(Vector3f direction) { + this.direction.set(direction); + } + + public Vector3f getPosition() { + return position; + } + + public void setPosition(Vector3f position) { + this.position.set(position); + } + + public float getSpotRange() { + return spotRange; + } + + /** + * Set the range of the light influence. + *

    + * Setting a non-zero range indicates the light should use attenuation. + * If a pixel's distance to this light's position + * is greater than the light's range, then the pixel will not be + * effected by this light, if the distance is less than the range, then + * the magnitude of the influence is equal to distance / range. + * + * @param spotRange the range of the light influence. + * + * @throws IllegalArgumentException If spotRange is negative + */ + public void setSpotRange(float spotRange) { + if (spotRange < 0) { + throw new IllegalArgumentException("SpotLight range cannot be negative"); + } + this.spotRange = spotRange; + if (spotRange != 0) { + this.invSpotRange = 1 / spotRange; + } else { + this.invSpotRange = 0; + } + } + + /** + * for internal use only + * @return the inverse of the spot range + */ + public float getInvSpotRange() { + return invSpotRange; + } + + /** + * returns the spot inner angle + * @return the spot inner angle + */ + public float getSpotInnerAngle() { + return spotInnerAngle; + } + + /** + * Sets the inner angle of the cone of influence. + * This angle is the angle between the spot direction axis and the inner border of the cone of influence. + * @param spotInnerAngle + */ + public void setSpotInnerAngle(float spotInnerAngle) { + this.spotInnerAngle = spotInnerAngle; + computePackedCos(); + } + + /** + * returns the spot outer angle + * @return the spot outer angle + */ + public float getSpotOuterAngle() { + return spotOuterAngle; + } + + /** + * Sets the outer angle of the cone of influence. + * This angle is the angle between the spot direction axis and the outer border of the cone of influence. + * this should be greater than the inner angle or the result will be unexpected. + * @param spotOuterAngle + */ + public void setSpotOuterAngle(float spotOuterAngle) { + this.spotOuterAngle = spotOuterAngle; + computePackedCos(); + } + + /** + * for internal use only + * @return the cosines of the inner and outter angle packed in a float + */ + public float getPackedAngleCos() { + return packedAngleCos; + } + + + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(direction, "direction", new Vector3f()); + oc.write(position, "position", new Vector3f()); + oc.write(spotInnerAngle, "spotInnerAngle", FastMath.QUARTER_PI / 8); + oc.write(spotOuterAngle, "spotOuterAngle", FastMath.QUARTER_PI / 6); + oc.write(spotRange, "spotRange", 100); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + spotInnerAngle = ic.readFloat("spotInnerAngle", FastMath.QUARTER_PI / 8); + spotOuterAngle = ic.readFloat("spotOuterAngle", FastMath.QUARTER_PI / 6); + computePackedCos(); + direction = (Vector3f) ic.readSavable("direction", new Vector3f()); + position = (Vector3f) ic.readSavable("position", new Vector3f()); + spotRange = ic.readFloat("spotRange", 100); + if (spotRange != 0) { + this.invSpotRange = 1 / spotRange; + } else { + this.invSpotRange = 0; + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/light/package.html b/jme3-core/src/main/java/com/jme3/light/package.html new file mode 100644 index 000000000..ac6581609 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/package.html @@ -0,0 +1,23 @@ + + + + + + + + + +The com.jme3.light package contains various lights that can be placed +in a scene. +

    +There are 3 types of lights currently supported in jMonkeyEngine: +

      +
    • Point Light - Point lights emit from a certain location and have a certain influence radius (optional)
    • +
    • Directional Light - Directional lights will always appear to emit from a certain direction + regardless of an object's position
    • +
    • Ambient Light - Ambient lights have no location or direction; + they simply emit a constant color that is applied to the entire scene
    • +
    + + + diff --git a/jme3-core/src/main/java/com/jme3/material/FixedFuncBinding.java b/jme3-core/src/main/java/com/jme3/material/FixedFuncBinding.java new file mode 100644 index 000000000..c28186a4a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/FixedFuncBinding.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material; + +/** + * Fixed function binding is used to specify a binding for a {@link MatParam} + * in case that shaders are not supported on the system. + * + * @author Kirill Vainer + */ +public enum FixedFuncBinding { + /** + * Specifies the material ambient color. + * Same as GL_AMBIENT for OpenGL. + */ + MaterialAmbient, + + /** + * Specifies the material diffuse color. + * Same as GL_DIFFUSE for OpenGL. + */ + MaterialDiffuse, + + /** + * Specifies the material specular color. + * Same as GL_SPECULAR for OpenGL + */ + MaterialSpecular, + + /** + * Specifies the color of the object. + *

    + * Used only for non-lit materials. + */ + Color, + + /** + * Specifies the material shininess value. + * + * Same as GL_SHININESS for OpenGL. + */ + MaterialShininess, + + /** + * Use vertex color as an additional diffuse color, if lighting is enabled. + * If lighting is disabled, vertex color is modulated with + * {@link #Color material color}. + */ + UseVertexColor, + + /** + * Set the alpha threshold to discard pixels. + * @see RenderState#setAlphaFallOff + */ + AlphaTestFallOff +} diff --git a/jme3-core/src/main/java/com/jme3/material/MatParam.java b/jme3-core/src/main/java/com/jme3/material/MatParam.java new file mode 100644 index 000000000..42abf49c6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/MatParam.java @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material; + +import com.jme3.asset.TextureKey; +import com.jme3.export.*; +import com.jme3.math.*; +import com.jme3.renderer.GL1Renderer; +import com.jme3.renderer.Renderer; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import java.io.IOException; + +/** + * Describes a material parameter. This is used for both defining a name and type + * as well as a material parameter value. + * + * @author Kirill Vainer + */ +public class MatParam implements Savable, Cloneable { + + protected VarType type; + protected String name; + protected String prefixedName; + protected Object value; + protected FixedFuncBinding ffBinding; + + /** + * Create a new material parameter. For internal use only. + */ + public MatParam(VarType type, String name, Object value, FixedFuncBinding ffBinding) { + this.type = type; + this.name = name; + this.prefixedName = "m_" + name; + this.value = value; + this.ffBinding = ffBinding; + } + + /** + * Serialization only. Do not use. + */ + public MatParam() { + } + + /** + * Returns the fixed function binding. + * + * @return the fixed function binding. + */ + public FixedFuncBinding getFixedFuncBinding() { + return ffBinding; + } + + /** + * Returns the material parameter type. + * + * @return the material parameter type. + */ + public VarType getVarType() { + return type; + } + + /** + * Returns the name of the material parameter. + * @return the name of the material parameter. + */ + public String getName() { + return name; + } + + /** + * Returns the name with "m_" prefixed to it. + * + * @return the name with "m_" prefixed to it + */ + public String getPrefixedName() { + return prefixedName; + } + + /** + * Used internally + * @param name + */ + void setName(String name) { + this.name = name; + this.prefixedName = "m_" + name; + } + + /** + * Returns the value of this material parameter. + *

    + * Material parameters that are used for material definitions + * will not have a value, unless there's a default value declared + * in the definition. + * + * @return the value of this material parameter. + */ + public Object getValue() { + return value; + } + + /** + * Sets the value of this material parameter. + *

    + * It is assumed the value is of the same {@link MatParam#getVarType() type} + * as this material parameter. + * + * @param value the value of this material parameter. + */ + public void setValue(Object value) { + this.value = value; + } + + void apply(Renderer r, Technique technique) { + TechniqueDef techDef = technique.getDef(); + if (techDef.isUsingShaders()) { + technique.updateUniformParam(getPrefixedName(), getVarType(), getValue()); + } + if (ffBinding != null && r instanceof GL1Renderer) { + ((GL1Renderer) r).setFixedFuncBinding(ffBinding, getValue()); + } + } + + /** + * Returns the material parameter value as it would appear in a J3M + * file. E.g.
    + * + * MaterialParameters {
    + * ABC : 1 2 3 4
    + * }
    + *
    + * Assuming "ABC" is a Vector4 parameter, then the value + * "1 2 3 4" would be returned by this method. + *

    + * @return material parameter value as it would appear in a J3M file. + */ + public String getValueAsString() { + switch (type) { + case Boolean: + case Float: + case Int: + return value.toString(); + case Vector2: + Vector2f v2 = (Vector2f) value; + return v2.getX() + " " + v2.getY(); +/* +This may get used at a later point of time +When arrays can be inserted in J3M files + + case Vector2Array: + Vector2f[] v2Arr = (Vector2f[]) value; + String v2str = ""; + for (int i = 0; i < v2Arr.length ; i++) { + v2str += v2Arr[i].getX() + " " + v2Arr[i].getY() + "\n"; + } + return v2str; +*/ + case Vector3: + Vector3f v3 = (Vector3f) value; + return v3.getX() + " " + v3.getY() + " " + v3.getZ(); +/* + case Vector3Array: + Vector3f[] v3Arr = (Vector3f[]) value; + String v3str = ""; + for (int i = 0; i < v3Arr.length ; i++) { + v3str += v3Arr[i].getX() + " " + + v3Arr[i].getY() + " " + + v3Arr[i].getZ() + "\n"; + } + return v3str; + case Vector4Array: + // can be either ColorRGBA, Vector4f or Quaternion + if (value instanceof Vector4f) { + Vector4f[] v4arr = (Vector4f[]) value; + String v4str = ""; + for (int i = 0; i < v4arr.length ; i++) { + v4str += v4arr[i].getX() + " " + + v4arr[i].getY() + " " + + v4arr[i].getZ() + " " + + v4arr[i].getW() + "\n"; + } + return v4str; + } else if (value instanceof ColorRGBA) { + ColorRGBA[] colorArr = (ColorRGBA[]) value; + String colStr = ""; + for (int i = 0; i < colorArr.length ; i++) { + colStr += colorArr[i].getRed() + " " + + colorArr[i].getGreen() + " " + + colorArr[i].getBlue() + " " + + colorArr[i].getAlpha() + "\n"; + } + return colStr; + } else if (value instanceof Quaternion) { + Quaternion[] quatArr = (Quaternion[]) value; + String quatStr = ""; + for (int i = 0; i < quatArr.length ; i++) { + quatStr += quatArr[i].getX() + " " + + quatArr[i].getY() + " " + + quatArr[i].getZ() + " " + + quatArr[i].getW() + "\n"; + } + return quatStr; + } else { + throw new UnsupportedOperationException("Unexpected Vector4Array type: " + value); + } +*/ + case Vector4: + // can be either ColorRGBA, Vector4f or Quaternion + if (value instanceof Vector4f) { + Vector4f v4 = (Vector4f) value; + return v4.getX() + " " + v4.getY() + " " + + v4.getZ() + " " + v4.getW(); + } else if (value instanceof ColorRGBA) { + ColorRGBA color = (ColorRGBA) value; + return color.getRed() + " " + color.getGreen() + " " + + color.getBlue() + " " + color.getAlpha(); + } else if (value instanceof Quaternion) { + Quaternion quat = (Quaternion) value; + return quat.getX() + " " + quat.getY() + " " + + quat.getZ() + " " + quat.getW(); + } else { + throw new UnsupportedOperationException("Unexpected Vector4 type: " + value); + } + case Texture2D: + case Texture3D: + case TextureArray: + case TextureBuffer: + case TextureCubeMap: + Texture texVal = (Texture) value; + TextureKey texKey = (TextureKey) texVal.getKey(); + if (texKey == null){ + throw new UnsupportedOperationException("The specified MatParam cannot be represented in J3M"); + } + + String ret = ""; + if (texKey.isFlipY()) { + ret += "Flip "; + } + if (texVal.getWrap(Texture.WrapAxis.S) == WrapMode.Repeat) { + ret += "Repeat "; + } + + return ret + texKey.getName(); + default: + return null; // parameter type not supported in J3M + } + } + + @Override + public MatParam clone() { + try { + MatParam param = (MatParam) super.clone(); + return param; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(type, "varType", null); + oc.write(name, "name", null); + oc.write(ffBinding, "ff_binding", null); + if (value instanceof Savable) { + Savable s = (Savable) value; + oc.write(s, "value_savable", null); + } else if (value instanceof Float) { + Float f = (Float) value; + oc.write(f.floatValue(), "value_float", 0f); + } else if (value instanceof Integer) { + Integer i = (Integer) value; + oc.write(i.intValue(), "value_int", 0); + } else if (value instanceof Boolean) { + Boolean b = (Boolean) value; + oc.write(b.booleanValue(), "value_bool", false); + } + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + type = ic.readEnum("varType", VarType.class, null); + name = ic.readString("name", null); + prefixedName = "m_" + name; + 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 obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final MatParam other = (MatParam) obj; + if (this.type != other.type) { + return false; + } + if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { + return false; + } + if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) { + return false; + } + if (this.ffBinding != other.ffBinding) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 59 * hash + (this.type != null ? this.type.hashCode() : 0); + hash = 59 * hash + (this.name != null ? this.name.hashCode() : 0); + hash = 59 * hash + (this.value != null ? this.value.hashCode() : 0); + hash = 59 * hash + (this.ffBinding != null ? this.ffBinding.hashCode() : 0); + return hash; + } + + @Override + public String toString() { + return type.name() + " " + name + " : " + getValueAsString(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java b/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java new file mode 100644 index 000000000..8bce967fc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.renderer.Renderer; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture; +import java.io.IOException; + +public class MatParamTexture extends MatParam { + + private Texture texture; + private int unit; + + public MatParamTexture(VarType type, String name, Texture texture, int unit) { + super(type, name, texture, null); + this.texture = texture; + this.unit = unit; + } + + public MatParamTexture() { + } + + public Texture getTextureValue() { + return texture; + } + + public void setTextureValue(Texture value) { + this.value = value; + this.texture = value; + } + + @Override + public void setValue(Object value) { + if (!(value instanceof Texture)) { + throw new IllegalArgumentException("value must be a texture object"); + } + this.value = value; + this.texture = (Texture) value; + } + + public void setUnit(int unit) { + this.unit = unit; + } + + public int getUnit() { + return unit; + } + + @Override + public void apply(Renderer r, Technique technique) { + TechniqueDef techDef = technique.getDef(); + r.setTexture(getUnit(), getTextureValue()); + if (techDef.isUsingShaders()) { + technique.updateUniformParam(getPrefixedName(), getVarType(), getUnit()); + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(unit, "texture_unit", -1); + + // For backwards compat + 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) value; + //texture = (Texture) ic.readSavable("texture", null); + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java new file mode 100644 index 000000000..73c928e96 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -0,0 +1,1241 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material; + +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetManager; +import com.jme3.asset.CloneableSmartAsset; +import com.jme3.export.*; +import com.jme3.light.*; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.material.RenderState.FaceCullMode; +import com.jme3.material.TechniqueDef.LightMode; +import com.jme3.material.TechniqueDef.ShadowMode; +import com.jme3.math.*; +import com.jme3.renderer.Caps; +import com.jme3.renderer.GL1Renderer; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.Geometry; +import com.jme3.shader.Shader; +import com.jme3.shader.Uniform; +import com.jme3.shader.UniformBindingManager; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture; +import com.jme3.util.ListMap; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Material describes the rendering style for a given + * {@link Geometry}. + *

    A material is essentially a list of {@link MatParam parameters}, + * those parameters map to uniforms which are defined in a shader. + * Setting the parameters can modify the behavior of a + * shader. + *

    + * + * @author Kirill Vainer + */ +public class Material implements CloneableSmartAsset, Cloneable, Savable { + + // Version #2: Fixed issue with RenderState.apply*** flags not getting exported + public static final int SAVABLE_VERSION = 2; + private static final Logger logger = Logger.getLogger(Material.class.getName()); + private static final RenderState additiveLight = new RenderState(); + private static final RenderState depthOnly = new RenderState(); + private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1); + + static { + depthOnly.setDepthTest(true); + depthOnly.setDepthWrite(true); + depthOnly.setFaceCullMode(RenderState.FaceCullMode.Back); + depthOnly.setColorWrite(false); + + additiveLight.setBlendMode(RenderState.BlendMode.AlphaAdditive); + additiveLight.setDepthWrite(false); + } + private AssetKey key; + private String name; + private MaterialDef def; + private ListMap 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 Material(MaterialDef def) { + if (def == null) { + throw new NullPointerException("Material definition cannot be null"); + } + this.def = def; + + // Load default values from definition (if any) + for (MatParam param : def.getMaterialParams()) { + if (param.getValue() != null) { + setParam(param.getName(), param.getVarType(), param.getValue()); + } + } + } + + public Material(AssetManager contentMan, String defName) { + this((MaterialDef) contentMan.loadAsset(new AssetKey(defName))); + } + + /** + * Do not use this constructor. Serialization purposes only. + */ + public Material() { + } + + /** + * Returns the asset key name of the asset from which this material was loaded. + * + *

    This value will be null unless this material was loaded + * from a .j3m file. + * + * @return Asset key name of the j3m file + */ + public String getAssetName() { + return key != null ? key.getName() : null; + } + + /** + * @return the name of the material (not the same as the asset name), the returned value can be null + */ + public String getName() { + return name; + } + + /** + * This method sets the name of the material. + * The name is not the same as the asset name. + * It can be null and there is no guarantee of its uniqness. + * @param name the name of the material + */ + public void setName(String name) { + this.name = name; + } + + public void setKey(AssetKey key) { + this.key = key; + } + + public AssetKey getKey() { + return key; + } + + /** + * Returns the sorting ID or sorting index for this material. + * + *

    The sorting ID is used internally by the system to sort rendering + * of geometries. It sorted to reduce shader switches, if the shaders + * are equal, then it is sorted by textures. + * + * @return The sorting ID used for sorting geometries for rendering. + */ + public int getSortId() { + Technique t = getActiveTechnique(); + if (sortingId == -1 && t != null && t.getShader() != null) { + int texId = -1; + for (int i = 0; i < paramValues.size(); i++) { + MatParam param = paramValues.getValue(i); + if (param instanceof MatParamTexture) { + MatParamTexture tex = (MatParamTexture) param; + if (tex.getTextureValue() != null && tex.getTextureValue().getImage() != null) { + if (texId == -1) { + texId = 0; + } + texId += tex.getTextureValue().getImage().getId() % 0xff; + } + } + } + sortingId = texId + t.getShader().getId() * 1000; + } + return sortingId; + } + + /** + * Clones this material. The result is returned. + */ + @Override + public Material clone() { + try { + Material mat = (Material) super.clone(); + + if (additionalState != null) { + mat.additionalState = additionalState.clone(); + } + mat.technique = null; + mat.techniques = new HashMap(); + + 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(ex); + } + } + + /** + * Compares two materials and returns true if they are equal. + * This methods compare definition, parameters, additional render states. + * Since materials are mutable objects, implementing equals() properly is not possible, + * hence the name contentEquals(). + * + * @param otherObj the material to compare to this material + * @return true if the materials are equal. + */ + public boolean contentEquals(Object otherObj) { + if (!(otherObj instanceof Material)) { + return false; + } + + Material other = (Material) otherObj; + + // Early exit if the material are the same object + if (this == other) { + return true; + } + + // Check material definition + if (this.getMaterialDef() != other.getMaterialDef()) { + return false; + } + + // Early exit if the size of the params is different + if (this.paramValues.size() != other.paramValues.size()) { + return false; + } + + // Checking technique + if (this.technique != null || other.technique != null) { + // Techniques are considered equal if their names are the same + // E.g. if user chose custom technique for one material but + // uses default technique for other material, the materials + // are not equal. + String thisDefName = this.technique != null ? this.technique.getDef().getName() : "Default"; + String otherDefName = other.technique != null ? other.technique.getDef().getName() : "Default"; + if (!thisDefName.equals(otherDefName)) { + return false; + } + } + + // Comparing parameters + for (String paramKey : paramValues.keySet()) { + MatParam thisParam = this.getParam(paramKey); + MatParam otherParam = other.getParam(paramKey); + + // This param does not exist in compared mat + if (otherParam == null) { + return false; + } + + if (!otherParam.equals(thisParam)) { + return false; + } + } + + // Comparing additional render states + if (additionalState == null) { + if (other.additionalState != null) { + return false; + } + } else { + if (!additionalState.equals(other.additionalState)) { + return false; + } + } + + return true; + } + + /** + * Works like {@link Object#hashCode() } except it may change together with the material as the material is mutable by definition. + */ + public int contentHashCode() { + int hash = 7; + hash = 29 * hash + (this.def != null ? this.def.hashCode() : 0); + hash = 29 * hash + (this.paramValues != null ? this.paramValues.hashCode() : 0); + hash = 29 * hash + (this.technique != null ? this.technique.getDef().getName().hashCode() : 0); + hash = 29 * hash + (this.additionalState != null ? this.additionalState.contentHashCode() : 0); + return hash; + } + + /** + * Returns the currently active technique. + *

    + * The technique is selected automatically by the {@link RenderManager} + * based on system capabilities. Users may select their own + * technique by using + * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) }. + * + * @return the currently active technique. + * + * @see #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) + */ + public Technique getActiveTechnique() { + return technique; + } + + /** + * Check if the transparent value marker is set on this material. + * @return True if the transparent value marker is set on this material. + * @see #setTransparent(boolean) + */ + public boolean isTransparent() { + return transparent; + } + + /** + * Set the transparent value marker. + * + *

    This value is merely a marker, by itself it does nothing. + * Generally model loaders will use this marker to indicate further + * up that the material is transparent and therefore any geometries + * using it should be put into the {@link Bucket#Transparent transparent + * bucket}. + * + * @param transparent the transparent value marker. + */ + public void setTransparent(boolean transparent) { + this.transparent = transparent; + } + + /** + * Check if the material should receive shadows or not. + * + * @return True if the material should receive shadows. + * + * @see Material#setReceivesShadows(boolean) + */ + public boolean isReceivesShadows() { + return receivesShadows; + } + + /** + * Set if the material should receive shadows or not. + * + *

    This value is merely a marker, by itself it does nothing. + * Generally model loaders will use this marker to indicate + * the material should receive shadows and therefore any + * geometries using it should have the {@link ShadowMode#Receive} set + * on them. + * + * @param receivesShadows if the material should receive shadows or not. + */ + public void setReceivesShadows(boolean receivesShadows) { + this.receivesShadows = receivesShadows; + } + + /** + * Acquire the additional {@link RenderState render state} to apply + * for this material. + * + *

    The first call to this method will create an additional render + * state which can be modified by the user to apply any render + * states in addition to the ones used by the renderer. Only render + * states which are modified in the additional render state will be applied. + * + * @return The additional render state. + */ + public RenderState getAdditionalRenderState() { + if (additionalState == null) { + additionalState = RenderState.ADDITIONAL.clone(); + } + return additionalState; + } + + /** + * Get the material definition (j3md file info) that this + * material is implementing. + * + * @return the material definition this material implements. + */ + public MaterialDef getMaterialDef() { + return def; + } + + /** + * Returns the parameter set on this material with the given name, + * returns null if the parameter is not set. + * + * @param name The parameter name to look up. + * @return The MatParam if set, or null if not set. + */ + public MatParam getParam(String name) { + return paramValues.get(name); + } + + /** + * Returns the texture parameter set on this material with the given name, + * returns null if the parameter is not set. + * + * @param name The parameter name to look up. + * @return The MatParamTexture if set, or null if not set. + */ + public MatParamTexture getTextureParam(String name) { + MatParam param = paramValues.get(name); + if (param instanceof MatParamTexture) { + return (MatParamTexture) param; + } + return null; + } + + /** + * Returns a collection of all parameters set on this material. + * + * @return a collection of all parameters set on this material. + * + * @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object) + */ + public Collection getParams() { + return paramValues.values(); + } + + /** + * Check if setting the parameter given the type and name is allowed. + * @param type The type that the "set" function is designed to set + * @param name The name of the parameter + */ + private void checkSetParam(VarType type, String name) { + MatParam paramDef = def.getMaterialParam(name); + if (paramDef == null) { + throw new IllegalArgumentException("Material parameter is not defined: " + name); + } + if (type != null && paramDef.getVarType() != type) { + logger.log(Level.WARNING, "Material parameter being set: {0} with " + + "type {1} doesn''t match definition types {2}", new Object[]{name, type.name(), paramDef.getVarType()}); + } + } + + /** + * Pass a parameter to the material shader. + * + * @param name the name of the parameter defined in the material definition (j3md) + * @param type the type of the parameter {@link VarType} + * @param value the value of the parameter + */ + public void setParam(String name, VarType type, Object value) { + checkSetParam(type, name); + + if (type.isTextureType()) { + setTextureParam(name, type, (Texture)value); + } else { + MatParam val = getParam(name); + if (val == null) { + MatParam paramDef = def.getMaterialParam(name); + paramValues.put(name, new MatParam(type, name, value, paramDef.getFixedFuncBinding())); + } else { + val.setValue(value); + } + + if (technique != null) { + technique.notifyParamChanged(name, type, value); + } + } + } + + /** + * Clear a parameter from this material. The parameter must exist + * @param name the name of the parameter to clear + */ + public void clearParam(String name) { + checkSetParam(null, name); + MatParam matParam = getParam(name); + if (matParam == null) { + return; + } + + paramValues.remove(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); + } + } + } + sortingId = -1; + } + if (technique != null) { + technique.notifyParamChanged(name, null, null); + } + } + + /** + * Set a texture parameter. + * + * @param name The name of the parameter + * @param type The variable type {@link VarType} + * @param value The texture value of the parameter. + * + * @throws IllegalArgumentException is value is null + */ + public void setTextureParam(String name, VarType type, Texture value) { + if (value == null) { + throw new IllegalArgumentException(); + } + + 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.notifyParamChanged(name, type, nextTexUnit - 1); + } + + // need to recompute sort ID + sortingId = -1; + } + + /** + * Pass a texture to the material shader. + * + * @param name the name of the texture defined in the material definition + * (j3md) (for example Texture for Lighting.j3md) + * @param value the Texture object previously loaded by the asset manager + */ + public void setTexture(String name, Texture value) { + if (value == null) { + // clear it + clearParam(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)

    + */ + protected void updateLightListUniforms(Shader shader, Geometry g, int numLights) { + if (numLights == 0) { // this shader does not do lighting, ignore. + return; + } + + LightList lightList = g.getWorldLightList(); + Uniform lightColor = shader.getUniform("g_LightColor"); + Uniform lightPos = shader.getUniform("g_LightPosition"); + Uniform lightDir = shader.getUniform("g_LightDirection"); + lightColor.setVector4Length(numLights); + lightPos.setVector4Length(numLights); + lightDir.setVector4Length(numLights); + + Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); + ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList)); + + int lightIndex = 0; + + for (int i = 0; i < numLights; i++) { + if (lightList.size() <= i) { + lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex); + lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex); + } else { + Light l = lightList.get(i); + ColorRGBA color = l.getColor(); + lightColor.setVector4InArray(color.getRed(), + color.getGreen(), + color.getBlue(), + l.getType().getId(), + i); + + switch (l.getType()) { + case Directional: + DirectionalLight dl = (DirectionalLight) l; + Vector3f dir = dl.getDirection(); + lightPos.setVector4InArray(dir.getX(), dir.getY(), dir.getZ(), -1, lightIndex); + break; + case Point: + PointLight pl = (PointLight) l; + Vector3f pos = pl.getPosition(); + float invRadius = pl.getInvRadius(); + lightPos.setVector4InArray(pos.getX(), pos.getY(), pos.getZ(), invRadius, lightIndex); + break; + case Spot: + SpotLight sl = (SpotLight) l; + Vector3f pos2 = sl.getPosition(); + Vector3f dir2 = sl.getDirection(); + float invRange = sl.getInvSpotRange(); + float spotAngleCos = sl.getPackedAngleCos(); + + lightPos.setVector4InArray(pos2.getX(), pos2.getY(), pos2.getZ(), invRange, lightIndex); + lightDir.setVector4InArray(dir2.getX(), dir2.getY(), dir2.getZ(), spotAngleCos, lightIndex); + break; + case Ambient: + // skip this light. Does not increase lightIndex + continue; + default: + throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); + } + } + + lightIndex++; + } + + while (lightIndex < numLights) { + lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex); + lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex); + + lightIndex++; + } + } + + protected void renderMultipassLighting(Shader shader, Geometry g, RenderManager rm) { + + Renderer r = rm.getRenderer(); + LightList lightList = g.getWorldLightList(); + Uniform lightDir = shader.getUniform("g_LightDirection"); + Uniform lightColor = shader.getUniform("g_LightColor"); + Uniform lightPos = shader.getUniform("g_LightPosition"); + Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); + boolean isFirstLight = true; + boolean isSecondLight = false; + + for (int i = 0; i < lightList.size(); i++) { + Light l = lightList.get(i); + if (l instanceof AmbientLight) { + continue; + } + + if (isFirstLight) { + // set ambient color for first light only + ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList)); + isFirstLight = false; + isSecondLight = true; + } else if (isSecondLight) { + ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); + // apply additive blending for 2nd and future lights + r.applyRenderState(additiveLight); + isSecondLight = false; + } + + TempVars vars = TempVars.get(); + Quaternion tmpLightDirection = vars.quat1; + Quaternion tmpLightPosition = vars.quat2; + ColorRGBA tmpLightColor = vars.color; + Vector4f tmpVec = vars.vect4f; + + ColorRGBA color = l.getColor(); + tmpLightColor.set(color); + tmpLightColor.a = l.getType().getId(); + lightColor.setValue(VarType.Vector4, tmpLightColor); + + switch (l.getType()) { + case Directional: + DirectionalLight dl = (DirectionalLight) l; + Vector3f dir = dl.getDirection(); + //FIXME : there is an inconstencie here due to backward + //compatibility of the lighting shader. + //The directional light direction is passed in the + //LightPosition uniform. The lightinf shader needs to be + //reworked though in order to fix this. + tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1); + lightPos.setValue(VarType.Vector4, tmpLightPosition); + tmpLightDirection.set(0, 0, 0, 0); + lightDir.setValue(VarType.Vector4, tmpLightDirection); + break; + case Point: + PointLight pl = (PointLight) l; + Vector3f pos = pl.getPosition(); + float invRadius = pl.getInvRadius(); + + tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius); + lightPos.setValue(VarType.Vector4, tmpLightPosition); + tmpLightDirection.set(0, 0, 0, 0); + lightDir.setValue(VarType.Vector4, tmpLightDirection); + break; + case Spot: + SpotLight sl = (SpotLight) l; + Vector3f pos2 = sl.getPosition(); + Vector3f dir2 = sl.getDirection(); + float invRange = sl.getInvSpotRange(); + float spotAngleCos = sl.getPackedAngleCos(); + + tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange); + lightPos.setValue(VarType.Vector4, tmpLightPosition); + + //We transform the spot directoin in view space here to save 5 varying later in the lighting shader + //one vec4 less and a vec4 that becomes a vec3 + //the downside is that spotAngleCos decoding happen now in the frag shader. + tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos); + + lightDir.setValue(VarType.Vector4, tmpLightDirection); + + break; + default: + throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); + } + vars.release(); + r.setShader(shader); + r.renderMesh(g.getMesh(), g.getLodLevel(), 1); + } + + if (isFirstLight && lightList.size() > 0) { + // There are only ambient lights in the scene. Render + // a dummy "normal light" so we can see the ambient + ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList)); + lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha); + lightPos.setValue(VarType.Vector4, nullDirLight); + r.setShader(shader); + r.renderMesh(g.getMesh(), g.getLodLevel(), 1); + } + } + + /** + * Select the technique to use for rendering this material. + *

    + * If name is "Default", then one of the + * {@link MaterialDef#getDefaultTechniques() default techniques} + * on the material will be selected. Otherwise, the named technique + * will be found in the material definition. + *

    + * Any candidate technique for selection (either default or named) + * must be verified to be compatible with the system, for that, the + * renderManager is queried for capabilities. + * + * @param name The name of the technique to select, pass "Default" to + * select one of the default techniques. + * @param renderManager The {@link RenderManager render manager} + * to query for capabilities. + * + * @throws IllegalArgumentException If "Default" is passed and no default + * techniques are available on the material definition, or if a name + * is passed but there's no technique by that name. + * @throws UnsupportedOperationException If no candidate technique supports + * the system capabilities. + */ + public void selectTechnique(String name, RenderManager renderManager) { + // check if already created + Technique tech = techniques.get(name); + // When choosing technique, we choose one that + // supports all the caps. + EnumSet rendererCaps = renderManager.getRenderer().getCaps(); + if (tech == null) { + + if (name.equals("Default")) { + List techDefs = def.getDefaultTechniques(); + if (techDefs == null || techDefs.isEmpty()) { + throw new IllegalArgumentException("No default techniques are available on material '" + def.getName() + "'"); + } + + TechniqueDef lastTech = null; + for (TechniqueDef techDef : techDefs) { + if (rendererCaps.containsAll(techDef.getRequiredCaps())) { + // use the first one that supports all the caps + tech = new Technique(this, techDef); + techniques.put(name, tech); + break; + } + lastTech = techDef; + } + if (tech == null) { + throw new UnsupportedOperationException("No default technique on material '" + def.getName() + "'\n" + + " is supported by the video hardware. The caps " + + lastTech.getRequiredCaps() + " are required."); + } + + } else { + // create "special" technique instance + TechniqueDef techDef = def.getTechniqueDef(name); + if (techDef == null) { + throw new IllegalArgumentException("For material " + def.getName() + ", technique not found: " + name); + } + + if (!rendererCaps.containsAll(techDef.getRequiredCaps())) { + throw new UnsupportedOperationException("The explicitly chosen technique '" + name + "' on material '" + def.getName() + "'\n" + + "requires caps " + techDef.getRequiredCaps() + " which are not " + + "supported by the video renderer"); + } + + tech = new Technique(this, techDef); + techniques.put(name, tech); + } + } else if (technique == tech) { + // attempting to switch to an already + // active technique. + return; + } + + technique = tech; + tech.makeCurrent(def.getAssetManager(), true, rendererCaps); + + // shader was changed + sortingId = -1; + } + + private void autoSelectTechnique(RenderManager rm) { + if (technique == null) { + selectTechnique("Default", rm); + } else { + technique.makeCurrent(def.getAssetManager(), false, rm.getRenderer().getCaps()); + } + } + + /** + * Preloads this material for the given render manager. + *

    + * Preloading the material can ensure that when the material is first + * used for rendering, there won't be any delay since the material has + * been already been setup for rendering. + * + * @param rm The render manager to preload for + */ + public void preload(RenderManager rm) { + autoSelectTechnique(rm); + + Renderer r = rm.getRenderer(); + TechniqueDef techDef = technique.getDef(); + + Collection 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()); + } + } + + Shader shader = technique.getShader(); + if (techDef.isUsingShaders()) { + r.setShader(shader); + } + } + + private void clearUniformsSetByCurrent(Shader shader) { + ListMap uniforms = shader.getUniformMap(); + int size = uniforms.size(); + for (int i = 0; i < size; i++) { + Uniform u = uniforms.getValue(i); + u.clearSetByCurrentMaterial(); + } + } + + private void resetUniformsNotSetByCurrent(Shader shader) { + ListMap uniforms = shader.getUniformMap(); + int size = uniforms.size(); + for (int i = 0; i < size; i++) { + Uniform u = uniforms.getValue(i); + if (!u.isSetByCurrentMaterial()) { + u.clearValue(); + } + } + } + + /** + * Called by {@link RenderManager} to render the geometry by + * using this material. + *

    + * The material is rendered as follows: + *

      + *
    • Determine which technique to use to render the material - + * either what the user selected via + * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) + * Material.selectTechnique()}, + * or the first default technique that the renderer supports + * (based on the technique's {@link TechniqueDef#getRequiredCaps() requested rendering capabilities})
        + *
      • If the technique has been changed since the last frame, then it is notified via + * {@link Technique#makeCurrent(com.jme3.asset.AssetManager, boolean, java.util.EnumSet) + * Technique.makeCurrent()}. + * If the technique wants to use a shader to render the model, it should load it at this part - + * the shader should have all the proper defines as declared in the technique definition, + * including those that are bound to material parameters. + * The technique can re-use the shader from the last frame if + * no changes to the defines occurred.
      + *
    • Set the {@link RenderState} to use for rendering. The render states are + * applied in this order (later RenderStates override earlier RenderStates):
        + *
      1. {@link TechniqueDef#getRenderState() Technique Definition's RenderState} + * - i.e. specific renderstate that is required for the shader.
      2. + *
      3. {@link #getAdditionalRenderState() Material Instance Additional RenderState} + * - i.e. ad-hoc renderstate set per model
      4. + *
      5. {@link RenderManager#getForcedRenderState() RenderManager's Forced RenderState} + * - i.e. renderstate requested by a {@link com.jme3.post.SceneProcessor} or + * post-processing filter.
      + *
    • If the technique {@link TechniqueDef#isUsingShaders() uses a shader}, then the uniforms of the shader must be updated.
        + *
      • Uniforms bound to material parameters are updated based on the current material parameter values.
      • + *
      • Uniforms bound to world parameters are updated from the RenderManager. + * Internally {@link UniformBindingManager} is used for this task.
      • + *
      • Uniforms bound to textures will cause the texture to be uploaded as necessary. + * The uniform is set to the texture unit where the texture is bound.
      + *
    • If the technique uses a shader, the model is then rendered according + * to the lighting mode specified on the technique definition.
        + *
      • {@link LightMode#SinglePass single pass light mode} fills the shader's light uniform arrays + * with the first 4 lights and renders the model once.
      • + *
      • {@link LightMode#MultiPass multi pass light mode} light mode renders the model multiple times, + * for the first light it is rendered opaque, on subsequent lights it is + * rendered with {@link BlendMode#AlphaAdditive alpha-additive} blending and depth writing disabled.
      • + *
      + *
    • For techniques that do not use shaders, + * fixed function OpenGL is used to render the model (see {@link GL1Renderer} interface):
        + *
      • OpenGL state ({@link FixedFuncBinding}) that is bound to material parameters is updated.
      • + *
      • The texture set on the material is uploaded and bound. + * Currently only 1 texture is supported for fixed function techniques.
      • + *
      • If the technique uses lighting, then OpenGL lighting state is updated + * based on the light list on the geometry, otherwise OpenGL lighting is disabled.
      • + *
      • The mesh is uploaded and rendered.
      • + *
      + *
    + * + * @param geom The geometry to render + * @param rm The render manager requesting the rendering + */ + public void render(Geometry geom, RenderManager rm) { + autoSelectTechnique(rm); + + Renderer r = rm.getRenderer(); + + TechniqueDef techDef = technique.getDef(); + + if (techDef.getLightMode() == LightMode.MultiPass + && geom.getWorldLightList().size() == 0) { + return; + } + + if (rm.getForcedRenderState() != null) { + r.applyRenderState(rm.getForcedRenderState()); + } else { + if (techDef.getRenderState() != null) { + r.applyRenderState(techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState)); + } else { + r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState)); + } + } + + + // update camera and world matrices + // NOTE: setWorldTransform should have been called already + if (techDef.isUsingShaders()) { + // reset unchanged uniform flag + clearUniformsSetByCurrent(technique.getShader()); + rm.updateUniformBindings(technique.getWorldBindUniforms()); + } + + // setup textures and uniforms + for (int i = 0; i < paramValues.size(); i++) { + MatParam param = paramValues.getValue(i); + param.apply(r, technique); + } + + Shader shader = technique.getShader(); + + // send lighting information, if needed + switch (techDef.getLightMode()) { + case Disable: + r.setLighting(null); + break; + case SinglePass: + updateLightListUniforms(shader, geom, 4); + break; + case FixedPipeline: + r.setLighting(geom.getWorldLightList()); + break; + case MultiPass: + // NOTE: Special case! + resetUniformsNotSetByCurrent(shader); + renderMultipassLighting(shader, geom, rm); + // very important, notice the return statement! + return; + } + + // upload and bind shader + if (techDef.isUsingShaders()) { + // any unset uniforms will be set to 0 + resetUniformsNotSetByCurrent(shader); + r.setShader(shader); + } + + r.renderMesh(geom.getMesh(), geom.getLodLevel(), 1); + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(def.getAssetName(), "material_def", null); + oc.write(additionalState, "render_state", null); + oc.write(transparent, "is_transparent", false); + oc.writeStringSavableMap(paramValues, "parameters", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + + additionalState = (RenderState) ic.readSavable("render_state", null); + transparent = ic.readBoolean("is_transparent", false); + + // Load the material def + String defName = ic.readString("material_def", null); + HashMap params = (HashMap) ic.readStringSavableMap("parameters", null); + + boolean enableVcolor = false; + boolean separateTexCoord = false; + boolean applyDefaultValues = false; + boolean guessRenderStateApply = false; + + int ver = ic.getSavableVersion(Material.class); + if (ver < 1) { + applyDefaultValues = true; + } + if (ver < 2) { + guessRenderStateApply = true; + } + if (im.getFormatVersion() == 0) { + // Enable compatibility with old models + if (defName.equalsIgnoreCase("Common/MatDefs/Misc/VertexColor.j3md")) { + // Using VertexColor, switch to Unshaded and set VertexColor=true + enableVcolor = true; + defName = "Common/MatDefs/Misc/Unshaded.j3md"; + } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/SimpleTextured.j3md") + || defName.equalsIgnoreCase("Common/MatDefs/Misc/SolidColor.j3md")) { + // Using SimpleTextured/SolidColor, just switch to Unshaded + defName = "Common/MatDefs/Misc/Unshaded.j3md"; + } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/WireColor.j3md")) { + // Using WireColor, set wireframe renderstate = true and use Unshaded + getAdditionalRenderState().setWireframe(true); + defName = "Common/MatDefs/Misc/Unshaded.j3md"; + } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/Unshaded.j3md")) { + // Uses unshaded, ensure that the proper param is set + MatParam value = params.get("SeperateTexCoord"); + if (value != null && ((Boolean) value.getValue()) == true) { + params.remove("SeperateTexCoord"); + separateTexCoord = true; + } + } + assert applyDefaultValues && guessRenderStateApply; + } + + def = (MaterialDef) im.getAssetManager().loadAsset(new AssetKey(defName)); + paramValues = new ListMap(); + + // 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.getTextureValue() == null || texVal.getTextureValue().getImage() == null) { + continue; + } + } + + if (im.getFormatVersion() == 0 && param.getName().startsWith("m_")) { + // Ancient version of jME3 ... + param.setName(param.getName().substring(2)); + } + + checkSetParam(param.getVarType(), param.getName()); + paramValues.put(param.getName(), param); + } + + if (applyDefaultValues) { + // compatability with old versions where default vars were + // not available + for (MatParam param : def.getMaterialParams()) { + if (param.getValue() != null && paramValues.get(param.getName()) == null) { + setParam(param.getName(), param.getVarType(), param.getValue()); + } + } + } + if (guessRenderStateApply && additionalState != null) { + // Try to guess values of "apply" render state based on defaults + // if value != default then set apply to true + additionalState.applyPolyOffset = additionalState.offsetEnabled; + additionalState.applyAlphaFallOff = additionalState.alphaTest; + additionalState.applyAlphaTest = additionalState.alphaTest; + additionalState.applyBlendMode = additionalState.blendMode != BlendMode.Off; + additionalState.applyColorWrite = !additionalState.colorWrite; + additionalState.applyCullMode = additionalState.cullMode != FaceCullMode.Back; + additionalState.applyDepthTest = !additionalState.depthTest; + additionalState.applyDepthWrite = !additionalState.depthWrite; + additionalState.applyPointSprite = additionalState.pointSprite; + additionalState.applyStencilTest = additionalState.stencilTest; + additionalState.applyWireFrame = additionalState.wireframe; + } + if (enableVcolor) { + setBoolean("VertexColor", true); + } + if (separateTexCoord) { + setBoolean("SeparateTexCoord", true); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/MaterialDef.java b/jme3-core/src/main/java/com/jme3/material/MaterialDef.java new file mode 100644 index 000000000..374e257de --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/MaterialDef.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material; + +import com.jme3.asset.AssetManager; +import com.jme3.shader.VarType; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Describes a J3MD (Material definition). + * + * @author Kirill Vainer + */ +public class MaterialDef { + + private static final Logger logger = Logger.getLogger(MaterialDef.class.getName()); + + private String name; + private String assetName; + private AssetManager assetManager; + + private List defaultTechs; + private Map techniques; + private Map matParams; + + /** + * Serialization only. Do not use. + */ + public MaterialDef(){ + } + + /** + * Creates a new material definition with the given name. + * + * @param assetManager The asset manager to use to load shaders + * @param name The debug name of the material definition + */ + public MaterialDef(AssetManager assetManager, String name){ + this.assetManager = assetManager; + this.name = name; + techniques = new HashMap(); + matParams = new HashMap(); + defaultTechs = new ArrayList(); + logger.log(Level.FINE, "Loaded material definition: {0}", name); + } + + /** + * Returns the asset key name of the asset from which this material + * definition was loaded. + * + * @return Asset key name of the j3md file + */ + public String getAssetName() { + return assetName; + } + + /** + * Set the asset key name. + * + * @param assetName the asset key name + */ + public void setAssetName(String assetName) { + this.assetName = assetName; + } + + /** + * Returns the AssetManager passed in the constructor. + * + * @return the AssetManager passed in the constructor. + */ + public AssetManager getAssetManager(){ + return assetManager; + } + + /** + * The debug name of the material definition. + * + * @return debug name of the material definition. + */ + public String getName(){ + return name; + } + + /** + * Adds a new material parameter. + * + * @param type Type of the parameter + * @param name Name of the parameter + * @param value Default value of the parameter + * @param ffBinding Fixed function binding for the parameter + */ + public void addMaterialParam(VarType type, String name, Object value, FixedFuncBinding ffBinding) { + matParams.put(name, new MatParam(type, name, value, ffBinding)); + } + + /** + * Returns the material parameter with the given name. + * + * @param name The name of the parameter to retrieve + * + * @return The material parameter, or null if it does not exist. + */ + public MatParam getMaterialParam(String name){ + return matParams.get(name); + } + + /** + * Returns a collection of all material parameters declared in this + * material definition. + *

    + * Modifying the material parameters or the collection will lead + * to undefined results. + * + * @return All material parameters declared in this definition. + */ + public Collection getMaterialParams(){ + return matParams.values(); + } + + /** + * Adds a new technique definition to this material definition. + *

    + * If the technique name is "Default", it will be added + * to the list of {@link MaterialDef#getDefaultTechniques() default techniques}. + * + * @param technique The technique definition to add. + */ + public void addTechniqueDef(TechniqueDef technique) { + if (technique.getName().equals("Default")) { + defaultTechs.add(technique); + } else { + techniques.put(technique.getName(), technique); + } + } + + /** + * Returns a list of all default techniques. + * + * @return a list of all default techniques. + */ + public List getDefaultTechniques(){ + return defaultTechs; + } + + /** + * Returns a technique definition with the given name. + * This does not include default techniques which can be + * retrieved via {@link MaterialDef#getDefaultTechniques() }. + * + * @param name The name of the technique definition to find + * + * @return The technique definition, or null if cannot be found. + */ + public TechniqueDef getTechniqueDef(String name) { + return techniques.get(name); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/material/MaterialList.java b/jme3-core/src/main/java/com/jme3/material/MaterialList.java new file mode 100644 index 000000000..54f05bad4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/MaterialList.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material; + +import java.util.HashMap; + +/** + * A map from material name to a material. Used by loaders to locate + * materials for meshes inside a model. + * + * @author Kirill Vainer + */ +public class MaterialList extends HashMap { +} diff --git a/jme3-core/src/main/java/com/jme3/material/MaterialProcessor.java b/jme3-core/src/main/java/com/jme3/material/MaterialProcessor.java new file mode 100644 index 000000000..49d04c5db --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/MaterialProcessor.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material; + +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetProcessor; + +public class MaterialProcessor implements AssetProcessor { + + public Object postProcess(AssetKey key, Object obj) { + return null; + } + + public Object createClone(Object obj) { + return ((Material) obj).clone(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/RenderState.java b/jme3-core/src/main/java/com/jme3/material/RenderState.java new file mode 100644 index 000000000..ef39ddc46 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/RenderState.java @@ -0,0 +1,1345 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material; + +import com.jme3.export.*; +import com.jme3.scene.Mesh; +import com.jme3.scene.Mesh.Mode; +import java.io.IOException; + +/** + * RenderState specifies material rendering properties that cannot + * be controlled by a shader on a {@link Material}. The properties + * allow manipulation of rendering features such as depth testing, alpha blending, + * face culling, stencil operations, and much more. + * + * @author Kirill Vainer + */ +public class RenderState implements Cloneable, Savable { + + /** + * The DEFAULT render state is the one used by default + * on all materials unless changed otherwise by the user. + * + *

    + * It has the following properties: + *

      + *
    • Back Face Culling
    • + *
    • Depth Testing Enabled
    • + *
    • Depth Writing Enabled
    • + *
    + */ + public static final RenderState DEFAULT = new RenderState(); + /** + * The NULL render state is identical to the {@link RenderState#DEFAULT} + * render state except that depth testing and face culling are disabled. + */ + public static final RenderState NULL = new RenderState(); + /** + * The ADDITIONAL render state is identical to the + * {@link RenderState#DEFAULT} render state except that all apply + * values are set to false. This allows the ADDITIONAL render + * state to be combined with other state but only influencing values + * that were changed from the original. + */ + public static final RenderState ADDITIONAL = new RenderState(); + + /** + * TestFunction specifies the testing function for stencil test + * function and alpha test function. + * + *

    The functions work similarly as described except that for stencil + * test function, the reference value given in the stencil command is + * the input value while the reference is the value already in the stencil + * buffer. + */ + public enum TestFunction { + + /** + * The test always fails + */ + Never, + /** + * The test succeeds if the input value is equal to the reference value. + */ + Equal, + /** + * The test succeeds if the input value is less than the reference value. + */ + Less, + /** + * The test succeeds if the input value is less than or equal to + * the reference value. + */ + LessOrEqual, + /** + * The test succeeds if the input value is greater than the reference value. + */ + Greater, + /** + * The test succeeds if the input value is greater than or equal to + * the reference value. + */ + GreaterOrEqual, + /** + * The test succeeds if the input value does not equal the + * reference value. + */ + NotEqual, + /** + * The test always passes + */ + Always,} + + /** + * BlendMode specifies the blending operation to use. + * + * @see RenderState#setBlendMode(com.jme3.material.RenderState.BlendMode) + */ + public enum BlendMode { + + /** + * No blending mode is used. + */ + Off, + /** + * Additive blending. For use with glows and particle emitters. + *

    + * Result = Source Color + Destination Color -> (GL_ONE, GL_ONE) + */ + Additive, + /** + * Premultiplied alpha blending, for use with premult alpha textures. + *

    + * Result = Source Color + (Dest Color * (1 - Source Alpha) ) -> (GL_ONE, GL_ONE_MINUS_SRC_ALPHA) + */ + PremultAlpha, + /** + * Additive blending that is multiplied with source alpha. + * For use with glows and particle emitters. + *

    + * Result = (Source Alpha * Source Color) + Dest Color -> (GL_SRC_ALPHA, GL_ONE) + */ + AlphaAdditive, + /** + * Color blending, blends in color from dest color + * using source color. + *

    + * Result = Source Color + (1 - Source Color) * Dest Color -> (GL_ONE, GL_ONE_MINUS_SRC_COLOR) + */ + Color, + /** + * Alpha blending, interpolates to source color from dest color + * using source alpha. + *

    + * Result = Source Alpha * Source Color + + * (1 - Source Alpha) * Dest Color -> (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + */ + Alpha, + /** + * Multiplies the source and dest colors. + *

    + * Result = Source Color * Dest Color -> (GL_DST_COLOR, GL_ZERO) + */ + Modulate, + /** + * Multiplies the source and dest colors then doubles the result. + *

    + * Result = 2 * Source Color * Dest Color -> (GL_DST_COLOR, GL_SRC_COLOR) + */ + ModulateX2 + } + + /** + * FaceCullMode specifies the criteria for faces to be culled. + * + * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode) + */ + public enum FaceCullMode { + + /** + * Face culling is disabled. + */ + Off, + /** + * Cull front faces + */ + Front, + /** + * Cull back faces + */ + Back, + /** + * Cull both front and back faces. + */ + FrontAndBack + } + + /** + * StencilOperation specifies the stencil operation to use + * in a certain scenario as specified in {@link RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) } + */ + public enum StencilOperation { + + /** + * Keep the current value. + */ + Keep, + /** + * Set the value to 0 + */ + Zero, + /** + * Replace the value in the stencil buffer with the reference value. + */ + Replace, + /** + * Increment the value in the stencil buffer, clamp once reaching + * the maximum value. + */ + Increment, + /** + * Increment the value in the stencil buffer and wrap to 0 when + * reaching the maximum value. + */ + IncrementWrap, + /** + * Decrement the value in the stencil buffer and clamp once reaching 0. + */ + Decrement, + /** + * Decrement the value in the stencil buffer and wrap to the maximum + * value when reaching 0. + */ + DecrementWrap, + /** + * Does a bitwise invert of the value in the stencil buffer. + */ + Invert + } + + static { + NULL.cullMode = FaceCullMode.Off; + NULL.depthTest = false; + } + + static { + ADDITIONAL.applyPointSprite = false; + ADDITIONAL.applyWireFrame = false; + ADDITIONAL.applyCullMode = false; + ADDITIONAL.applyDepthWrite = false; + ADDITIONAL.applyDepthTest = false; + ADDITIONAL.applyColorWrite = false; + ADDITIONAL.applyBlendMode = false; + ADDITIONAL.applyAlphaTest = false; + ADDITIONAL.applyAlphaFallOff = false; + ADDITIONAL.applyPolyOffset = false; + } + boolean pointSprite = false; + boolean applyPointSprite = true; + boolean wireframe = false; + boolean applyWireFrame = true; + FaceCullMode cullMode = FaceCullMode.Back; + boolean applyCullMode = true; + boolean depthWrite = true; + boolean applyDepthWrite = true; + boolean depthTest = true; + boolean applyDepthTest = true; + boolean colorWrite = true; + boolean applyColorWrite = true; + BlendMode blendMode = BlendMode.Off; + boolean applyBlendMode = true; + boolean alphaTest = false; + boolean applyAlphaTest = true; + float alphaFallOff = 0; + boolean applyAlphaFallOff = true; + float offsetFactor = 0; + float offsetUnits = 0; + boolean offsetEnabled = false; + boolean applyPolyOffset = true; + boolean stencilTest = false; + boolean applyStencilTest = false; + TestFunction depthFunc = TestFunction.LessOrEqual; + //by default depth func will be applied anyway if depth test is applied + boolean applyDepthFunc = false; + //by default alpha func will be applied anyway if alpha test is applied + TestFunction alphaFunc = TestFunction.Greater; + boolean applyAlphaFunc = false; + StencilOperation frontStencilStencilFailOperation = StencilOperation.Keep; + StencilOperation frontStencilDepthFailOperation = StencilOperation.Keep; + StencilOperation frontStencilDepthPassOperation = StencilOperation.Keep; + StencilOperation backStencilStencilFailOperation = StencilOperation.Keep; + StencilOperation backStencilDepthFailOperation = StencilOperation.Keep; + StencilOperation backStencilDepthPassOperation = StencilOperation.Keep; + TestFunction frontStencilFunction = TestFunction.Always; + TestFunction backStencilFunction = TestFunction.Always; + int cachedHashCode = -1; + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(pointSprite, "pointSprite", false); + oc.write(wireframe, "wireframe", false); + oc.write(cullMode, "cullMode", FaceCullMode.Back); + oc.write(depthWrite, "depthWrite", true); + oc.write(depthTest, "depthTest", true); + oc.write(colorWrite, "colorWrite", true); + oc.write(blendMode, "blendMode", BlendMode.Off); + oc.write(alphaTest, "alphaTest", false); + oc.write(alphaFallOff, "alphaFallOff", 0); + oc.write(offsetEnabled, "offsetEnabled", false); + oc.write(offsetFactor, "offsetFactor", 0); + oc.write(offsetUnits, "offsetUnits", 0); + oc.write(stencilTest, "stencilTest", false); + oc.write(frontStencilStencilFailOperation, "frontStencilStencilFailOperation", StencilOperation.Keep); + oc.write(frontStencilDepthFailOperation, "frontStencilDepthFailOperation", StencilOperation.Keep); + oc.write(frontStencilDepthPassOperation, "frontStencilDepthPassOperation", StencilOperation.Keep); + oc.write(backStencilStencilFailOperation, "frontStencilStencilFailOperation", StencilOperation.Keep); + oc.write(backStencilDepthFailOperation, "backStencilDepthFailOperation", StencilOperation.Keep); + oc.write(backStencilDepthPassOperation, "backStencilDepthPassOperation", StencilOperation.Keep); + oc.write(frontStencilFunction, "frontStencilFunction", TestFunction.Always); + oc.write(backStencilFunction, "backStencilFunction", TestFunction.Always); + + // Only "additional render state" has them set to false by default + oc.write(applyPointSprite, "applyPointSprite", true); + oc.write(applyWireFrame, "applyWireFrame", true); + oc.write(applyCullMode, "applyCullMode", true); + oc.write(applyDepthWrite, "applyDepthWrite", true); + oc.write(applyDepthTest, "applyDepthTest", true); + oc.write(applyColorWrite, "applyColorWrite", true); + oc.write(applyBlendMode, "applyBlendMode", true); + oc.write(applyAlphaTest, "applyAlphaTest", true); + oc.write(applyAlphaFallOff, "applyAlphaFallOff", true); + oc.write(applyPolyOffset, "applyPolyOffset", true); + oc.write(applyDepthFunc, "applyDepthFunc", true); + oc.write(applyAlphaFunc, "applyAlphaFunc", false); + oc.write(depthFunc, "depthFunc", TestFunction.LessOrEqual); + oc.write(alphaFunc, "alphaFunc", TestFunction.Greater); + + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + pointSprite = ic.readBoolean("pointSprite", false); + wireframe = ic.readBoolean("wireframe", false); + cullMode = ic.readEnum("cullMode", FaceCullMode.class, FaceCullMode.Back); + depthWrite = ic.readBoolean("depthWrite", true); + depthTest = ic.readBoolean("depthTest", true); + colorWrite = ic.readBoolean("colorWrite", true); + blendMode = ic.readEnum("blendMode", BlendMode.class, BlendMode.Off); + alphaTest = ic.readBoolean("alphaTest", false); + alphaFallOff = ic.readFloat("alphaFallOff", 0); + offsetEnabled = ic.readBoolean("offsetEnabled", false); + offsetFactor = ic.readFloat("offsetFactor", 0); + offsetUnits = ic.readFloat("offsetUnits", 0); + stencilTest = ic.readBoolean("stencilTest", false); + frontStencilStencilFailOperation = ic.readEnum("frontStencilStencilFailOperation", StencilOperation.class, StencilOperation.Keep); + frontStencilDepthFailOperation = ic.readEnum("frontStencilDepthFailOperation", StencilOperation.class, StencilOperation.Keep); + frontStencilDepthPassOperation = ic.readEnum("frontStencilDepthPassOperation", StencilOperation.class, StencilOperation.Keep); + backStencilStencilFailOperation = ic.readEnum("backStencilStencilFailOperation", StencilOperation.class, StencilOperation.Keep); + backStencilDepthFailOperation = ic.readEnum("backStencilDepthFailOperation", StencilOperation.class, StencilOperation.Keep); + backStencilDepthPassOperation = ic.readEnum("backStencilDepthPassOperation", StencilOperation.class, StencilOperation.Keep); + frontStencilFunction = ic.readEnum("frontStencilFunction", TestFunction.class, TestFunction.Always); + backStencilFunction = ic.readEnum("backStencilFunction", TestFunction.class, TestFunction.Always); + depthFunc = ic.readEnum("depthFunc", TestFunction.class, TestFunction.LessOrEqual); + alphaFunc = ic.readEnum("alphaFunc", TestFunction.class, TestFunction.Greater); + + applyPointSprite = ic.readBoolean("applyPointSprite", true); + applyWireFrame = ic.readBoolean("applyWireFrame", true); + applyCullMode = ic.readBoolean("applyCullMode", true); + applyDepthWrite = ic.readBoolean("applyDepthWrite", true); + applyDepthTest = ic.readBoolean("applyDepthTest", true); + applyColorWrite = ic.readBoolean("applyColorWrite", true); + applyBlendMode = ic.readBoolean("applyBlendMode", true); + applyAlphaTest = ic.readBoolean("applyAlphaTest", true); + applyAlphaFallOff = ic.readBoolean("applyAlphaFallOff", true); + applyPolyOffset = ic.readBoolean("applyPolyOffset", true); + applyDepthFunc = ic.readBoolean("applyDepthFunc", true); + applyAlphaFunc = ic.readBoolean("applyAlphaFunc", false); + + } + + /** + * Create a clone of this RenderState + * + * @return Clone of this render state. + */ + @Override + public RenderState clone() { + try { + return (RenderState) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * returns true if the given renderState is equall to this one + * @param o the renderState to compate to + * @return true if the renderStates are equal + */ + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (!(o instanceof RenderState)) { + return false; + } + RenderState rs = (RenderState) o; + if (pointSprite != rs.pointSprite) { + return false; + } + + if (wireframe != rs.wireframe) { + return false; + } + + if (cullMode != rs.cullMode) { + return false; + } + + if (depthWrite != rs.depthWrite) { + return false; + } + + if (depthTest != rs.depthTest) { + return false; + } + if (depthTest) { + if (depthFunc != rs.depthFunc) { + return false; + } + } + + if (colorWrite != rs.colorWrite) { + return false; + } + + if (blendMode != rs.blendMode) { + return false; + } + + if (alphaTest != rs.alphaTest) { + return false; + } + if (alphaTest) { + if (alphaFunc != rs.alphaFunc) { + return false; + } + } + + if (alphaFallOff != rs.alphaFallOff) { + return false; + } + + if (offsetEnabled != rs.offsetEnabled) { + return false; + } + + if (offsetFactor != rs.offsetFactor) { + return false; + } + + if (offsetUnits != rs.offsetUnits) { + return false; + } + + if (stencilTest != rs.stencilTest) { + return false; + } + + if (stencilTest) { + if (frontStencilStencilFailOperation != rs.frontStencilStencilFailOperation) { + return false; + } + if (frontStencilDepthFailOperation != rs.frontStencilDepthFailOperation) { + return false; + } + if (frontStencilDepthPassOperation != rs.frontStencilDepthPassOperation) { + return false; + } + if (backStencilStencilFailOperation != rs.backStencilStencilFailOperation) { + return false; + } + if (backStencilDepthFailOperation != rs.backStencilDepthFailOperation) { + return false; + } + + if (backStencilDepthPassOperation != rs.backStencilDepthPassOperation) { + return false; + } + if (frontStencilFunction != rs.frontStencilFunction) { + return false; + } + if (backStencilFunction != rs.backStencilFunction) { + return false; + } + } + + return true; + } + + /** + * Enables point sprite mode. + * + *

    When point sprite is enabled, any meshes + * with the type of {@link Mode#Points} will be rendered as 2D quads + * with texturing enabled. Fragment shaders can write to the + * gl_PointCoord variable to manipulate the texture coordinate + * for each pixel. The size of the 2D quad can be controlled by writing + * to the gl_PointSize variable in the vertex shader. + * + * @param pointSprite Enables Point Sprite mode. + */ + public void setPointSprite(boolean pointSprite) { + applyPointSprite = true; + this.pointSprite = pointSprite; + cachedHashCode = -1; + } + + /** + * Sets the alpha fall off value for alpha testing. + * + *

    If the pixel's alpha value is greater than the + * alphaFallOff then the pixel will be rendered, otherwise + * the pixel will be discarded. + * + * Note : Alpha test is deprecated since opengl 3.0 and does not exists in + * openglES 2.0. + * The prefered way is to use the alphaDiscardThreshold on the material + * Or have a shader that discards the pixel when its alpha value meets the + * discarding condition. + * + * @param alphaFallOff The alpha of all rendered pixels must be higher + * than this value to be rendered. This value should be between 0 and 1. + * + * @see RenderState#setAlphaTest(boolean) + */ + public void setAlphaFallOff(float alphaFallOff) { + applyAlphaFallOff = true; + this.alphaFallOff = alphaFallOff; + cachedHashCode = -1; + } + + /** + * Enable alpha testing. + * + *

    When alpha testing is enabled, all input pixels' alpha are compared + * to the {@link RenderState#setAlphaFallOff(float) constant alpha falloff}. + * If the input alpha is greater than the falloff, the pixel will be rendered, + * otherwise it will be discarded. + * + * @param alphaTest Set to true to enable alpha testing. + * + * Note : Alpha test is deprecated since opengl 3.0 and does not exists in + * openglES 2.0. + * The prefered way is to use the alphaDiscardThreshold on the material + * Or have a shader that discards the pixel when its alpha value meets the + * discarding condition. + * + * + * @see RenderState#setAlphaFallOff(float) + */ + public void setAlphaTest(boolean alphaTest) { + applyAlphaTest = true; + this.alphaTest = alphaTest; + cachedHashCode = -1; + } + + /** + * Enable writing color. + * + *

    When color write is enabled, the result of a fragment shader, the + * gl_FragColor, will be rendered into the color buffer + * (including alpha). + * + * @param colorWrite Set to true to enable color writing. + */ + public void setColorWrite(boolean colorWrite) { + applyColorWrite = true; + this.colorWrite = colorWrite; + cachedHashCode = -1; + } + + /** + * Set the face culling mode. + * + *

    See the {@link FaceCullMode} enum on what each value does. + * Face culling will project the triangle's points onto the screen + * and determine if the triangle is in counter-clockwise order or + * clockwise order. If a triangle is in counter-clockwise order, then + * it is considered a front-facing triangle, otherwise, it is considered + * a back-facing triangle. + * + * @param cullMode the face culling mode. + */ + public void setFaceCullMode(FaceCullMode cullMode) { + applyCullMode = true; + this.cullMode = cullMode; + cachedHashCode = -1; + } + + /** + * Set the blending mode. + * + *

    When blending is enabled, (blendMode is not {@link BlendMode#Off}) + * the input pixel will be blended with the pixel + * already in the color buffer. The blending operation is determined + * by the {@link BlendMode}. For example, the {@link BlendMode#Additive} + * will add the input pixel's color to the color already in the color buffer: + *
    + * Result = Source Color + Destination Color + * + * @param blendMode The blend mode to use. Set to {@link BlendMode#Off} + * to disable blending. + */ + public void setBlendMode(BlendMode blendMode) { + applyBlendMode = true; + this.blendMode = blendMode; + cachedHashCode = -1; + } + + /** + * Enable depth testing. + * + *

    When depth testing is enabled, a pixel must pass the depth test + * before it is written to the color buffer. + * The input pixel's depth value must be less than or equal than + * the value already in the depth buffer to pass the depth test. + * + * @param depthTest Enable or disable depth testing. + */ + public void setDepthTest(boolean depthTest) { + applyDepthTest = true; + this.depthTest = depthTest; + cachedHashCode = -1; + } + + /** + * Enable depth writing. + * + *

    After passing the {@link RenderState#setDepthTest(boolean) depth test}, + * a pixel's depth value will be written into the depth buffer if + * depth writing is enabled. + * + * @param depthWrite True to enable writing to the depth buffer. + */ + public void setDepthWrite(boolean depthWrite) { + applyDepthWrite = true; + this.depthWrite = depthWrite; + cachedHashCode = -1; + } + + /** + * Enables wireframe rendering mode. + * + *

    When in wireframe mode, {@link Mesh meshes} rendered in triangle mode + * will not be solid, but instead, only the edges of the triangles + * will be rendered. + * + * @param wireframe True to enable wireframe mode. + */ + public void setWireframe(boolean wireframe) { + applyWireFrame = true; + this.wireframe = wireframe; + cachedHashCode = -1; + } + + /** + * 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; + if (factor == 0 && units == 0) { + offsetEnabled = false; + } else { + offsetEnabled = true; + offsetFactor = factor; + offsetUnits = units; + } + cachedHashCode = -1; + } + + /** + * Enable stencil testing. + * + *

    Stencil testing can be used to filter pixels according to the stencil + * buffer. Objects can be rendered with some stencil operation to manipulate + * the values in the stencil buffer, then, other objects can be rendered + * to test against the values written previously. + * + * @param enabled Set to true to enable stencil functionality. If false + * all other parameters are ignored. + * + * @param _frontStencilStencilFailOperation Sets the operation to occur when + * a front-facing triangle fails the front stencil function. + * @param _frontStencilDepthFailOperation Sets the operation to occur when + * a front-facing triangle fails the depth test. + * @param _frontStencilDepthPassOperation Set the operation to occur when + * a front-facing triangle passes the depth test. + * @param _backStencilStencilFailOperation Set the operation to occur when + * a back-facing triangle fails the back stencil function. + * @param _backStencilDepthFailOperation Set the operation to occur when + * a back-facing triangle fails the depth test. + * @param _backStencilDepthPassOperation Set the operation to occur when + * a back-facing triangle passes the depth test. + * @param _frontStencilFunction Set the test function for front-facing triangles. + * @param _backStencilFunction Set the test function for back-facing triangles. + */ + public void setStencil(boolean enabled, + StencilOperation _frontStencilStencilFailOperation, + StencilOperation _frontStencilDepthFailOperation, + StencilOperation _frontStencilDepthPassOperation, + StencilOperation _backStencilStencilFailOperation, + StencilOperation _backStencilDepthFailOperation, + StencilOperation _backStencilDepthPassOperation, + TestFunction _frontStencilFunction, + TestFunction _backStencilFunction) { + + stencilTest = enabled; + applyStencilTest = true; + this.frontStencilStencilFailOperation = _frontStencilStencilFailOperation; + this.frontStencilDepthFailOperation = _frontStencilDepthFailOperation; + this.frontStencilDepthPassOperation = _frontStencilDepthPassOperation; + this.backStencilStencilFailOperation = _backStencilStencilFailOperation; + this.backStencilDepthFailOperation = _backStencilDepthFailOperation; + this.backStencilDepthPassOperation = _backStencilDepthPassOperation; + this.frontStencilFunction = _frontStencilFunction; + this.backStencilFunction = _backStencilFunction; + cachedHashCode = -1; + } + + /** + * Set the depth conparison function to the given TestFunction + * default is LessOrEqual (GL_LEQUAL) + * @see TestFunction + * @see RenderState#setDepthTest(boolean) + * @param depthFunc the depth comparison function + */ + public void setDepthFunc(TestFunction depthFunc) { + applyDepthFunc = true; + this.depthFunc = depthFunc; + cachedHashCode = -1; + } + + /** + * Sets the alpha comparision function to the given TestFunction + * default is Greater (GL_GREATER) + * + * Note : Alpha test is deprecated since opengl 3.0 and does not exists in + * openglES 2.0. + * The prefered way is to use the alphaDiscardThreshold on the material + * Or have a shader taht discards the pixel when its alpha value meets the + * discarding condition. + * + * @see TestFunction + * @see RenderState#setAlphaTest(boolean) + * @see RenderState#setAlphaFallOff(float) + * @param alphaFunc the alpha comparision function + */ + public void setAlphaFunc(TestFunction alphaFunc) { + applyAlphaFunc = true; + this.alphaFunc = alphaFunc; + cachedHashCode = -1; + } + + + + /** + * Check if stencil test is enabled. + * + * @return True if stencil test is enabled. + */ + public boolean isStencilTest() { + return stencilTest; + } + + /** + * Retrieve the front stencil fail operation. + * + * @return the front stencil fail operation. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public StencilOperation getFrontStencilStencilFailOperation() { + return frontStencilStencilFailOperation; + } + + /** + * Retrieve the front depth test fail operation. + * + * @return the front depth test fail operation. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public StencilOperation getFrontStencilDepthFailOperation() { + return frontStencilDepthFailOperation; + } + + /** + * Retrieve the front depth test pass operation. + * + * @return the front depth test pass operation. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public StencilOperation getFrontStencilDepthPassOperation() { + return frontStencilDepthPassOperation; + } + + /** + * Retrieve the back stencil fail operation. + * + * @return the back stencil fail operation. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public StencilOperation getBackStencilStencilFailOperation() { + return backStencilStencilFailOperation; + } + + /** + * Retrieve the back depth test fail operation. + * + * @return the back depth test fail operation. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public StencilOperation getBackStencilDepthFailOperation() { + return backStencilDepthFailOperation; + } + + /** + * Retrieve the back depth test pass operation. + * + * @return the back depth test pass operation. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public StencilOperation getBackStencilDepthPassOperation() { + return backStencilDepthPassOperation; + } + + /** + * Retrieve the front stencil function. + * + * @return the front stencil function. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public TestFunction getFrontStencilFunction() { + return frontStencilFunction; + } + + /** + * Retrieve the back stencil function. + * + * @return the back stencil function. + * + * @see RenderState#setStencil(boolean, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.StencilOperation, + * com.jme3.material.RenderState.TestFunction, + * com.jme3.material.RenderState.TestFunction) + */ + public TestFunction getBackStencilFunction() { + return backStencilFunction; + } + + /** + * Retrieve the blend mode. + * + * @return the blend mode. + */ + public BlendMode getBlendMode() { + return blendMode; + } + + /** + * Check if point sprite mode is enabled + * + * @return True if point sprite mode is enabled. + * + * @see RenderState#setPointSprite(boolean) + */ + public boolean isPointSprite() { + return pointSprite; + } + + /** + * Check if alpha test is enabled. + * + * @return True if alpha test is enabled. + * + * @see RenderState#setAlphaTest(boolean) + */ + public boolean isAlphaTest() { + return alphaTest; + } + + /** + * Retrieve the face cull mode. + * + * @return the face cull mode. + * + * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode) + */ + public FaceCullMode getFaceCullMode() { + return cullMode; + } + + /** + * Check if depth test is enabled. + * + * @return True if depth test is enabled. + * + * @see RenderState#setDepthTest(boolean) + */ + public boolean isDepthTest() { + return depthTest; + } + + /** + * Check if depth write is enabled. + * + * @return True if depth write is enabled. + * + * @see RenderState#setDepthWrite(boolean) + */ + public boolean isDepthWrite() { + return depthWrite; + } + + /** + * Check if wireframe mode is enabled. + * + * @return True if wireframe mode is enabled. + * + * @see RenderState#setWireframe(boolean) + */ + public boolean isWireframe() { + return wireframe; + } + + /** + * Check if color writing is enabled. + * + * @return True if color writing is enabled. + * + * @see RenderState#setColorWrite(boolean) + */ + public boolean isColorWrite() { + return colorWrite; + } + + /** + * Retrieve the poly offset factor value. + * + * @return the poly offset factor value. + * + * @see RenderState#setPolyOffset(float, float) + */ + public float getPolyOffsetFactor() { + return offsetFactor; + } + + /** + * Retrieve the poly offset units value. + * + * @return the poly offset units value. + * + * @see RenderState#setPolyOffset(float, float) + */ + public float getPolyOffsetUnits() { + return offsetUnits; + } + + /** + * Check if polygon offset is enabled. + * + * @return True if polygon offset is enabled. + * + * @see RenderState#setPolyOffset(float, float) + */ + public boolean isPolyOffset() { + return offsetEnabled; + } + + /** + * Retrieve the alpha falloff value. + * + * @return the alpha falloff value. + * + * @see RenderState#setAlphaFallOff(float) + */ + public float getAlphaFallOff() { + return alphaFallOff; + } + + /** + * Retrieve the depth comparison function + * + * @return the depth comparison function + * + * @see RenderState#setDepthFunc(com.jme3.material.RenderState.TestFunction) + */ + public TestFunction getDepthFunc() { + return depthFunc; + } + + /** + * Retrieve the alpha comparison function + * + * @return the alpha comparison function + * + * @see RenderState#setAlphaFunc(com.jme3.material.RenderState.TestFunction) + */ + public TestFunction getAlphaFunc() { + return alphaFunc; + } + + + + 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 boolean isApplyDepthFunc() { + return applyDepthFunc; + } + + public boolean isApplyAlphaFunc() { + return applyAlphaFunc; + } + + + + /** + * + */ + public int contentHashCode() { + if (cachedHashCode == -1){ + int hash = 7; + hash = 79 * hash + (this.pointSprite ? 1 : 0); + hash = 79 * hash + (this.wireframe ? 1 : 0); + hash = 79 * hash + (this.cullMode != null ? this.cullMode.hashCode() : 0); + hash = 79 * hash + (this.depthWrite ? 1 : 0); + hash = 79 * hash + (this.depthTest ? 1 : 0); + hash = 79 * hash + (this.depthFunc != null ? this.depthFunc.hashCode() : 0); + hash = 79 * hash + (this.colorWrite ? 1 : 0); + hash = 79 * hash + (this.blendMode != null ? this.blendMode.hashCode() : 0); + hash = 79 * hash + (this.alphaTest ? 1 : 0); + hash = 79 * hash + (this.alphaFunc != null ? this.alphaFunc.hashCode() : 0); + hash = 79 * hash + Float.floatToIntBits(this.alphaFallOff); + hash = 79 * hash + Float.floatToIntBits(this.offsetFactor); + hash = 79 * hash + Float.floatToIntBits(this.offsetUnits); + hash = 79 * hash + (this.offsetEnabled ? 1 : 0); + hash = 79 * hash + (this.stencilTest ? 1 : 0); + hash = 79 * hash + (this.frontStencilStencilFailOperation != null ? this.frontStencilStencilFailOperation.hashCode() : 0); + hash = 79 * hash + (this.frontStencilDepthFailOperation != null ? this.frontStencilDepthFailOperation.hashCode() : 0); + hash = 79 * hash + (this.frontStencilDepthPassOperation != null ? this.frontStencilDepthPassOperation.hashCode() : 0); + hash = 79 * hash + (this.backStencilStencilFailOperation != null ? this.backStencilStencilFailOperation.hashCode() : 0); + hash = 79 * hash + (this.backStencilDepthFailOperation != null ? this.backStencilDepthFailOperation.hashCode() : 0); + hash = 79 * hash + (this.backStencilDepthPassOperation != null ? this.backStencilDepthPassOperation.hashCode() : 0); + hash = 79 * hash + (this.frontStencilFunction != null ? this.frontStencilFunction.hashCode() : 0); + hash = 79 * hash + (this.backStencilFunction != null ? this.backStencilFunction.hashCode() : 0); + cachedHashCode = hash; + } + return cachedHashCode; + } + + /** + * Merges this state and additionalState into + * the parameter state based on a specific criteria. + * + *

    The criteria for this merge is the following:
    + * For every given property, such as alpha test or depth write, check + * if it was modified from the original in the additionalState + * if it was modified, then copy the property from the additionalState + * into the parameter state, otherwise, copy the property from this + * into the parameter state. If additionalState + * is null, then no modifications are made and this is returned, + * otherwise, the parameter state is returned with the result + * of the merge. + * + * @param additionalState The additionalState, from which data is taken only + * if it was modified by the user. + * @param state Contains output of the method if additionalState + * is not null. + * @return state if additionalState is non-null, + * otherwise returns this + */ + public RenderState copyMergedTo(RenderState additionalState, RenderState state) { + if (additionalState == null) { + return this; + } + + if (additionalState.applyPointSprite) { + state.pointSprite = additionalState.pointSprite; + } else { + state.pointSprite = pointSprite; + } + if (additionalState.applyWireFrame) { + state.wireframe = additionalState.wireframe; + } else { + state.wireframe = wireframe; + } + + if (additionalState.applyCullMode) { + state.cullMode = additionalState.cullMode; + } else { + state.cullMode = cullMode; + } + if (additionalState.applyDepthWrite) { + state.depthWrite = additionalState.depthWrite; + } else { + state.depthWrite = depthWrite; + } + if (additionalState.applyDepthTest) { + state.depthTest = additionalState.depthTest; + } else { + state.depthTest = depthTest; + } + if (additionalState.applyDepthFunc) { + state.depthFunc = additionalState.depthFunc; + } else { + state.depthFunc = depthFunc; + } + if (additionalState.applyColorWrite) { + state.colorWrite = additionalState.colorWrite; + } else { + state.colorWrite = colorWrite; + } + if (additionalState.applyBlendMode) { + state.blendMode = additionalState.blendMode; + } else { + state.blendMode = blendMode; + } + if (additionalState.applyAlphaTest) { + state.alphaTest = additionalState.alphaTest; + } else { + state.alphaTest = alphaTest; + } + if (additionalState.applyAlphaFunc) { + state.alphaFunc = additionalState.alphaFunc; + } else { + state.alphaFunc = alphaFunc; + } + + if (additionalState.applyAlphaFallOff) { + state.alphaFallOff = additionalState.alphaFallOff; + } else { + state.alphaFallOff = alphaFallOff; + } + if (additionalState.applyPolyOffset) { + state.offsetEnabled = additionalState.offsetEnabled; + state.offsetFactor = additionalState.offsetFactor; + state.offsetUnits = additionalState.offsetUnits; + } else { + state.offsetEnabled = offsetEnabled; + state.offsetFactor = offsetFactor; + state.offsetUnits = offsetUnits; + } + if (additionalState.applyStencilTest) { + state.stencilTest = additionalState.stencilTest; + + state.frontStencilStencilFailOperation = additionalState.frontStencilStencilFailOperation; + state.frontStencilDepthFailOperation = additionalState.frontStencilDepthFailOperation; + state.frontStencilDepthPassOperation = additionalState.frontStencilDepthPassOperation; + + state.backStencilStencilFailOperation = additionalState.backStencilStencilFailOperation; + state.backStencilDepthFailOperation = additionalState.backStencilDepthFailOperation; + state.backStencilDepthPassOperation = additionalState.backStencilDepthPassOperation; + + state.frontStencilFunction = additionalState.frontStencilFunction; + state.backStencilFunction = additionalState.backStencilFunction; + } else { + state.stencilTest = stencilTest; + + state.frontStencilStencilFailOperation = frontStencilStencilFailOperation; + state.frontStencilDepthFailOperation = frontStencilDepthFailOperation; + state.frontStencilDepthPassOperation = frontStencilDepthPassOperation; + + state.backStencilStencilFailOperation = backStencilStencilFailOperation; + state.backStencilDepthFailOperation = backStencilDepthFailOperation; + state.backStencilDepthPassOperation = backStencilDepthPassOperation; + + state.frontStencilFunction = frontStencilFunction; + state.backStencilFunction = backStencilFunction; + } + state.cachedHashCode = -1; + return state; + } + + @Override + public String toString() { + return "RenderState[\n" + + "pointSprite=" + pointSprite + + "\napplyPointSprite=" + applyPointSprite + + "\nwireframe=" + wireframe + + "\napplyWireFrame=" + applyWireFrame + + "\ncullMode=" + cullMode + + "\napplyCullMode=" + applyCullMode + + "\ndepthWrite=" + depthWrite + + "\napplyDepthWrite=" + applyDepthWrite + + "\ndepthTest=" + depthTest + + "\ndepthFunc=" + depthFunc + + "\napplyDepthTest=" + applyDepthTest + + "\ncolorWrite=" + colorWrite + + "\napplyColorWrite=" + applyColorWrite + + "\nblendMode=" + blendMode + + "\napplyBlendMode=" + applyBlendMode + + "\nalphaTest=" + alphaTest + + "\nalphaFunc=" + alphaFunc + + "\napplyAlphaTest=" + applyAlphaTest + + "\nalphaFallOff=" + alphaFallOff + + "\napplyAlphaFallOff=" + applyAlphaFallOff + + "\noffsetEnabled=" + offsetEnabled + + "\napplyPolyOffset=" + applyPolyOffset + + "\noffsetFactor=" + offsetFactor + + "\noffsetUnits=" + offsetUnits + + "\n]"; + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/ShaderGenerationInfo.java b/jme3-core/src/main/java/com/jme3/material/ShaderGenerationInfo.java new file mode 100644 index 000000000..2958f2aa0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/ShaderGenerationInfo.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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.shader.ShaderNodeVariable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * this class is basically a struct that contains the ShaderNodes informations + * in an appropriate way to ease the shader generation process and make it + * faster. + * + * @author Nehon + */ +public class ShaderGenerationInfo implements Savable { + + /** + * the list of attributes of the vertex shader + */ + protected List attributes = new ArrayList(); + /** + * the list of all the uniforms to declare in the vertex shader + */ + protected List vertexUniforms = new ArrayList(); + /** + * the global output of the vertex shader (to assign ot gl_Position) + */ + protected ShaderNodeVariable vertexGlobal = null; + /** + * the list of varyings + */ + protected List varyings = new ArrayList(); + /** + * the list of all the uniforms to declare in the fragment shader + */ + protected List fragmentUniforms = new ArrayList(); + /** + * the list of all the fragment shader global outputs (to assign ot gl_FragColor or gl_Fragdata[n]) + */ + protected List fragmentGlobals = new ArrayList(); + /** + * the unused node names of this shader (node whose output are never used) + */ + protected List unusedNodes = new ArrayList(); + + /** + * + * @return the attributes + */ + public List getAttributes() { + return attributes; + } + + /** + * + * @return the vertex shader uniforms + */ + public List getVertexUniforms() { + return vertexUniforms; + } + + /** + * + * @return the fragment shader uniforms + */ + public List getFragmentUniforms() { + return fragmentUniforms; + } + + /** + * + * @return the vertex shader global ouput + */ + public ShaderNodeVariable getVertexGlobal() { + return vertexGlobal; + } + + /** + * + * @return the fragment shader global outputs + */ + public List getFragmentGlobals() { + return fragmentGlobals; + } + + /** + * + * @return the varyings + */ + public List getVaryings() { + return varyings; + } + + /** + * sets the vertex shader global output + * + * @param vertexGlobal the global output + */ + public void setVertexGlobal(ShaderNodeVariable vertexGlobal) { + this.vertexGlobal = vertexGlobal; + } + + /** + * + * @return the list on unused node names + */ + public List getUnusedNodes() { + return unusedNodes; + } + + /** + * the list of unused node names + * @param unusedNodes + */ + public void setUnusedNodes(List unusedNodes) { + this.unusedNodes = unusedNodes; + } + + /** + * convenient toString method + * + * @return the informations + */ + @Override + public String toString() { + return "ShaderGenerationInfo{" + "attributes=" + attributes + ", vertexUniforms=" + vertexUniforms + ", vertexGlobal=" + vertexGlobal + ", varyings=" + varyings + ", fragmentUniforms=" + fragmentUniforms + ", fragmentGlobals=" + fragmentGlobals + '}'; + } + + + + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.writeSavableArrayList((ArrayList) attributes, "attributes", new ArrayList()); + oc.writeSavableArrayList((ArrayList) vertexUniforms, "vertexUniforms", new ArrayList()); + oc.writeSavableArrayList((ArrayList) varyings, "varyings", new ArrayList()); + oc.writeSavableArrayList((ArrayList) fragmentUniforms, "fragmentUniforms", new ArrayList()); + oc.writeSavableArrayList((ArrayList) fragmentGlobals, "fragmentGlobals", new ArrayList()); + oc.write(vertexGlobal, "vertexGlobal", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + attributes = ic.readSavableArrayList("attributes", new ArrayList()); + vertexUniforms = ic.readSavableArrayList("vertexUniforms", new ArrayList()); + varyings = ic.readSavableArrayList("varyings", new ArrayList()); + fragmentUniforms = ic.readSavableArrayList("fragmentUniforms", new ArrayList()); + fragmentGlobals = ic.readSavableArrayList("fragmentGlobals", new ArrayList()); + vertexGlobal = (ShaderNodeVariable) ic.readSavable("vertexGlobal", null); + + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/Technique.java b/jme3-core/src/main/java/com/jme3/material/Technique.java new file mode 100644 index 000000000..82c87313a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/Technique.java @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material; + +import com.jme3.asset.AssetManager; +import com.jme3.renderer.Caps; +import com.jme3.shader.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +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; + + /** + * Creates a new technique instance that implements the given + * technique definition. + * + * @param owner The material that will own this technique + * @param def The technique definition being implemented. + */ + public Technique(Material owner, TechniqueDef def) { + this.owner = owner; + this.def = def; + if (def.isUsingShaders()) { + this.worldBindUniforms = new ArrayList(); + this.defines = new DefineList(); + } + } + + /** + * Serialization only. Do not use. + */ + public Technique() { + } + + /** + * Returns the technique definition that is implemented by this technique + * instance. + * + * @return the technique definition that is implemented by this technique + * instance. + */ + public TechniqueDef getDef() { + return def; + } + + /** + * Returns the shader currently used by this technique instance. + *

    + * Shaders are typically loaded dynamically when the technique is first + * used, therefore, this variable will most likely be null most of the time. + * + * @return the shader currently used by this technique instance. + */ + public Shader getShader() { + return shader; + } + + /** + * Returns a list of uniforms that implements the world parameters + * that were requested by the material definition. + * + * @return a list of uniforms implementing the world parameters. + */ + public List getWorldBindUniforms() { + return worldBindUniforms; + } + + /** + * Called by the material to tell the technique a parameter was modified. + * Specify null for value if the param is to be cleared. + */ + void notifyParamChanged(String paramName, VarType type, Object value) { + // Check if there's a define binding associated with this + // parameter. + String defineName = def.getShaderParamDefine(paramName); + if (defineName != null) { + // There is a define. Change it on the define list. + // The "needReload" variable will determine + // if the shader will be reloaded when the material + // is rendered. + + if (value == null) { + // Clear the define. + needReload = defines.remove(defineName) || needReload; + } else { + // Set the define. + needReload = defines.set(defineName, type, value) || needReload; + } + } + } + + void updateUniformParam(String paramName, VarType type, Object value) { + if (paramName == null) { + throw new IllegalArgumentException(); + } + + Uniform u = shader.getUniform(paramName); + switch (type) { + case TextureBuffer: + case Texture2D: // fall intentional + case Texture3D: + case TextureArray: + case TextureCubeMap: + case Int: + u.setValue(VarType.Int, value); + break; + default: + u.setValue(type, value); + break; + } + } + + /** + * Returns true if the technique must be reloaded. + *

    + * If a technique needs to reload, then the {@link Material} should + * call {@link #makeCurrent(com.jme3.asset.AssetManager) } on this + * technique. + * + * @return true if the technique must be reloaded. + */ + public boolean isNeedReload() { + return needReload; + } + + /** + * Prepares the technique for use by loading the shader and setting + * the proper defines based on material parameters. + * + * @param assetManager The asset manager to use for loading shaders. + */ + public void makeCurrent(AssetManager assetManager, boolean techniqueSwitched, EnumSet rendererCaps) { + if (!def.isUsingShaders()) { + // No shaders are used, no processing is neccessary. + return; + } + + if (techniqueSwitched) { + // If the technique was switched, check if the define list changed + // based on material parameters. + + Collection params = owner.getParams(); + + if (!defines.equalsParams(params,def)) { + // Defines were changed, update define list + defines.clear(); + for (MatParam param : params) { + String defineName = def.getShaderParamDefine(param.getName()); + if (defineName != null) { + defines.set(defineName, param.getVarType(), param.getValue()); + } + } + needReload = true; + } + } + + if (needReload) { + loadShader(assetManager,rendererCaps); + } + } + + private void loadShader(AssetManager manager,EnumSet rendererCaps) { + + ShaderKey key = new ShaderKey(def.getVertexShaderName(), + def.getFragmentShaderName(), + getAllDefines(), + def.getVertexShaderLanguage(), + def.getFragmentShaderLanguage()); + + if (getDef().isUsingShaderNodes()) { + manager.getShaderGenerator(rendererCaps).initialize(this); + key.setUsesShaderNodes(true); + } + shader = manager.loadShader(key); + + // register the world bound uniforms + worldBindUniforms.clear(); + if (def.getWorldBindings() != null) { + for (UniformBinding binding : def.getWorldBindings()) { + Uniform uniform = shader.getUniform("g_" + binding.name()); + uniform.setBinding(binding); + worldBindUniforms.add(uniform); + } + } + needReload = false; + } + + /** + * Computes the define list + * @return the complete define list + */ + public DefineList getAllDefines() { + DefineList allDefines = new DefineList(); + allDefines.addFrom(def.getShaderPresetDefines()); + allDefines.addFrom(defines); + return allDefines; + } + + /* + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(def, "def", 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); + } + */ +} diff --git a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java new file mode 100644 index 000000000..e939c581f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material; + +import com.jme3.export.*; +import com.jme3.renderer.Caps; +import com.jme3.renderer.Renderer; +import com.jme3.shader.DefineList; +import com.jme3.shader.ShaderNode; +import com.jme3.shader.UniformBinding; +import com.jme3.shader.VarType; +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; + +/** + * Describes a technique definition. + * + * @author Kirill Vainer + */ +public class TechniqueDef implements Savable { + + /** + * Version #1: Separate shader language for each shader source. + */ + public static final int SAVABLE_VERSION = 1; + + /** + * Describes light rendering mode. + */ + public enum LightMode { + /** + * Disable light-based rendering + */ + Disable, + + /** + * Enable light rendering by using a single pass. + *

    + * An array of light positions and light colors is passed to the shader + * containing the world light list for the geometry being rendered. + */ + SinglePass, + + /** + * Enable light rendering by using multi-pass rendering. + *

    + * The geometry will be rendered once for each light. Each time the + * light position and light color uniforms are updated to contain + * the values for the current light. The ambient light color uniform + * is only set to the ambient light color on the first pass, future + * passes have it set to black. + */ + MultiPass, + + /** + * Enable light rendering by using the + * {@link Renderer#setLighting(com.jme3.light.LightList) renderer's setLighting} + * method. + *

    + * The specific details of rendering the lighting is up to the + * renderer implementation. + */ + 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 vertLanguage; + private String fragLanguage; + + private DefineList presetDefines; + private boolean usesShaders; + private boolean usesNodes = false; + private List shaderNodes; + private ShaderGenerationInfo shaderGenerationInfo; + + private RenderState renderState; + private RenderState forcedRenderState; + + private LightMode lightMode = LightMode.Disable; + private ShadowMode shadowMode = ShadowMode.Disable; + + private HashMap defineParams; + private ArrayList worldBinds; + + /** + * Creates a new technique definition. + *

    + * Used internally by the J3M/J3MD loader. + * + * @param name The name of the technique, should be set to null + * for default techniques. + */ + public TechniqueDef(String name){ + this.name = name == null ? "Default" : name; + } + + /** + * Serialization only. Do not use. + */ + public TechniqueDef(){ + } + + /** + * Returns the name of this technique as specified in the J3MD file. + * Default techniques have the name "Default". + * + * @return the name of this technique + */ + public String getName(){ + return name; + } + + /** + * Returns the light mode. + * @return the light mode. + * @see LightMode + */ + public LightMode getLightMode() { + return lightMode; + } + + /** + * Set the light mode + * + * @param lightMode the light mode + * + * @see LightMode + */ + public void setLightMode(LightMode lightMode) { + this.lightMode = lightMode; + } + + /** + * Returns the shadow mode. + * @return the shadow mode. + */ + public ShadowMode getShadowMode() { + return shadowMode; + } + + /** + * Set the shadow mode. + * + * @param shadowMode the shadow mode. + * + * @see ShadowMode + */ + public void setShadowMode(ShadowMode shadowMode) { + this.shadowMode = shadowMode; + } + + /** + * Returns the render state that this technique is using + * @return the render state that this technique is using + * @see #setRenderState(com.jme3.material.RenderState) + */ + public RenderState getRenderState() { + return renderState; + } + + /** + * Sets the render state that this technique is using. + * + * @param renderState the render state that this technique is using. + * + * @see RenderState + */ + public void setRenderState(RenderState renderState) { + this.renderState = renderState; + } + + /** + * Returns true if this technique uses shaders, false otherwise. + * + * @return true if this technique uses shaders, false otherwise. + * + * @see #setShaderFile(java.lang.String, java.lang.String, java.lang.String) + */ + public boolean isUsingShaders(){ + return usesShaders; + } + + /** + * Returns true if this technique uses Shader Nodes, false otherwise. + * + * @return true if this technique uses Shader Nodes, false otherwise. + * + */ + public boolean isUsingShaderNodes(){ + return usesNodes; + } + + /** + * Gets the {@link Caps renderer capabilities} that are required + * by this technique. + * + * @return the required renderer capabilities + */ + public EnumSet getRequiredCaps() { + return requiredCaps; + } + + /** + * Sets the shaders that this technique definition will use. + * + * @param vertexShader The name of the vertex shader + * @param fragmentShader The name of the fragment shader + * @param vertLanguage The vertex shader language + * @param fragLanguage The fragment shader language + */ + public void setShaderFile(String vertexShader, String fragmentShader, String vertLanguage, String fragLanguage){ + this.vertName = vertexShader; + this.fragName = fragmentShader; + this.vertLanguage = vertLanguage; + this.fragLanguage = fragLanguage; + + Caps vertCap = Caps.valueOf(vertLanguage); + requiredCaps.add(vertCap); + Caps fragCap = Caps.valueOf(fragLanguage); + requiredCaps.add(fragCap); + + usesShaders = true; + } + + /** + * Returns the define name which the given material parameter influences. + * + * @param paramName The parameter name to look up + * @return The define name + * + * @see #addShaderParamDefine(java.lang.String, java.lang.String) + */ + public String getShaderParamDefine(String paramName){ + if (defineParams == null) { + return null; + } + return defineParams.get(paramName); + } + + /** + * Adds a define linked to a material parameter. + *

    + * Any time the material parameter on the parent material is altered, + * the appropriate define on the technique will be modified as well. + * See the method + * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) } + * on the exact details of how the material parameter changes the define. + * + * @param paramName The name of the material parameter to link to. + * @param defineName The name of the define parameter, e.g. USE_LIGHTING + */ + public void addShaderParamDefine(String paramName, String defineName){ + if (defineParams == null) { + defineParams = new HashMap(); + } + defineParams.put(paramName, defineName); + } + + /** + * Returns the {@link DefineList} for the preset defines. + * + * @return the {@link DefineList} for the preset defines. + * + * @see #addShaderPresetDefine(java.lang.String, com.jme3.shader.VarType, java.lang.Object) + */ + public DefineList getShaderPresetDefines() { + return presetDefines; + } + + /** + * Adds a preset define. + *

    + * Preset defines do not depend upon any parameters to be activated, + * they are always passed to the shader as long as this technique is used. + * + * @param defineName The name of the define parameter, e.g. USE_LIGHTING + * @param type The type of the define. See + * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) } + * to see why it matters. + * + * @param value The value of the define + */ + public void addShaderPresetDefine(String defineName, VarType type, Object value){ + if (presetDefines == null) { + presetDefines = new DefineList(); + } + presetDefines.set(defineName, type, value); + } + + /** + * Returns the name of the fragment shader used by the technique, or null + * if no fragment shader is specified. + * + * @return the name of the fragment shader to be used. + */ + public String getFragmentShaderName() { + return fragName; + } + + + /** + * Returns the name of the vertex shader used by the technique, or null + * if no vertex shader is specified. + * + * @return the name of the vertex shader to be used. + */ + public String getVertexShaderName() { + return vertName; + } + + /** + * @deprecated Use {@link #getVertexShaderLanguage() } instead. + */ + @Deprecated + public String getShaderLanguage() { + return vertLanguage; + } + + /** + * Returns the language of the fragment shader used in this technique. + */ + public String getFragmentShaderLanguage() { + return fragLanguage; + } + + /** + * Returns the language of the vertex shader used in this technique. + */ + public String getVertexShaderLanguage() { + return vertLanguage; + } + + /** + * Adds a new world parameter by the given name. + * + * @param name The world parameter to add. + * @return True if the world parameter name was found and added + * to the list of world parameters, false otherwise. + */ + public boolean addWorldParam(String name) { + if (worldBinds == null){ + worldBinds = new ArrayList(); + } + + try { + worldBinds.add( UniformBinding.valueOf(name) ); + return true; + } catch (IllegalArgumentException ex){ + return false; + } + } + + public RenderState getForcedRenderState() { + return forcedRenderState; + } + + public void setForcedRenderState(RenderState forcedRenderState) { + this.forcedRenderState = forcedRenderState; + } + + /** + * Returns a list of world parameters that are used by this + * technique definition. + * + * @return The list of world parameters + */ + public List getWorldBindings() { + return worldBinds; + } + + public void write(JmeExporter ex) throws IOException{ + OutputCapsule oc = ex.getCapsule(this); + oc.write(name, "name", null); + oc.write(vertName, "vertName", null); + oc.write(fragName, "fragName", null); + oc.write(vertLanguage, "vertLanguage", null); + oc.write(vertLanguage, "fragLanguage", 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); + oc.write(usesNodes, "usesNodes", false); + oc.writeSavableArrayList((ArrayList)shaderNodes,"shaderNodes", null); + oc.write(shaderGenerationInfo, "shaderGenerationInfo", null); + + // 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); + 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); + + if (ic.getSavableVersion(TechniqueDef.class) == 0) { + // Old version + vertLanguage = ic.readString("shaderLang", null); + fragLanguage = vertLanguage; + } else { + // New version + vertLanguage = ic.readString("vertLanguage", null); + fragLanguage = ic.readString("fragLanguage", null);; + } + + usesNodes = ic.readBoolean("usesNodes", false); + shaderNodes = ic.readSavableArrayList("shaderNodes", null); + shaderGenerationInfo = (ShaderGenerationInfo) ic.readSavable("shaderGenerationInfo", null); + } + + public List getShaderNodes() { + return shaderNodes; + } + + public void setShaderNodes(List shaderNodes) { + this.shaderNodes = shaderNodes; + usesNodes = true; + usesShaders = true; + } + + public ShaderGenerationInfo getShaderGenerationInfo() { + return shaderGenerationInfo; + } + + public void setShaderGenerationInfo(ShaderGenerationInfo shaderGenerationInfo) { + this.shaderGenerationInfo = shaderGenerationInfo; + } + + @Override + public String toString() { + return "TechniqueDef{" + "requiredCaps=" + requiredCaps + ", name=" + name + ", vertName=" + vertName + ", fragName=" + fragName + ", vertLanguage=" + vertLanguage + ", fragLanguage=" + fragLanguage + ", presetDefines=" + presetDefines + ", usesShaders=" + usesShaders + ", usesNodes=" + usesNodes + ", shaderNodes=" + shaderNodes + ", shaderGenerationInfo=" + shaderGenerationInfo + ", renderState=" + renderState + ", forcedRenderState=" + forcedRenderState + ", lightMode=" + lightMode + ", shadowMode=" + shadowMode + ", defineParams=" + defineParams + ", worldBinds=" + worldBinds + '}'; + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/package.html b/jme3-core/src/main/java/com/jme3/material/package.html new file mode 100644 index 000000000..9af9cc843 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/package.html @@ -0,0 +1,58 @@ + + + + + + + + + +The com.jme3.material package contains classes for manipulating +jMonkeyEngine materials. +Materials are applied to {@link com.jme3.scene.Geometry geometries} in the +scene. +Each geometry has a single material which is used to render that +geometry. +

    +Materials (also known as material instances) are extended from +material definitions. + +

    Material definitions

    +

    +Material definitions provide the "logic" for the material. Usually a shader that +will handle drawing the object, and corresponding parameters that allow +configuration of the shader. +Material definitions can be created through J3MD files. +The J3MD file abstracts the shader and its configuration away from the user, allowing a +simple interface where one can simply set a few parameters on the material to change its +appearance and the way its handled. + +

    Techniques

    +

    +Techniques specify a specific way of rendering a material. Typically +a technique is used to implement the same material for each configuration +of the system. For GPUs that do not support shaders, a "fixed function pipeline" +technique could exist to take care of rendering for that configuration + +

    Render states

    +

    +See {@link com.jme3.material.RenderState}. + +

    Example Usage

    +

    +Creating a textured material + +// Create a material instance +Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + +// Load the texture. +Texture tex = assetManager.loadTexture("Textures/Test/Test.jpg"); + +// Set the parameters +mat.setTexture("ColorMap", tex); + + + + + + diff --git a/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java b/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java new file mode 100644 index 000000000..5bd6442b6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; + +public abstract class AbstractTriangle implements Collidable { + + public abstract Vector3f get1(); + public abstract Vector3f get2(); + public abstract Vector3f get3(); + public abstract void set(Vector3f v1, Vector3f v2, Vector3f v3); + + public int collideWith(Collidable other, CollisionResults results){ + return other.collideWith(this, results); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java new file mode 100644 index 000000000..347bec609 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java @@ -0,0 +1,595 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +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, java.io.Serializable { + + static final long serialVersionUID = 1; + /** + * The color black (0,0,0). + */ + public static final ColorRGBA Black = new ColorRGBA(0f, 0f, 0f, 1f); + /** + * The color white (1,1,1). + */ + public static final ColorRGBA White = new ColorRGBA(1f, 1f, 1f, 1f); + /** + * The color gray (.2,.2,.2). + */ + public static final ColorRGBA DarkGray = new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f); + /** + * The color gray (.5,.5,.5). + */ + public static final ColorRGBA Gray = new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f); + /** + * The color gray (.8,.8,.8). + */ + public static final ColorRGBA LightGray = new ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f); + /** + * The color red (1,0,0). + */ + public static final ColorRGBA Red = new ColorRGBA(1f, 0f, 0f, 1f); + /** + * The color green (0,1,0). + */ + public static final ColorRGBA Green = new ColorRGBA(0f, 1f, 0f, 1f); + /** + * The color blue (0,0,1). + */ + public static final ColorRGBA Blue = new ColorRGBA(0f, 0f, 1f, 1f); + /** + * The color yellow (1,1,0). + */ + public static final ColorRGBA Yellow = new ColorRGBA(1f, 1f, 0f, 1f); + /** + * The color magenta (1,0,1). + */ + public static final ColorRGBA Magenta = new ColorRGBA(1f, 0f, 1f, 1f); + /** + * The color cyan (0,1,1). + */ + public static final ColorRGBA Cyan = new ColorRGBA(0f, 1f, 1f, 1f); + /** + * The color orange (251/255, 130/255,0). + */ + public static final ColorRGBA Orange = new ColorRGBA(251f / 255f, 130f / 255f, 0f, 1f); + /** + * The color brown (65/255, 40/255, 25/255). + */ + public static final ColorRGBA Brown = new ColorRGBA(65f / 255f, 40f / 255f, 25f / 255f, 1f); + /** + * The color pink (1, 0.68, 0.68). + */ + public static final ColorRGBA Pink = new ColorRGBA(1f, 0.68f, 0.68f, 1f); + /** + * The black color with no alpha (0, 0, 0, 0). + */ + public static final ColorRGBA BlackNoAlpha = new ColorRGBA(0f, 0f, 0f, 0f); + /** + * The red component of the color. 0 is none and 1 is maximum red. + */ + public float r; + /** + * The green component of the color. 0 is none and 1 is maximum green. + */ + public float g; + /** + * The blue component of the color. 0 is none and 1 is maximum blue. + */ + 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 ColorRGBA. + * @param b The blue component of this ColorRGBA. + * @param a The alpha component of this ColorRGBA. + */ + 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 ColorRGBA. 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 ColorRGBA to those + * set by a parameter color. + * + * @param rgba The color to set this ColorRGBA 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 + * ColorRGBA as a four element float array in the + * order: r,g,b,a. + * @return The float array that contains the color components. + */ + public float[] getColorArray() { + return new float[]{r, g, b, a}; + } + + /** + * Stores the current r,g,b,a values into the given array. The given array must have a + * length of 4 or greater, or an array index out of bounds exception will be thrown. + * @param store The float array to store the values into. + * @return The float array after storage. + */ + public float[] getColorArray(float[] store) { + store[0] = r; + store[1] = g; + store[2] = b; + store[3] = a; + return store; + } + + /** + * Retrieves the alpha component value of this ColorRGBA. + * @return The alpha component value. + */ + public float getAlpha() { + return a; + } + + /** + * Retrieves the red component value of this ColorRGBA. + * @return The red component value. + */ + public float getRed() { + return r; + } + + /** + * Retrieves the blue component value of this ColorRGBA. + * @return The blue component value. + */ + public float getBlue() { + return b; + } + + /** + * Retrieves the green component value of this ColorRGBA. + * @return The green component value. + */ + public float getGreen() { + return g; + } + + /** + * Sets this ColorRGBA 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 percentage + * 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 ColorRGBA 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 + * opaque color. + * @return a random ColorRGBA with an alpha set to 1. + */ + 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 ColorRGBA by the corresponding + * 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 by. + * @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 ColorRGBA 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 by. + * @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 ColorRGBA by the given scalar and + * returns the result (this). + * Used as a way of making colors dimmer or brighter. + * @param scalar The scalar to multiply by. + * @return 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 ColorRGBA by the corresponding + * 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 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); + } + + /** + * Adds each r,g,b,a of this ColorRGBA by the r,g,b,a the given + * color and returns the result (this). + * Used as a way of combining colors and lights. + * @param c The color to add. + * @return 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 ColorRGBA. + * The format of the string is:
    + * : [R=RR.RRRR, G=GG.GGGG, B=BB.BBBB, A=AA.AAAA] + * @return The string representation of this ColorRGBA. + */ + @Override + 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 array. + * @param floats The float array 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 ColorRGBA is logically equivalent + * to a given color. That is, if all the components of the two colors are the same. + * False is returned otherwise. + * @param o The object to compare against. + * @return true if the colors are equal, false otherwise. + */ + @Override + 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 ColorRGBA based + * on its values. If two colors are logically equivalent, they will return + * the same hash code value. + * @return The hash code value of this ColorRGBA. + */ + @Override + 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); + } + /** + * Retrieves the component values of this ColorRGBA as + * a four element byte array in the order: r,g,b,a. + * @return the byte array that contains the color components. + */ + 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; + } + + /** + * Retrieves the component values of this ColorRGBA as an + * int in a,r,g,b order. + * Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue. + * @return The integer representation of this ColorRGBA in a,r,g,b order. + */ + 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; + } + + /** + * Retrieves the component values of this ColorRGBA as an + * int in r,g,b,a order. + * Bits 24-31 are red, 16-23 are green, 8-15 are blue, 0-7 are alpha. + * @return The integer representation of this ColorRGBA in r,g,b,a order. + */ + 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; + } + /** + * Retrieves the component values of this ColorRGBA as an + * int in a,b,g,r order. + * Bits 24-31 are alpha, 16-23 are blue, 8-15 are green, 0-7 are red. + * @return The integer representation of this ColorRGBA in a,b,g,r order. + */ + 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; + } + /** + * Sets the component values of this ColorRGBA with the given + * combined ARGB int. + * Bits 24-31 are alpha, bits 16-23 are red, bits 8-15 are green, bits 0-7 are blue. + * @param color The integer ARGB value used to set this ColorRGBA. + */ + 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; + } + /** + * Sets the RGBA values of this ColorRGBA with the given combined RGBA value + * Bits 24-31 are red, bits 16-23 are green, bits 8-15 are blue, bits 0-7 are alpha. + * @param color The integer RGBA value used to set this object. + */ + public void fromIntRGBA(int color) { + r = ((byte) (color >> 24) & 0xFF) / 255f; + g = ((byte) (color >> 16) & 0xFF) / 255f; + b = ((byte) (color >> 8) & 0xFF) / 255f; + a = ((byte) (color) & 0xFF) / 255f; + } + + /** + * Transform this ColorRGBA to a Vector3f using + * x = r, y = g, z = b. The Alpha value is not used. + * This method is useful to use for shaders assignment. + * @return A Vector3f containing the RGB value of this ColorRGBA. + */ + public Vector3f toVector3f() { + return new Vector3f(r, g, b); + } + + /** + * Transform this ColorRGBA to a Vector4f using + * x = r, y = g, z = b, w = a. + * This method is useful to use for shaders assignment. + * @return A Vector4f containing the RGBA value of this ColorRGBA. + */ + public Vector4f toVector4f() { + return new Vector4f(r, g, b, a); + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/CurveAndSurfaceMath.java b/jme3-core/src/main/java/com/jme3/math/CurveAndSurfaceMath.java new file mode 100644 index 000000000..82226e34c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/CurveAndSurfaceMath.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.math.Spline.SplineType; +import java.util.List; + +/** + * This class offers methods to help with curves and surfaces calculations. + * @author Marcin Roguski (Kealthas) + */ +public class CurveAndSurfaceMath { + private static final float KNOTS_MINIMUM_DELTA = 0.0001f; + + /** + * A private constructor is defined to avoid instatiation of this class. + */ + private CurveAndSurfaceMath() {} + + /** + * This method interpolates tha data for the nurbs curve. + * @param u + * the u value + * @param nurbSpline + * the nurbs spline definition + * @param store + * the resulting point in 3D space + */ + public static void interpolateNurbs(float u, Spline nurbSpline, Vector3f store) { + if (nurbSpline.getType() != SplineType.Nurb) { + throw new IllegalArgumentException("Given spline is not of a NURB type!"); + } + List controlPoints = nurbSpline.getControlPoints(); + float[] weights = nurbSpline.getWeights(); + List knots = nurbSpline.getKnots(); + int controlPointAmount = controlPoints.size(); + + store.set(Vector3f.ZERO); + float delimeter = 0; + for (int i = 0; i < controlPointAmount; ++i) { + float val = weights[i] * CurveAndSurfaceMath.computeBaseFunctionValue(i, nurbSpline.getBasisFunctionDegree(), u, knots); + store.addLocal(nurbSpline.getControlPoints().get(i) + .mult(val)); + delimeter += val; + } + store.divideLocal(delimeter); + } + + /** + * This method interpolates tha data for the nurbs surface. + * + * @param u + * the u value + * @param v + * the v value + * @param controlPoints + * the nurbs' control points + * @param knots + * the nurbs' knots + * @param basisUFunctionDegree + * the degree of basis U function + * @param basisVFunctionDegree + * the degree of basis V function + * @param store + * the resulting point in 3D space + */ + public static void interpolate(float u, float v, List> controlPoints, List[] knots, + int basisUFunctionDegree, int basisVFunctionDegree, Vector3f store) { + store.set(Vector3f.ZERO); + float delimeter = 0; + int vControlPointsAmount = controlPoints.size(); + int uControlPointsAmount = controlPoints.get(0).size(); + for (int i = 0; i < vControlPointsAmount; ++i) { + for (int j = 0; j < uControlPointsAmount; ++j) { + Vector4f controlPoint = controlPoints.get(i).get(j); + float val = controlPoint.w + * CurveAndSurfaceMath.computeBaseFunctionValue(i, basisVFunctionDegree, v, knots[1]) + * CurveAndSurfaceMath.computeBaseFunctionValue(j, basisUFunctionDegree, u, knots[0]); + store.addLocal(controlPoint.x * val, controlPoint.y * val, controlPoint.z * val); + delimeter += val; + } + } + store.divideLocal(delimeter); + } + + /** + * This method prepares the knots to be used. If the knots represent non-uniform B-splines (first and last knot values are being + * repeated) it leads to NaN results during calculations. This method adds a small number to each of such knots to avoid NaN's. + * @param knots + * the knots to be prepared to use + * @param basisFunctionDegree + * the degree of basis function + */ + // TODO: improve this; constant delta may lead to errors if the difference between tha last repeated + // point and the following one is lower than it + public static void prepareNurbsKnots(List knots, int basisFunctionDegree) { + float delta = KNOTS_MINIMUM_DELTA; + float prevValue = knots.get(0).floatValue(); + for(int i=1;i knots) { + if (k == 1) { + return knots.get(i) <= t && t < knots.get(i + 1) ? 1.0f : 0.0f; + } else { + return (t - knots.get(i)) / (knots.get(i + k - 1) - knots.get(i)) * + CurveAndSurfaceMath.computeBaseFunctionValue(i, k - 1, t, knots) + + (knots.get(i + k) - t) / (knots.get(i + k) - knots.get(i + 1)) * + CurveAndSurfaceMath.computeBaseFunctionValue(i + 1, k - 1, t, knots); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/Eigen3f.java b/jme3-core/src/main/java/com/jme3/math/Eigen3f.java new file mode 100644 index 000000000..b43731bad --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Eigen3f.java @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Eigen3f implements java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Eigen3f.class + .getName()); + + float[] eigenValues = new float[3]; + Vector3f[] eigenVectors = new Vector3f[3]; + + static final double ONE_THIRD_DOUBLE = 1.0 / 3.0; + static final double ROOT_THREE_DOUBLE = Math.sqrt(3.0); + + + public Eigen3f() { + + } + + public Eigen3f(Matrix3f data) { + calculateEigen(data); + } + + public void calculateEigen(Matrix3f data) { + // prep work... + eigenVectors[0] = new Vector3f(); + eigenVectors[1] = new Vector3f(); + eigenVectors[2] = new Vector3f(); + + Matrix3f scaledData = new Matrix3f(data); + float maxMagnitude = scaleMatrix(scaledData); + + // Compute the eigenvalues using double-precision arithmetic. + double roots[] = new double[3]; + computeRoots(scaledData, roots); + eigenValues[0] = (float) roots[0]; + eigenValues[1] = (float) roots[1]; + eigenValues[2] = (float) roots[2]; + + float[] maxValues = new float[3]; + Vector3f[] maxRows = new Vector3f[3]; + maxRows[0] = new Vector3f(); + maxRows[1] = new Vector3f(); + maxRows[2] = new Vector3f(); + + for (int i = 0; i < 3; i++) { + Matrix3f tempMatrix = new Matrix3f(scaledData); + tempMatrix.m00 -= eigenValues[i]; + tempMatrix.m11 -= eigenValues[i]; + tempMatrix.m22 -= eigenValues[i]; + float[] val = new float[1]; + val[0] = maxValues[i]; + if (!positiveRank(tempMatrix, val, maxRows[i])) { + // Rank was 0 (or very close to 0), so just return. + // Rescale back to the original size. + if (maxMagnitude > 1f) { + for (int j = 0; j < 3; j++) { + eigenValues[j] *= maxMagnitude; + } + } + + eigenVectors[0].set(Vector3f.UNIT_X); + eigenVectors[1].set(Vector3f.UNIT_Y); + eigenVectors[2].set(Vector3f.UNIT_Z); + return; + } + maxValues[i] = val[0]; + } + + float maxCompare = maxValues[0]; + int i = 0; + if (maxValues[1] > maxCompare) { + maxCompare = maxValues[1]; + i = 1; + } + if (maxValues[2] > maxCompare) { + i = 2; + } + + // use the largest row to compute and order the eigen vectors. + switch (i) { + case 0: + maxRows[0].normalizeLocal(); + computeVectors(scaledData, maxRows[0], 1, 2, 0); + break; + case 1: + maxRows[1].normalizeLocal(); + computeVectors(scaledData, maxRows[1], 2, 0, 1); + break; + case 2: + maxRows[2].normalizeLocal(); + computeVectors(scaledData, maxRows[2], 0, 1, 2); + break; + } + + // Rescale the values back to the original size. + if (maxMagnitude > 1f) { + for (i = 0; i < 3; i++) { + eigenValues[i] *= maxMagnitude; + } + } + } + + /** + * Scale the matrix so its entries are in [-1,1]. The scaling is applied + * only when at least one matrix entry has magnitude larger than 1. + * + * @return the max magnitude in this matrix + */ + private float scaleMatrix(Matrix3f mat) { + + float max = FastMath.abs(mat.m00); + float abs = FastMath.abs(mat.m01); + + if (abs > max) { + max = abs; + } + abs = FastMath.abs(mat.m02); + if (abs > max) { + max = abs; + } + abs = FastMath.abs(mat.m11); + if (abs > max) { + max = abs; + } + abs = FastMath.abs(mat.m12); + if (abs > max) { + max = abs; + } + abs = FastMath.abs(mat.m22); + if (abs > max) { + max = abs; + } + + if (max > 1f) { + float fInvMax = 1f / max; + mat.multLocal(fInvMax); + } + + return max; + } + + /** + * Compute the eigenvectors of the given Matrix, using the + * @param mat + * @param vect + * @param index1 + * @param index2 + * @param index3 + */ + private void computeVectors(Matrix3f mat, Vector3f vect, int index1, + int index2, int index3) { + Vector3f vectorU = new Vector3f(), vectorV = new Vector3f(); + Vector3f.generateComplementBasis(vectorU, vectorV, vect); + + Vector3f tempVect = mat.mult(vectorU); + float p00 = eigenValues[index3] - vectorU.dot(tempVect); + float p01 = vectorV.dot(tempVect); + float p11 = eigenValues[index3] - vectorV.dot(mat.mult(vectorV)); + float invLength; + float max = FastMath.abs(p00); + int row = 0; + float fAbs = FastMath.abs(p01); + if (fAbs > max) { + max = fAbs; + } + fAbs = FastMath.abs(p11); + if (fAbs > max) { + max = fAbs; + row = 1; + } + + if (max >= FastMath.ZERO_TOLERANCE) { + if (row == 0) { + invLength = FastMath.invSqrt(p00 * p00 + p01 * p01); + p00 *= invLength; + p01 *= invLength; + vectorU.mult(p01, eigenVectors[index3]) + .addLocal(vectorV.mult(p00)); + } else { + invLength = FastMath.invSqrt(p11 * p11 + p01 * p01); + p11 *= invLength; + p01 *= invLength; + vectorU.mult(p11, eigenVectors[index3]) + .addLocal(vectorV.mult(p01)); + } + } else { + if (row == 0) { + eigenVectors[index3] = vectorV; + } else { + eigenVectors[index3] = vectorU; + } + } + + Vector3f vectorS = vect.cross(eigenVectors[index3]); + mat.mult(vect, tempVect); + p00 = eigenValues[index1] - vect.dot(tempVect); + p01 = vectorS.dot(tempVect); + p11 = eigenValues[index1] - vectorS.dot(mat.mult(vectorS)); + max = FastMath.abs(p00); + row = 0; + fAbs = FastMath.abs(p01); + if (fAbs > max) { + max = fAbs; + } + fAbs = FastMath.abs(p11); + if (fAbs > max) { + max = fAbs; + row = 1; + } + + if (max >= FastMath.ZERO_TOLERANCE) { + if (row == 0) { + invLength = FastMath.invSqrt(p00 * p00 + p01 * p01); + p00 *= invLength; + p01 *= invLength; + eigenVectors[index1] = vect.mult(p01).add(vectorS.mult(p00)); + } else { + invLength = FastMath.invSqrt(p11 * p11 + p01 * p01); + p11 *= invLength; + p01 *= invLength; + eigenVectors[index1] = vect.mult(p11).add(vectorS.mult(p01)); + } + } else { + if (row == 0) { + eigenVectors[index1].set(vectorS); + } else { + eigenVectors[index1].set(vect); + } + } + + eigenVectors[index3].cross(eigenVectors[index1], eigenVectors[index2]); + } + + /** + * Check the rank of the given Matrix to determine if it is positive. While + * doing so, store the max magnitude entry in the given float store and the + * max row of the matrix in the Vector store. + * + * @param matrix + * the Matrix3f to analyze. + * @param maxMagnitudeStore + * a float array in which to store (in the 0th position) the max + * magnitude entry of the matrix. + * @param maxRowStore + * a Vector3f to store the values of the row of the matrix + * containing the max magnitude entry. + * @return true if the given matrix has a non 0 rank. + */ + private boolean positiveRank(Matrix3f matrix, float[] maxMagnitudeStore, Vector3f maxRowStore) { + // Locate the maximum-magnitude entry of the matrix. + maxMagnitudeStore[0] = -1f; + int iRow, iCol, iMaxRow = -1; + for (iRow = 0; iRow < 3; iRow++) { + for (iCol = iRow; iCol < 3; iCol++) { + float fAbs = FastMath.abs(matrix.get(iRow, iCol)); + if (fAbs > maxMagnitudeStore[0]) { + maxMagnitudeStore[0] = fAbs; + iMaxRow = iRow; + } + } + } + + // Return the row containing the maximum, to be used for eigenvector + // construction. + maxRowStore.set(matrix.getRow(iMaxRow)); + + return maxMagnitudeStore[0] >= FastMath.ZERO_TOLERANCE; + } + + /** + * Generate the base eigen values of the given matrix using double precision + * math. + * + * @param mat + * the Matrix3f to analyze. + * @param rootsStore + * a double array to store the results in. Must be at least + * length 3. + */ + private void computeRoots(Matrix3f mat, double[] rootsStore) { + // Convert the unique matrix entries to double precision. + double a = mat.m00, b = mat.m01, c = mat.m02, + d = mat.m11, e = mat.m12, + f = mat.m22; + + // The characteristic equation is x^3 - c2*x^2 + c1*x - c0 = 0. The + // eigenvalues are the roots to this equation, all guaranteed to be + // real-valued, because the matrix is symmetric. + double char0 = a * d * f + 2.0 * b * c * e - a + * e * e - d * c * c - f * b * b; + + double char1 = a * d - b * b + a * f - c * c + + d * f - e * e; + + double char2 = a + d + f; + + // Construct the parameters used in classifying the roots of the + // equation and in solving the equation for the roots in closed form. + double char2Div3 = char2 * ONE_THIRD_DOUBLE; + double abcDiv3 = (char1 - char2 * char2Div3) * ONE_THIRD_DOUBLE; + if (abcDiv3 > 0.0) { + abcDiv3 = 0.0; + } + + double mbDiv2 = 0.5 * (char0 + char2Div3 * (2.0 * char2Div3 * char2Div3 - char1)); + + double q = mbDiv2 * mbDiv2 + abcDiv3 * abcDiv3 * abcDiv3; + if (q > 0.0) { + q = 0.0; + } + + // Compute the eigenvalues by solving for the roots of the polynomial. + double magnitude = Math.sqrt(-abcDiv3); + double angle = Math.atan2(Math.sqrt(-q), mbDiv2) * ONE_THIRD_DOUBLE; + double cos = Math.cos(angle); + double sin = Math.sin(angle); + double root0 = char2Div3 + 2.0 * magnitude * cos; + double root1 = char2Div3 - magnitude + * (cos + ROOT_THREE_DOUBLE * sin); + double root2 = char2Div3 - magnitude + * (cos - ROOT_THREE_DOUBLE * sin); + + // Sort in increasing order. + if (root1 >= root0) { + rootsStore[0] = root0; + rootsStore[1] = root1; + } else { + rootsStore[0] = root1; + rootsStore[1] = root0; + } + + if (root2 >= rootsStore[1]) { + rootsStore[2] = root2; + } else { + rootsStore[2] = rootsStore[1]; + if (root2 >= rootsStore[0]) { + rootsStore[1] = root2; + } else { + rootsStore[1] = rootsStore[0]; + rootsStore[0] = root2; + } + } + } + + public static void main(String[] args) { + Matrix3f mat = new Matrix3f(2, 1, 1, 1, 2, 1, 1, 1, 2); + Eigen3f eigenSystem = new Eigen3f(mat); + + logger.info("eigenvalues = "); + for (int i = 0; i < 3; i++) + logger.log(Level.FINE, "{0} ", eigenSystem.getEigenValue(i)); + + logger.info("eigenvectors = "); + for (int i = 0; i < 3; i++) { + Vector3f vector = eigenSystem.getEigenVector(i); + logger.info(vector.toString()); + mat.setColumn(i, vector); + } + logger.info(mat.toString()); + + // eigenvalues = + // 1.000000 1.000000 4.000000 + // eigenvectors = + // 0.411953 0.704955 0.577350 + // 0.404533 -0.709239 0.577350 + // -0.816485 0.004284 0.577350 + } + + public float getEigenValue(int i) { + return eigenValues[i]; + } + + public Vector3f getEigenVector(int i) { + return eigenVectors[i]; + } + + public float[] getEigenValues() { + return eigenValues; + } + + public Vector3f[] getEigenVectors() { + return eigenVectors; + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/FastMath.java b/jme3-core/src/main/java/com/jme3/math/FastMath.java new file mode 100644 index 000000000..2165f62fc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/FastMath.java @@ -0,0 +1,959 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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); + } + + /** + * Linear extrapolation from startValue to endValue by the given scale. + * if scale is between 0 and 1 this method returns the same result as interpolateLinear + * if the scale is over 1 the value is linearly extrapolated. + * Note that the end value is the value for a scale of 1. + * @param scale the scale for extrapolation + * @param startValue the starting value (scale = 0) + * @param endValue the end value (scale = 1) + * @return an extrapolation for the given parameters + */ + public static float extrapolateLinear(float scale, float startValue, float endValue) { +// if (scale <= 0f) { +// return startValue; +// } + return ((1f - scale) * startValue) + (scale * endValue); + } + + /** + * Linear extrapolation from startValue to endValue by the given scale. + * if scale is between 0 and 1 this method returns the same result as interpolateLinear + * if the scale is over 1 the value is linearly extrapolated. + * Note that the end value is the value for a scale of 1. + * @param scale the scale for extrapolation + * @param startValue the starting value (scale = 0) + * @param endValue the end value (scale = 1) + * @param store an initialized vector to store the return value + * @return an extrapolation for the given parameters + */ + public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vector3f endValue, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } +// if (scale <= 1f) { +// return interpolateLinear(scale, startValue, endValue, store); +// } + store.x = extrapolateLinear(scale, startValue.x, endValue.x); + store.y = extrapolateLinear(scale, startValue.y, endValue.y); + store.z = extrapolateLinear(scale, startValue.z, endValue.z); + return store; + } + + /** + * Linear extrapolation from startValue to endValue by the given scale. + * if scale is between 0 and 1 this method returns the same result as interpolateLinear + * if the scale is over 1 the value is linearly extrapolated. + * Note that the end value is the value for a scale of 1. + * @param scale the scale for extrapolation + * @param startValue the starting value (scale = 0) + * @param endValue the end value (scale = 1) + * @return an extrapolation for the given parameters + */ + public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vector3f endValue) { + return extrapolateLinear(scale, startValue, endValue, null); + } + + /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation. + * here is the interpolation matrix + * m = [ 0.0 1.0 0.0 0.0 ] + * [-T 0.0 T 0.0 ] + * [ 2T T-3 3-2T -T ] + * [-T 2-T T-2 T ] + * where T is the curve tension + * the result is a value between p1 and p2, t=0 for p1, t=1 for p2 + * @param u value from 0 to 1 + * @param T The tension of the curve + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return catmull-Rom interpolation + */ + public static float interpolateCatmullRom(float u, float T, float p0, float p1, float p2, float p3) { + float c1, c2, c3, c4; + c1 = p1; + c2 = -1.0f * T * p0 + T * p2; + c3 = 2 * T * p0 + (T - 3) * p1 + (3 - 2 * T) * p2 + -T * p3; + c4 = -T * p0 + (2 - T) * p1 + (T - 2) * p2 + T * p3; + + return (float) (((c4 * u + c3) * u + c2) * u + c1); + } + + /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation. + * here is the interpolation matrix + * m = [ 0.0 1.0 0.0 0.0 ] + * [-T 0.0 T 0.0 ] + * [ 2T T-3 3-2T -T ] + * [-T 2-T T-2 T ] + * where T is the tension of the curve + * the result is a value between p1 and p2, t=0 for p1, t=1 for p2 + * @param u value from 0 to 1 + * @param T The tension of the curve + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @param store a Vector3f to store the result + * @return catmull-Rom interpolation + */ + public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + store.x = interpolateCatmullRom(u, T, p0.x, p1.x, p2.x, p3.x); + store.y = interpolateCatmullRom(u, T, p0.y, p1.y, p2.y, p3.y); + store.z = interpolateCatmullRom(u, T, p0.z, p1.z, p2.z, p3.z); + return store; + } + + /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation. + * here is the interpolation matrix + * m = [ 0.0 1.0 0.0 0.0 ] + * [-T 0.0 T 0.0 ] + * [ 2T T-3 3-2T -T ] + * [-T 2-T T-2 T ] + * where T is the tension of the curve + * the result is a value between p1 and p2, t=0 for p1, t=1 for p2 + * @param u value from 0 to 1 + * @param T The tension of the curve + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return catmull-Rom interpolation + */ + public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) { + return interpolateCatmullRom(u, T, p0, p1, p2, p3, null); + } + + /**Interpolate a spline between at least 4 control points following the Bezier equation. + * here is the interpolation matrix + * m = [ -1.0 3.0 -3.0 1.0 ] + * [ 3.0 -6.0 3.0 0.0 ] + * [ -3.0 3.0 0.0 0.0 ] + * [ 1.0 0.0 0.0 0.0 ] + * where T is the curve tension + * the result is a value between p1 and p3, t=0 for p1, t=1 for p3 + * @param u value from 0 to 1 + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return Bezier interpolation + */ + public static float interpolateBezier(float u, float p0, float p1, float p2, float p3) { + float oneMinusU = 1.0f - u; + float oneMinusU2 = oneMinusU * oneMinusU; + float u2 = u * u; + return p0 * oneMinusU2 * oneMinusU + + 3.0f * p1 * u * oneMinusU2 + + 3.0f * p2 * u2 * oneMinusU + + p3 * u2 * u; + } + + /**Interpolate a spline between at least 4 control points following the Bezier equation. + * here is the interpolation matrix + * m = [ -1.0 3.0 -3.0 1.0 ] + * [ 3.0 -6.0 3.0 0.0 ] + * [ -3.0 3.0 0.0 0.0 ] + * [ 1.0 0.0 0.0 0.0 ] + * where T is the tension of the curve + * the result is a value between p1 and p3, t=0 for p1, t=1 for p3 + * @param u value from 0 to 1 + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @param store a Vector3f to store the result + * @return Bezier interpolation + */ + public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + store.x = interpolateBezier(u, p0.x, p1.x, p2.x, p3.x); + store.y = interpolateBezier(u, p0.y, p1.y, p2.y, p3.y); + store.z = interpolateBezier(u, p0.z, p1.z, p2.z, p3.z); + return store; + } + + /**Interpolate a spline between at least 4 control points following the Bezier equation. + * here is the interpolation matrix + * m = [ -1.0 3.0 -3.0 1.0 ] + * [ 3.0 -6.0 3.0 0.0 ] + * [ -3.0 3.0 0.0 0.0 ] + * [ 1.0 0.0 0.0 0.0 ] + * where T is the tension of the curve + * the result is a value between p1 and p3, t=0 for p1, t=1 for p3 + * @param u value from 0 to 1 + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return Bezier interpolation + */ + public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) { + return interpolateBezier(u, p0, p1, p2, p3, null); + } + + /** + * Compute the lenght on a catmull rom spline between control point 1 and 2 + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @param startRange the starting range on the segment (use 0) + * @param endRange the end range on the segment (use 1) + * @param curveTension the curve tension + * @return the length of the segment + */ + public static float getCatmullRomP1toP2Length(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, float startRange, float endRange, float curveTension) { + + float epsilon = 0.001f; + float middleValue = (startRange + endRange) * 0.5f; + Vector3f start = p1.clone(); + if (startRange != 0) { + FastMath.interpolateCatmullRom(startRange, curveTension, p0, p1, p2, p3, start); + } + Vector3f end = p2.clone(); + if (endRange != 1) { + FastMath.interpolateCatmullRom(endRange, curveTension, p0, p1, p2, p3, end); + } + Vector3f middle = FastMath.interpolateCatmullRom(middleValue, curveTension, p0, p1, p2, p3); + float l = end.subtract(start).length(); + float l1 = middle.subtract(start).length(); + float l2 = end.subtract(middle).length(); + float len = l1 + l2; + if (l + epsilon < len) { + l1 = getCatmullRomP1toP2Length(p0, p1, p2, p3, startRange, middleValue, curveTension); + l2 = getCatmullRomP1toP2Length(p0, p1, p2, p3, middleValue, endRange, curveTension); + } + l = l1 + l2; + return l; + } + + /** + * Compute the lenght on a bezier spline between control point 1 and 2 + * @param p0 control point 0 + * @param p1 control point 1 + * @param p2 control point 2 + * @param p3 control point 3 + * @return the length of the segment + */ + public static float getBezierP1toP2Length(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) { + float delta = 0.02f, t = 0.0f, result = 0.0f; + Vector3f v1 = p0.clone(), v2 = new Vector3f(); + while (t <= 1.0f) { + FastMath.interpolateBezier(t, p0, p1, p2, p3, v2); + result += v1.subtractLocal(v2).length(); + v1.set(v2); + t += delta; + } + return result; + } + + /** + * Returns the arc cosine of a value.
    + * 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 value to arc cosine. + * @return The angle, in radians. + * @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 a value.
    + * 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 value to arc sine. + * @return the angle in radians. + * @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 atan + * @see java.lang.Math#atan(double) + */ + public static float atan(float fValue) { + return (float) Math.atan(fValue); + } + + /** + * A direct call to Math.atan2. + * @param fY + * @param fX + * @return Math.atan2(fY,fX) + * @see java.lang.Math#atan2(double, double) + */ + public static float atan2(float fY, float fX) { + return (float) Math.atan2(fY, fX); + } + + /** + * Rounds a fValue up. A call to Math.ceil + * @param fValue The value. + * @return The fValue rounded up + * @see java.lang.Math#ceil(double) + */ + public static float ceil(float fValue) { + return (float) Math.ceil(fValue); + } + + /** + * Returns cosine of an angle. Direct call to java.lang.Math + * @see Math#cos(double) + * @param v The angle to cosine. + * @return the cosine of the angle. + */ + public static float cos(float v) { + return (float) Math.cos(v); + } + + /** + * Returns the sine of an angle. Direct call to java.lang.Math + * @see Math#sin(double) + * @param v The angle to sine. + * @return the sine of the angle. + */ + public static float sin(float v) { + return (float) Math.sin(v); + } + + /** + * Returns E^fValue + * @param fValue Value to raise to a power. + * @return The value E^fValue + * @see java.lang.Math#exp(double) + */ + public static float exp(float fValue) { + return (float) Math.exp(fValue); + } + + /** + * Returns Absolute value of a float. + * @param fValue The value to abs. + * @return The abs of the value. + * @see java.lang.Math#abs(float) + */ + public static float abs(float fValue) { + if (fValue < 0) { + return -fValue; + } + return fValue; + } + + /** + * Returns a number rounded down. + * @param fValue The value to round + * @return The given number rounded down + * @see java.lang.Math#floor(double) + */ + public static float floor(float fValue) { + return (float) Math.floor(fValue); + } + + /** + * Returns 1/sqrt(fValue) + * @param fValue The value to process. + * @return 1/sqrt(fValue) + * @see java.lang.Math#sqrt(double) + */ + public static float invSqrt(float fValue) { + return (float) (1.0f / Math.sqrt(fValue)); + } + + public static float fastInvSqrt(float x) { + float xhalf = 0.5f * x; + int i = Float.floatToIntBits(x); // get bits for floating value + i = 0x5f375a86 - (i >> 1); // gives initial guess y0 + x = Float.intBitsToFloat(i); // convert bits back to float + x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy + return x; + } + + /** + * Returns the log base E of a value. + * @param fValue The value to log. + * @return The log of fValue base E + * @see java.lang.Math#log(double) + */ + public static float log(float fValue) { + return (float) Math.log(fValue); + } + + /** + * Returns the logarithm of value with given base, calculated as log(value)/log(base), + * so that pow(base, return)==value (contributed by vear) + * @param value The value to log. + * @param base Base of logarithm. + * @return The logarithm of value with given base + */ + public static float log(float value, float base) { + return (float) (Math.log(value) / Math.log(base)); + } + + /** + * Returns a number raised to an exponent power. fBase^fExponent + * @param fBase The base value (IE 2) + * @param fExponent The exponent value (IE 3) + * @return base raised to exponent (IE 8) + * @see java.lang.Math#pow(double, double) + */ + public static float pow(float fBase, float fExponent) { + return (float) Math.pow(fBase, fExponent); + } + + /** + * Returns the value squared. fValue ^ 2 + * @param fValue The vaule to square. + * @return The square of the given value. + */ + public static float sqr(float fValue) { + return fValue * fValue; + } + + /** + * Returns the square root of a given value. + * @param fValue The value to sqrt. + * @return The square root of the given value. + * @see java.lang.Math#sqrt(double) + */ + public static float sqrt(float fValue) { + return (float) Math.sqrt(fValue); + } + + /** + * Returns the tangent of a value. If USE_FAST_TRIG is enabled, an approximate value + * is returned. Otherwise, a direct value is used. + * @param fValue The value to tangent, in radians. + * @return The tangent of fValue. + * @see java.lang.Math#tan(double) + */ + public static float tan(float fValue) { + return (float) Math.tan(fValue); + } + + /** + * Returns 1 if the number is positive, -1 if the number is negative, and 0 otherwise + * @param iValue The integer to examine. + * @return The integer's sign. + */ + public static int sign(int iValue) { + if (iValue > 0) { + return 1; + } + if (iValue < 0) { + return -1; + } + return 0; + } + + /** + * Returns 1 if the number is positive, -1 if the number is negative, and 0 otherwise + * @param fValue The float to examine. + * @return The float's sign. + */ + public static float sign(float fValue) { + return Math.signum(fValue); + } + + /** + * Given 3 points in a 2d plane, this function computes if the points going from A-B-C + * are moving counter clock wise. + * @param p0 Point 0. + * @param p1 Point 1. + * @param p2 Point 2. + * @return 1 If they are CCW, -1 if they are not CCW, 0 if p2 is between p0 and p1. + */ + public static int counterClockwise(Vector2f p0, Vector2f p1, Vector2f p2) { + float dx1, dx2, dy1, dy2; + dx1 = p1.x - p0.x; + dy1 = p1.y - p0.y; + dx2 = p2.x - p0.x; + dy2 = p2.y - p0.y; + if (dx1 * dy2 > dy1 * dx2) { + return 1; + } + if (dx1 * dy2 < dy1 * dx2) { + return -1; + } + if ((dx1 * dx2 < 0) || (dy1 * dy2 < 0)) { + return -1; + } + if ((dx1 * dx1 + dy1 * dy1) < (dx2 * dx2 + dy2 * dy2)) { + return 1; + } + return 0; + } + + /** + * Test if a point is inside a triangle. 1 if the point is on the ccw side, + * -1 if the point is on the cw side, and 0 if it is on neither. + * @param t0 First point of the triangle. + * @param t1 Second point of the triangle. + * @param t2 Third point of the triangle. + * @param p The point to test. + * @return Value 1 or -1 if inside triangle, 0 otherwise. + */ + public static int pointInsideTriangle(Vector2f t0, Vector2f t1, Vector2f t2, Vector2f p) { + int val1 = counterClockwise(t0, t1, p); + if (val1 == 0) { + return 1; + } + int val2 = counterClockwise(t1, t2, p); + if (val2 == 0) { + return 1; + } + if (val2 != val1) { + return 0; + } + int val3 = counterClockwise(t2, t0, p); + if (val3 == 0) { + return 1; + } + if (val3 != val1) { + return 0; + } + return val3; + } + + /** + * A method that computes normal for a triangle defined by three vertices. + * @param v1 first vertex + * @param v2 second vertex + * @param v3 third vertex + * @return a normal for the face + */ + public static Vector3f computeNormal(Vector3f v1, Vector3f v2, Vector3f v3) { + Vector3f a1 = v1.subtract(v2); + Vector3f a2 = v3.subtract(v2); + return a2.crossLocal(a1).normalizeLocal(); + } + + /** + * Returns the determinant of a 4x4 matrix. + */ + public static float determinant(double m00, double m01, double m02, + double m03, double m10, double m11, double m12, double m13, + double m20, double m21, double m22, double m23, double m30, + double m31, double m32, double m33) { + + double det01 = m20 * m31 - m21 * m30; + double det02 = m20 * m32 - m22 * m30; + double det03 = m20 * m33 - m23 * m30; + double det12 = m21 * m32 - m22 * m31; + double det13 = m21 * m33 - m23 * m31; + double det23 = m22 * m33 - m23 * m32; + return (float) (m00 * (m11 * det23 - m12 * det13 + m13 * det12) - m01 + * (m10 * det23 - m12 * det03 + m13 * det02) + m02 + * (m10 * det13 - m11 * det03 + m13 * det01) - m03 + * (m10 * det12 - m11 * det02 + m12 * det01)); + } + + /** + * Returns a random float between 0 and 1. + * + * @return A random float between 0.0f (inclusive) to + * 1.0f (exclusive). + */ + public static float nextRandomFloat() { + return rand.nextFloat(); + } + + /** + * Returns a random integer 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) { + if (store == null) { + store = new Vector3f(); + } + 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) { + if (store == null) { + store = new Vector3f(); + } + 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) { + if (store == null) { + store = new Vector3f(); + } + 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) { + if (store == null) { + store = new Vector3f(); + } + 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
    broken link + * + * @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/jme3-core/src/main/java/com/jme3/math/Line.java b/jme3-core/src/main/java/com/jme3/math/Line.java new file mode 100644 index 000000000..1ca99b4be --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Line.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.FloatBuffer; + +/** + * 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, java.io.Serializable { + + static final long serialVersionUID = 1; + + 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(); + + Vector3f compVec1 = vars.vect1; + Vector3f compVec2 = vars.vect2; + + point.subtract(origin, compVec1); + float lineParameter = direction.dot(compVec1); + origin.add(direction.mult(lineParameter, compVec2), compVec2); + compVec2.subtract(point, compVec1); + float len = compVec1.lengthSquared(); + vars.release(); + return len; + } + + public float distance(Vector3f point) { + return FastMath.sqrt(distanceSquared(point)); + } + + public void orthogonalLineFit(FloatBuffer points) { + if (points == null) { + return; + } + + TempVars vars = TempVars.get(); + + Vector3f compVec1 = vars.vect1; + Vector3f compVec2 = vars.vect2; + Matrix3f compMat1 = vars.tempMat3; + Eigen3f compEigen1 = vars.eigen; + + points.rewind(); + + // compute average of points + int length = points.remaining() / 3; + + BufferUtils.populateFromBuffer(origin, points, 0); + for (int i = 1; i < length; i++) { + BufferUtils.populateFromBuffer(compVec1, points, i); + origin.addLocal(compVec1); + } + + origin.multLocal(1f / (float) length); + + // compute sums of products + float sumXX = 0.0f, sumXY = 0.0f, sumXZ = 0.0f; + float sumYY = 0.0f, sumYZ = 0.0f, sumZZ = 0.0f; + + points.rewind(); + for (int i = 0; i < length; i++) { + BufferUtils.populateFromBuffer(compVec1, points, i); + compVec1.subtract(origin, compVec2); + sumXX += compVec2.x * compVec2.x; + sumXY += compVec2.x * compVec2.y; + sumXZ += compVec2.x * compVec2.z; + sumYY += compVec2.y * compVec2.y; + sumYZ += compVec2.y * compVec2.z; + sumZZ += compVec2.z * compVec2.z; + } + + //find the smallest eigen vector for the direction vector + compMat1.m00 = sumYY + sumZZ; + compMat1.m01 = -sumXY; + compMat1.m02 = -sumXZ; + compMat1.m10 = -sumXY; + compMat1.m11 = sumXX + sumZZ; + compMat1.m12 = -sumYZ; + compMat1.m20 = -sumXZ; + compMat1.m21 = -sumYZ; + compMat1.m22 = sumXX + sumYY; + + compEigen1.calculateEigen(compMat1); + direction = compEigen1.getEigenVector(0); + + vars.release(); + } + + /** + * + * 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/jme3-core/src/main/java/com/jme3/math/LineSegment.java b/jme3-core/src/main/java/com/jme3/math/LineSegment.java new file mode 100644 index 000000000..b01edeb50 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/LineSegment.java @@ -0,0 +1,631 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +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, java.io.Serializable { + + static final long serialVersionUID = 1; + + private Vector3f origin; + private Vector3f direction; + private float extent; + + public LineSegment() { + origin = new Vector3f(); + direction = new Vector3f(); + } + + public LineSegment(LineSegment ls) { + this.origin = new Vector3f(ls.getOrigin()); + this.direction = new Vector3f(ls.getDirection()); + this.extent = ls.getExtent(); + } + + /** + *

    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() * 0.5f; + direction.normalizeLocal(); + } + + public void set(LineSegment ls) { + this.origin = new Vector3f(ls.getOrigin()); + this.direction = new Vector3f(ls.getDirection()); + this.extent = ls.getExtent(); + } + + public float distance(Vector3f point) { + return FastMath.sqrt(distanceSquared(point)); + } + + public float distance(LineSegment ls) { + return FastMath.sqrt(distanceSquared(ls)); + } + + public float distance(Ray r) { + return FastMath.sqrt(distanceSquared(r)); + } + + public float distanceSquared(Vector3f point) { + TempVars vars = TempVars.get(); + Vector3f compVec1 = vars.vect1; + + point.subtract(origin, compVec1); + float segmentParameter = direction.dot(compVec1); + + if (-extent < segmentParameter) { + if (segmentParameter < extent) { + origin.add(direction.mult(segmentParameter, compVec1), + compVec1); + } else { + origin.add(direction.mult(extent, compVec1), compVec1); + } + } else { + origin.subtract(direction.mult(extent, compVec1), compVec1); + } + + compVec1.subtractLocal(point); + float len = compVec1.lengthSquared(); + vars.release(); + return len; + } + + public float distanceSquared(LineSegment test) { + TempVars vars = TempVars.get(); + Vector3f compVec1 = vars.vect1; + + origin.subtract(test.getOrigin(), compVec1); + float negativeDirectionDot = -(direction.dot(test.getDirection())); + float diffThisDot = compVec1.dot(direction); + float diffTestDot = -(compVec1.dot(test.getDirection())); + float lengthOfDiff = compVec1.lengthSquared(); + vars.release(); + float determinant = FastMath.abs(1.0f - negativeDirectionDot + * negativeDirectionDot); + float s0, s1, squareDistance, extentDeterminant0, extentDeterminant1, tempS0, tempS1; + + if (determinant >= FastMath.FLT_EPSILON) { + // segments are not parallel + s0 = negativeDirectionDot * diffTestDot - diffThisDot; + s1 = negativeDirectionDot * diffThisDot - diffTestDot; + extentDeterminant0 = extent * determinant; + extentDeterminant1 = test.getExtent() * determinant; + + if (s0 >= -extentDeterminant0) { + if (s0 <= extentDeterminant0) { + if (s1 >= -extentDeterminant1) { + if (s1 <= extentDeterminant1) // region 0 (interior) + { + // minimum at two interior points of 3D lines + float inverseDeterminant = ((float) 1.0) + / determinant; + s0 *= inverseDeterminant; + s1 *= inverseDeterminant; + squareDistance = s0 + * (s0 + negativeDirectionDot * s1 + (2.0f) * diffThisDot) + + s1 + * (negativeDirectionDot * s0 + s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } else // region 3 (side) + { + s1 = test.getExtent(); + tempS0 = -(negativeDirectionDot * s1 + diffThisDot); + if (tempS0 < -extent) { + s0 = -extent; + squareDistance = s0 * (s0 - (2.0f) * tempS0) + + s1 * (s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } else if (tempS0 <= extent) { + s0 = tempS0; + squareDistance = -s0 * s0 + s1 + * (s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } else { + s0 = extent; + squareDistance = s0 * (s0 - (2.0f) * tempS0) + + s1 * (s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } + } + } else // region 7 (side) + { + s1 = -test.getExtent(); + tempS0 = -(negativeDirectionDot * s1 + diffThisDot); + if (tempS0 < -extent) { + s0 = -extent; + squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1 + * (s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } else if (tempS0 <= extent) { + s0 = tempS0; + squareDistance = -s0 * s0 + s1 + * (s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } else { + s0 = extent; + squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1 + * (s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } + } + } else { + if (s1 >= -extentDeterminant1) { + if (s1 <= extentDeterminant1) // region 1 (side) + { + s0 = extent; + tempS1 = -(negativeDirectionDot * s0 + diffTestDot); + if (tempS1 < -test.getExtent()) { + s1 = -test.getExtent(); + squareDistance = s1 * (s1 - (2.0f) * tempS1) + + s0 * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } else if (tempS1 <= test.getExtent()) { + s1 = tempS1; + squareDistance = -s1 * s1 + s0 + * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } else { + s1 = test.getExtent(); + squareDistance = s1 * (s1 - (2.0f) * tempS1) + + s0 * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } + } else // region 2 (corner) + { + s1 = test.getExtent(); + tempS0 = -(negativeDirectionDot * s1 + diffThisDot); + if (tempS0 < -extent) { + s0 = -extent; + squareDistance = s0 * (s0 - (2.0f) * tempS0) + + s1 * (s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } else if (tempS0 <= extent) { + s0 = tempS0; + squareDistance = -s0 * s0 + s1 + * (s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } else { + s0 = extent; + tempS1 = -(negativeDirectionDot * s0 + diffTestDot); + if (tempS1 < -test.getExtent()) { + s1 = -test.getExtent(); + squareDistance = s1 + * (s1 - (2.0f) * tempS1) + s0 + * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } else if (tempS1 <= test.getExtent()) { + s1 = tempS1; + squareDistance = -s1 * s1 + s0 + * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } else { + s1 = test.getExtent(); + squareDistance = s1 + * (s1 - (2.0f) * tempS1) + s0 + * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } + } + } + } else // region 8 (corner) + { + s1 = -test.getExtent(); + tempS0 = -(negativeDirectionDot * s1 + diffThisDot); + if (tempS0 < -extent) { + s0 = -extent; + squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1 + * (s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } else if (tempS0 <= extent) { + s0 = tempS0; + squareDistance = -s0 * s0 + s1 + * (s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } else { + s0 = extent; + tempS1 = -(negativeDirectionDot * s0 + diffTestDot); + if (tempS1 > test.getExtent()) { + s1 = test.getExtent(); + squareDistance = s1 * (s1 - (2.0f) * tempS1) + + s0 * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } else if (tempS1 >= -test.getExtent()) { + s1 = tempS1; + squareDistance = -s1 * s1 + s0 + * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } else { + s1 = -test.getExtent(); + squareDistance = s1 * (s1 - (2.0f) * tempS1) + + s0 * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } + } + } + } + } else { + if (s1 >= -extentDeterminant1) { + if (s1 <= extentDeterminant1) // region 5 (side) + { + s0 = -extent; + tempS1 = -(negativeDirectionDot * s0 + diffTestDot); + if (tempS1 < -test.getExtent()) { + s1 = -test.getExtent(); + squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0 + * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } else if (tempS1 <= test.getExtent()) { + s1 = tempS1; + squareDistance = -s1 * s1 + s0 + * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } else { + s1 = test.getExtent(); + squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0 + * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } + } else // region 4 (corner) + { + s1 = test.getExtent(); + tempS0 = -(negativeDirectionDot * s1 + diffThisDot); + if (tempS0 > extent) { + s0 = extent; + squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1 + * (s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } else if (tempS0 >= -extent) { + s0 = tempS0; + squareDistance = -s0 * s0 + s1 + * (s1 + (2.0f) * diffTestDot) + + lengthOfDiff; + } else { + s0 = -extent; + tempS1 = -(negativeDirectionDot * s0 + diffTestDot); + if (tempS1 < -test.getExtent()) { + s1 = -test.getExtent(); + squareDistance = s1 * (s1 - (2.0f) * tempS1) + + s0 * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } else if (tempS1 <= test.getExtent()) { + s1 = tempS1; + squareDistance = -s1 * s1 + s0 + * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } else { + s1 = test.getExtent(); + squareDistance = s1 * (s1 - (2.0f) * tempS1) + + s0 * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } + } + } + } else // region 6 (corner) + { + s1 = -test.getExtent(); + tempS0 = -(negativeDirectionDot * s1 + diffThisDot); + if (tempS0 > extent) { + s0 = extent; + squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1 + * (s1 + (2.0f) * diffTestDot) + lengthOfDiff; + } else if (tempS0 >= -extent) { + s0 = tempS0; + squareDistance = -s0 * s0 + s1 + * (s1 + (2.0f) * diffTestDot) + lengthOfDiff; + } else { + s0 = -extent; + tempS1 = -(negativeDirectionDot * s0 + diffTestDot); + if (tempS1 < -test.getExtent()) { + s1 = -test.getExtent(); + squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0 + * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } else if (tempS1 <= test.getExtent()) { + s1 = tempS1; + squareDistance = -s1 * s1 + s0 + * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } else { + s1 = test.getExtent(); + squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0 + * (s0 + (2.0f) * diffThisDot) + + lengthOfDiff; + } + } + } + } + } else { + // The segments are parallel. The average b0 term is designed to + // ensure symmetry of the function. That is, dist(seg0,seg1) and + // dist(seg1,seg0) should produce the same number.get + float extentSum = extent + test.getExtent(); + float sign = (negativeDirectionDot > 0.0f ? -1.0f : 1.0f); + float averageB0 = (0.5f) * (diffThisDot - sign * diffTestDot); + float lambda = -averageB0; + if (lambda < -extentSum) { + lambda = -extentSum; + } else if (lambda > extentSum) { + lambda = extentSum; + } + + squareDistance = lambda * (lambda + (2.0f) * averageB0) + + lengthOfDiff; + } + + return FastMath.abs(squareDistance); + } + + public float distanceSquared(Ray r) { + Vector3f kDiff = r.getOrigin().subtract(origin); + float fA01 = -r.getDirection().dot(direction); + float fB0 = kDiff.dot(r.getDirection()); + float fB1 = -kDiff.dot(direction); + float fC = kDiff.lengthSquared(); + float fDet = FastMath.abs(1.0f - fA01 * fA01); + float fS0, fS1, fSqrDist, fExtDet; + + if (fDet >= FastMath.FLT_EPSILON) { + // The ray and segment are not parallel. + fS0 = fA01 * fB1 - fB0; + fS1 = fA01 * fB0 - fB1; + fExtDet = extent * fDet; + + if (fS0 >= (float) 0.0) { + if (fS1 >= -fExtDet) { + if (fS1 <= fExtDet) // region 0 + { + // minimum at interior points of ray and segment + float fInvDet = ((float) 1.0) / fDet; + fS0 *= fInvDet; + fS1 *= fInvDet; + fSqrDist = fS0 + * (fS0 + fA01 * fS1 + ((float) 2.0) * fB0) + + fS1 + * (fA01 * fS0 + fS1 + ((float) 2.0) * fB1) + fC; + } else // region 1 + { + fS1 = extent; + fS0 = -(fA01 * fS1 + fB0); + if (fS0 > (float) 0.0) { + fSqrDist = -fS0 * fS0 + fS1 + * (fS1 + ((float) 2.0) * fB1) + fC; + } else { + fS0 = (float) 0.0; + fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC; + } + } + } else // region 5 + { + fS1 = -extent; + fS0 = -(fA01 * fS1 + fB0); + if (fS0 > (float) 0.0) { + fSqrDist = -fS0 * fS0 + fS1 + * (fS1 + ((float) 2.0) * fB1) + fC; + } else { + fS0 = (float) 0.0; + fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC; + } + } + } else { + if (fS1 <= -fExtDet) // region 4 + { + fS0 = -(-fA01 * extent + fB0); + if (fS0 > (float) 0.0) { + fS1 = -extent; + fSqrDist = -fS0 * fS0 + fS1 + * (fS1 + ((float) 2.0) * fB1) + fC; + } else { + fS0 = (float) 0.0; + fS1 = -fB1; + if (fS1 < -extent) { + fS1 = -extent; + } else if (fS1 > extent) { + fS1 = extent; + } + fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC; + } + } else if (fS1 <= fExtDet) // region 3 + { + fS0 = (float) 0.0; + fS1 = -fB1; + if (fS1 < -extent) { + fS1 = -extent; + } else if (fS1 > extent) { + fS1 = extent; + } + fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC; + } else // region 2 + { + fS0 = -(fA01 * extent + fB0); + if (fS0 > (float) 0.0) { + fS1 = extent; + fSqrDist = -fS0 * fS0 + fS1 + * (fS1 + ((float) 2.0) * fB1) + fC; + } else { + fS0 = (float) 0.0; + fS1 = -fB1; + if (fS1 < -extent) { + fS1 = -extent; + } else if (fS1 > extent) { + fS1 = extent; + } + fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC; + } + } + } + } else { + // ray and segment are parallel + if (fA01 > (float) 0.0) { + // opposite direction vectors + fS1 = -extent; + } else { + // same direction vectors + fS1 = extent; + } + + fS0 = -(fA01 * fS1 + fB0); + if (fS0 > (float) 0.0) { + fSqrDist = -fS0 * fS0 + fS1 * (fS1 + ((float) 2.0) * fB1) + fC; + } else { + fS0 = (float) 0.0; + fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC; + } + } + return FastMath.abs(fSqrDist); + } + + public Vector3f getDirection() { + return direction; + } + + public void setDirection(Vector3f direction) { + this.direction = direction; + } + + public float getExtent() { + return extent; + } + + public void setExtent(float extent) { + this.extent = extent; + } + + public Vector3f getOrigin() { + return origin; + } + + public void setOrigin(Vector3f origin) { + this.origin = origin; + } + + // P+e*D + public Vector3f getPositiveEnd(Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + return origin.add((direction.mult(extent, store)), store); + } + + // P-e*D + public Vector3f getNegativeEnd(Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + return origin.subtract((direction.mult(extent, store)), store); + } + + public void write(JmeExporter e) throws IOException { + OutputCapsule capsule = e.getCapsule(this); + capsule.write(origin, "origin", Vector3f.ZERO); + capsule.write(direction, "direction", Vector3f.ZERO); + capsule.write(extent, "extent", 0); + } + + public void read(JmeImporter e) throws IOException { + InputCapsule capsule = e.getCapsule(this); + origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone()); + direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone()); + extent = capsule.readFloat("extent", 0); + } + + @Override + public LineSegment clone() { + try { + LineSegment segment = (LineSegment) super.clone(); + segment.direction = direction.clone(); + segment.origin = origin.clone(); + return segment; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + /** + *

    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/jme3-core/src/main/java/com/jme3/math/Matrix3f.java b/jme3-core/src/main/java/com/jme3/math/Matrix3f.java new file mode 100644 index 000000000..ca9e7287b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Matrix3f.java @@ -0,0 +1,1434 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.logging.Logger; + +/** + * 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, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Matrix3f.class.getName()); + protected float m00, m01, m02; + protected float m10, m11, m12; + protected float m20, m21, m22; + public static final Matrix3f ZERO = new Matrix3f(0, 0, 0, 0, 0, 0, 0, 0, 0); + public static final Matrix3f IDENTITY = new Matrix3f(); + + /** + * Constructor instantiates a new 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). + */ + @SuppressWarnings("fallthrough") + public float get(int i, int j) { + switch (i) { + case 0: + switch (j) { + case 0: + return m00; + case 1: + return m01; + case 2: + return m02; + } + case 1: + switch (j) { + case 0: + return m10; + case 1: + return m11; + case 2: + return m12; + } + case 2: + switch (j) { + case 0: + return m20; + case 1: + return m21; + case 2: + return m22; + } + } + + logger.warning("Invalid matrix index."); + throw new IllegalArgumentException("Invalid indices into matrix."); + } + + /** + * 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()."); + } + } + + /** + * Normalize this matrix and store the result in the store parameter that is + * returned. + * + * Note that the original matrix is not altered. + * + * @param store the matrix to store the result of the normalization. If this + * parameter is null a new one is created + * @return the normalized matrix + */ + public Matrix3f normalize(Matrix3f store) { + if (store == null) { + store = new Matrix3f(); + } + + float mag = 1.0f / FastMath.sqrt( + m00 * m00 + + m10 * m10 + + m20 * m20); + + store.m00 = m00 * mag; + store.m10 = m10 * mag; + store.m20 = m20 * mag; + + mag = 1.0f / FastMath.sqrt( + m01 * m01 + + m11 * m11 + + m21 * m21); + + store.m01 = m01 * mag; + store.m11 = m11 * mag; + store.m21 = m21 * mag; + + store.m02 = store.m10 * store.m21 - store.m11 * store.m20; + store.m12 = store.m01 * store.m20 - store.m00 * store.m21; + store.m22 = store.m00 * store.m11 - store.m01 * store.m10; + return store; + } + + /** + * Normalize this matrix + * @return this matrix once normalized. + */ + public Matrix3f normalizeLocal() { + return normalize(this); + } + + /** + * 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); +// } + + TempVars vars = TempVars.get(); + + + fillFloatArray(vars.matrixWrite, columnMajor); + fb.put(vars.matrixWrite, 0, 9); + + vars.release(); + + return fb; + } + + public void fillFloatArray(float[] f, boolean columnMajor) { + if (columnMajor) { + f[ 0] = m00; + f[ 1] = m10; + f[ 2] = m20; + f[ 3] = m01; + f[ 4] = m11; + f[ 5] = m21; + f[ 6] = m02; + f[ 7] = m12; + f[ 8] = m22; + } else { + f[ 0] = m00; + f[ 1] = m01; + f[ 2] = m02; + f[ 3] = m10; + f[ 4] = m11; + f[ 5] = m12; + f[ 6] = m20; + f[ 7] = m21; + f[ 8] = m22; + } + } + + /** + * + * 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 + */ + @SuppressWarnings("fallthrough") + public Matrix3f set(int i, int j, float value) { + switch (i) { + case 0: + switch (j) { + case 0: + m00 = value; + return this; + case 1: + m01 = value; + return this; + case 2: + m02 = value; + return this; + } + case 1: + switch (j) { + case 0: + m10 = value; + return this; + case 1: + m11 = value; + return this; + case 2: + m12 = value; + return this; + } + case 2: + switch (j) { + case 0: + m20 = value; + return this; + case 1: + m21 = value; + return this; + case 2: + m22 = value; + return this; + } + } + + logger.warning("Invalid matrix index."); + throw new IllegalArgumentException("Invalid indices into matrix."); + } + + /** + * + * 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) <= 0f) { + return zero(); + } + + float f00 = m11 * m22 - m12 * m21; + float f01 = m02 * m21 - m01 * m22; + float f02 = m01 * m12 - m02 * m11; + float f10 = m12 * m20 - m10 * m22; + float f11 = m00 * m22 - m02 * m20; + float f12 = m02 * m10 - m00 * m12; + float f20 = m10 * m21 - m11 * m20; + float f21 = m01 * m20 - m00 * m21; + float f22 = m00 * m11 - m01 * m10; + + m00 = f00; + m01 = f01; + m02 = f02; + m10 = f10; + m11 = f11; + m12 = f12; + m20 = f20; + m21 = f21; + m22 = f22; + + multLocal(1f / det); + return this; + } + + /** + * Returns a new matrix representing the adjoint of this matrix. + * + * @return The adjoint matrix + */ + public Matrix3f adjoint() { + return adjoint(null); + } + + /** + * Places the adjoint of this matrix in store (creates store if null.) + * + * @param store + * The matrix to store the result in. If null, a new matrix is created. + * @return store + */ + public Matrix3f adjoint(Matrix3f store) { + if (store == null) { + store = new Matrix3f(); + } + + store.m00 = m11 * m22 - m12 * m21; + store.m01 = m02 * m21 - m01 * m22; + store.m02 = m01 * m12 - m02 * m11; + store.m10 = m12 * m20 - m10 * m22; + store.m11 = m00 * m22 - m02 * m20; + store.m12 = m02 * m10 - m00 * m12; + store.m20 = m10 * m21 - m11 * m20; + store.m21 = m01 * m20 - m00 * m21; + store.m22 = m00 * m11 - m01 * m10; + + return store; + } + + /** + * determinant generates the determinant of this matrix. + * + * @return the determinant + */ + public float determinant() { + float fCo00 = m11 * m22 - m12 * m21; + float fCo10 = m12 * m20 - m10 * m22; + float fCo20 = m10 * m21 - m11 * m20; + float fDet = m00 * fCo00 + m01 * fCo10 + m02 * fCo20; + return fDet; + } + + /** + * Sets all of the values in this matrix to zero. + * + * @return this matrix + */ + public Matrix3f zero() { + m00 = m01 = m02 = m10 = m11 = m12 = m20 = m21 = m22 = 0.0f; + return this; + } + + /** + * 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. + */ + @Override + public String toString() { + StringBuilder result = new StringBuilder("Matrix3f\n[\n"); + result.append(" "); + result.append(m00); + result.append(" "); + result.append(m01); + result.append(" "); + result.append(m02); + result.append(" \n"); + result.append(" "); + result.append(m10); + result.append(" "); + result.append(m11); + result.append(" "); + result.append(m12); + result.append(" \n"); + result.append(" "); + result.append(m20); + result.append(" "); + result.append(m21); + result.append(" "); + result.append(m22); + result.append(" \n]"); + return result.toString(); + } + + /** + * + * 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(m10); + hash = 37 * hash + Float.floatToIntBits(m11); + hash = 37 * hash + Float.floatToIntBits(m12); + + hash = 37 * hash + Float.floatToIntBits(m20); + hash = 37 * hash + Float.floatToIntBits(m21); + hash = 37 * hash + Float.floatToIntBits(m22); + + return hash; + } + + /** + * are these two matrices the same? they are is they both have the same mXX values. + * + * @param o + * the object to compare for equality + * @return true if they are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Matrix3f) || o == null) { + return false; + } + + if (this == o) { + return true; + } + + Matrix3f comp = (Matrix3f) o; + if (Float.compare(m00, comp.m00) != 0) { + return false; + } + if (Float.compare(m01, comp.m01) != 0) { + return false; + } + if (Float.compare(m02, comp.m02) != 0) { + return false; + } + + if (Float.compare(m10, comp.m10) != 0) { + return false; + } + if (Float.compare(m11, comp.m11) != 0) { + return false; + } + if (Float.compare(m12, comp.m12) != 0) { + return false; + } + + if (Float.compare(m20, comp.m20) != 0) { + return false; + } + if (Float.compare(m21, comp.m21) != 0) { + return false; + } + if (Float.compare(m22, comp.m22) != 0) { + return false; + } + + return true; + } + + public void write(JmeExporter e) throws IOException { + OutputCapsule cap = e.getCapsule(this); + cap.write(m00, "m00", 1); + cap.write(m01, "m01", 0); + cap.write(m02, "m02", 0); + cap.write(m10, "m10", 0); + cap.write(m11, "m11", 1); + cap.write(m12, "m12", 0); + cap.write(m20, "m20", 0); + cap.write(m21, "m21", 0); + cap.write(m22, "m22", 1); + } + + public void read(JmeImporter e) throws IOException { + InputCapsule cap = e.getCapsule(this); + m00 = cap.readFloat("m00", 1); + m01 = cap.readFloat("m01", 0); + m02 = cap.readFloat("m02", 0); + m10 = cap.readFloat("m10", 0); + m11 = cap.readFloat("m11", 1); + m12 = cap.readFloat("m12", 0); + m20 = cap.readFloat("m20", 0); + m21 = cap.readFloat("m21", 0); + m22 = cap.readFloat("m22", 1); + } + + /** + * A function for creating a rotation matrix that rotates a vector called + * "start" into another vector called "end". + * + * @param start + * normalized non-zero starting vector + * @param end + * normalized non-zero ending vector + * @see "Tomas M�ller, John Hughes \"Efficiently Building a Matrix to Rotate \ + * One Vector to Another\" Journal of Graphics Tools, 4(4):1-4, 1999" + */ + public void fromStartEndVectors(Vector3f start, Vector3f end) { + Vector3f v = new Vector3f(); + float e, h, f; + + start.cross(end, v); + e = start.dot(end); + f = (e < 0) ? -e : e; + + // if "from" and "to" vectors are nearly parallel + if (f > 1.0f - FastMath.ZERO_TOLERANCE) { + Vector3f u = new Vector3f(); + Vector3f x = new Vector3f(); + float c1, c2, c3; /* coefficients for later use */ + int i, j; + + x.x = (start.x > 0.0) ? start.x : -start.x; + x.y = (start.y > 0.0) ? start.y : -start.y; + x.z = (start.z > 0.0) ? start.z : -start.z; + + if (x.x < x.y) { + if (x.x < x.z) { + x.x = 1.0f; + x.y = x.z = 0.0f; + } else { + x.z = 1.0f; + x.x = x.y = 0.0f; + } + } else { + if (x.y < x.z) { + x.y = 1.0f; + x.x = x.z = 0.0f; + } else { + x.z = 1.0f; + x.x = x.y = 0.0f; + } + } + + u.x = x.x - start.x; + u.y = x.y - start.y; + u.z = x.z - start.z; + v.x = x.x - end.x; + v.y = x.y - end.y; + v.z = x.z - end.z; + + c1 = 2.0f / u.dot(u); + c2 = 2.0f / v.dot(v); + c3 = c1 * c2 * u.dot(v); + + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + float val = -c1 * u.get(i) * u.get(j) - c2 * v.get(i) + * v.get(j) + c3 * v.get(i) * u.get(j); + set(i, j, val); + } + float val = get(i, i); + set(i, i, val + 1.0f); + } + } else { + // the most common case, unless "start"="end", or "start"=-"end" + float hvx, hvz, hvxy, hvxz, hvyz; + h = 1.0f / (1.0f + e); + hvx = h * v.x; + hvz = h * v.z; + hvxy = hvx * v.y; + hvxz = hvx * v.z; + hvyz = hvz * v.y; + set(0, 0, e + hvx * v.x); + set(0, 1, hvxy - v.z); + set(0, 2, hvxz + v.y); + + set(1, 0, hvxy + v.z); + set(1, 1, e + h * v.y * v.y); + set(1, 2, hvyz - v.x); + + set(2, 0, hvxz - v.y); + set(2, 1, hvyz + v.x); + set(2, 2, e + hvz * v.z); + } + } + + /** + * 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 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/jme3-core/src/main/java/com/jme3/math/Matrix4f.java b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java new file mode 100644 index 000000000..c8dd3ef4a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java @@ -0,0 +1,2385 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.logging.Logger; + +/** + * 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, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Matrix4f.class.getName()); + public float m00, m01, m02, m03; + public float m10, m11, m12, m13; + public float m20, m21, m22, m23; + public float m30, m31, m32, m33; + public static final Matrix4f ZERO = new Matrix4f(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + public static final Matrix4f IDENTITY = new Matrix4f(); + + /** + * Constructor instantiates a new 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(); + + Vector3f f = vars.vect1.set(direction); + Vector3f s = vars.vect2.set(f).crossLocal(up); + Vector3f u = vars.vect3.set(s).crossLocal(f); +// s.normalizeLocal(); +// u.normalizeLocal(); + + m00 = s.x; + m01 = s.y; + m02 = s.z; + + m10 = u.x; + m11 = u.y; + m12 = u.z; + + m20 = -f.x; + m21 = -f.y; + m22 = -f.z; + +// m00 = -left.x; +// m10 = -left.y; +// m20 = -left.z; +// +// m01 = up.x; +// m11 = up.y; +// m21 = up.z; +// +// m02 = -direction.x; +// m12 = -direction.y; +// m22 = -direction.z; +// + + Matrix4f transMatrix = vars.tempMat4; + transMatrix.loadIdentity(); + transMatrix.m03 = -location.x; + transMatrix.m13 = -location.y; + transMatrix.m23 = -location.z; + this.multLocal(transMatrix); + + vars.release(); + +// transMatrix.multLocal(this); + +// set(transMatrix); + } + + /** + * 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). + */ + @SuppressWarnings("fallthrough") + public float get(int i, int j) { + switch (i) { + case 0: + switch (j) { + case 0: + return m00; + case 1: + return m01; + case 2: + return m02; + case 3: + return m03; + } + case 1: + switch (j) { + case 0: + return m10; + case 1: + return m11; + case 2: + return m12; + case 3: + return m13; + } + case 2: + switch (j) { + case 0: + return m20; + case 1: + return m21; + case 2: + return m22; + case 3: + return m23; + } + case 3: + switch (j) { + case 0: + return m30; + case 1: + return m31; + case 2: + return m32; + case 3: + return m33; + } + } + + logger.warning("Invalid matrix index."); + throw new IllegalArgumentException("Invalid indices into matrix."); + } + + /** + * 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). + */ + @SuppressWarnings("fallthrough") + public void set(int i, int j, float value) { + switch (i) { + case 0: + switch (j) { + case 0: + m00 = value; + return; + case 1: + m01 = value; + return; + case 2: + m02 = value; + return; + case 3: + m03 = value; + return; + } + case 1: + switch (j) { + case 0: + m10 = value; + return; + case 1: + m11 = value; + return; + case 2: + m12 = value; + return; + case 3: + m13 = value; + return; + } + case 2: + switch (j) { + case 0: + m20 = value; + return; + case 1: + m21 = value; + return; + case 2: + m22 = value; + return; + case 3: + m23 = value; + return; + } + case 3: + switch (j) { + case 0: + m30 = value; + return; + case 1: + m31 = value; + return; + case 2: + m32 = value; + return; + case 3: + m33 = value; + return; + } + } + + logger.warning("Invalid matrix index."); + throw new IllegalArgumentException("Invalid indices into matrix."); + } + + /** + * 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]; + } + + + /** + * Sets the values of this matrix + */ + public void set(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; + } + + /** + * 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); +// } + + TempVars vars = TempVars.get(); + + + fillFloatArray(vars.matrixWrite, columnMajor); + fb.put(vars.matrixWrite, 0, 16); + + vars.release(); + + return fb; + } + + public void fillFloatArray(float[] f, boolean columnMajor) { + if (columnMajor) { + f[ 0] = m00; + f[ 1] = m10; + f[ 2] = m20; + f[ 3] = m30; + f[ 4] = m01; + f[ 5] = m11; + f[ 6] = m21; + f[ 7] = m31; + f[ 8] = m02; + f[ 9] = m12; + f[10] = m22; + f[11] = m32; + f[12] = m03; + f[13] = m13; + f[14] = m23; + f[15] = m33; + } else { + f[ 0] = m00; + f[ 1] = m01; + f[ 2] = m02; + f[ 3] = m03; + f[ 4] = m10; + f[ 5] = m11; + f[ 6] = m12; + f[ 7] = m13; + f[ 8] = m20; + f[ 9] = m21; + f[10] = m22; + f[11] = m23; + f[12] = m30; + f[13] = m31; + f[14] = m32; + f[15] = m33; + } + } + + /** + * 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.warning("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. + * + * @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.warning("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.w = 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.warning("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; + } + + /** + * Retreives the scale vector from the matrix. + * + * @return the scale vector + */ + public Vector3f toScaleVector() { + Vector3f result = new Vector3f(); + this.toScaleVector(result); + return result; + } + + /** + * Retreives the scale vector from the matrix and stores it into a given + * vector. + * + * @param the + * vector where the scale will be stored + */ + public void toScaleVector(Vector3f vector) { + float scaleX = (float) Math.sqrt(m00 * m00 + m10 * m10 + m20 * m20); + float scaleY = (float) Math.sqrt(m01 * m01 + m11 * m11 + m21 * m21); + float scaleZ = (float) Math.sqrt(m02 * m02 + m12 * m12 + m22 * m22); + vector.set(scaleX, scaleY, scaleZ); + } + + /** + * Sets the scale. + * + * @param x + * the X scale + * @param y + * the Y scale + * @param z + * the Z scale + */ + public void setScale(float x, float y, float z) { + TempVars vars = TempVars.get(); + vars.vect1.set(m00, m10, m20); + vars.vect1.normalizeLocal().multLocal(x); + m00 = vars.vect1.x; + m10 = vars.vect1.y; + m20 = vars.vect1.z; + + vars.vect1.set(m01, m11, m21); + vars.vect1.normalizeLocal().multLocal(y); + m01 = vars.vect1.x; + m11 = vars.vect1.y; + m21 = vars.vect1.z; + + vars.vect1.set(m02, m12, m22); + vars.vect1.normalizeLocal().multLocal(z); + m02 = vars.vect1.x; + m12 = vars.vect1.y; + m22 = vars.vect1.z; + vars.release(); + } + + /** + * Sets the scale. + * + * @param scale + * the scale vector to set + */ + public void setScale(Vector3f scale) { + this.setScale(scale.x, scale.y, 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/jme3-core/src/main/java/com/jme3/math/Plane.java b/jme3-core/src/main/java/com/jme3/math/Plane.java new file mode 100644 index 000000000..e8d1b328e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Plane.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +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, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger + .getLogger(Plane.class.getName()); + + public static enum Side { + None, + Positive, + Negative + } + + /** + * Vector normal to the plane. + */ + protected Vector3f normal = new Vector3f(); + + /** + * Constant of the plane. See formula in class definition. + */ + protected float constant; + + /** + * Constructor instantiates a new Plane object. This is the + * default object and contains a normal of (0,0,0) and a constant of 0. + */ + public Plane() { + } + + /** + * Constructor instantiates a new 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) { + throw new IllegalArgumentException("normal cannot be null"); + } + + this.normal.set(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) { + throw new IllegalArgumentException("normal cannot be null"); + } + this.normal.set(normal); + } + + /** + * setNormal sets the normal of the plane. + * + */ + public void setNormal(float x, float y, float z) { + 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/jme3-core/src/main/java/com/jme3/math/Quaternion.java b/jme3-core/src/main/java/com/jme3/math/Quaternion.java new file mode 100644 index 000000000..1591a13a0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Quaternion.java @@ -0,0 +1,1373 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +import com.jme3.util.TempVars; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.logging.Logger; + +/** + * 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, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Quaternion.class.getName()); + /** + * Represents the identity quaternion rotation (0, 0, 0, 1). + */ + public static final Quaternion IDENTITY = new Quaternion(); + public static final Quaternion DIRECTION_Z = new Quaternion(); + public static final Quaternion ZERO = new Quaternion(0, 0, 0, 0); + + static { + DIRECTION_Z.fromAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z); + } + protected float x, y, z, w; + + /** + * Constructor instantiates a new 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 (x,y,z) aka (pitch, yaw, rall)). Note that we are applying in order: (y, z, x) aka (yaw, roll, pitch) 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 xAngle + * the Euler pitch of rotation (in radians). (aka Attitude, often rot + * around x) + * @param yAngle + * the Euler yaw of rotation (in radians). (aka Heading, often + * rot around y) + * @param zAngle + * the Euler roll of rotation (in radians). (aka Bank, often + * rot around z) + */ + public Quaternion fromAngles(float xAngle, float yAngle, float zAngle) { + float angle; + float sinY, sinZ, sinX, cosY, cosZ, cosX; + angle = zAngle * 0.5f; + sinZ = FastMath.sin(angle); + cosZ = FastMath.cos(angle); + angle = yAngle * 0.5f; + sinY = FastMath.sin(angle); + cosY = FastMath.cos(angle); + angle = xAngle * 0.5f; + sinX = FastMath.sin(angle); + cosX = FastMath.cos(angle); + + // variables used to reduce multiplication calls. + float cosYXcosZ = cosY * cosZ; + float sinYXsinZ = sinY * sinZ; + float cosYXsinZ = cosY * sinZ; + float sinYXcosZ = sinY * cosZ; + + w = (cosYXcosZ * cosX - sinYXsinZ * sinX); + x = (cosYXcosZ * sinX + sinYXsinZ * cosX); + y = (sinYXcosZ * cosX + cosYXsinZ * sinX); + z = (cosYXsinZ * cosX - sinYXcosZ * sinX); + + normalizeLocal(); + return this; + } + + /** + * toAngles returns this quaternion converted to Euler + * rotation angles (yaw,roll,pitch).
    + * Note that the result is not always 100% accurate due to the implications of euler angles. + * @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) { + // first normalize the forward (F), up (U) and side (S) vectors of the rotation matrix + // so that the scale does not affect the rotation + float lengthSquared = m00 * m00 + m10 * m10 + m20 * m20; + if (lengthSquared != 1f && lengthSquared != 0f) { + lengthSquared = 1.0f / FastMath.sqrt(lengthSquared); + m00 *= lengthSquared; + m10 *= lengthSquared; + m20 *= lengthSquared; + } + lengthSquared = m01 * m01 + m11 * m11 + m21 * m21; + if (lengthSquared != 1f && lengthSquared != 0f) { + lengthSquared = 1.0f / FastMath.sqrt(lengthSquared); + m01 *= lengthSquared; + m11 *= lengthSquared; + m21 *= lengthSquared; + } + lengthSquared = m02 * m02 + m12 * m12 + m22 * m22; + if (lengthSquared != 1f && lengthSquared != 0f) { + lengthSquared = 1.0f / FastMath.sqrt(lengthSquared); + m02 *= lengthSquared; + m12 *= lengthSquared; + m22 *= lengthSquared; + } + + // 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) { + Vector3f originalScale = result.toScaleVector(); + result.setScale(1, 1, 1); + 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); + + result.setScale(originalScale); + 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 + * follows: 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); + } + + /** + * Sets the values of this quaternion to the nlerp from itself to q2 by blend. + * @param q2 + * @param blend + */ + public void nlerp(Quaternion q2, float blend) { + float dot = dot(q2); + float blendI = 1.0f - blend; + if (dot < 0.0f) { + x = blendI * x - blend * q2.x; + y = blendI * y - blend * q2.y; + z = blendI * z - blend * q2.z; + w = blendI * w - blend * q2.w; + } else { + x = blendI * x + blend * q2.x; + y = blendI * y + blend * q2.y; + z = blendI * z + blend * q2.z; + w = blendI * w + blend * q2.w; + } + normalizeLocal(); + } + + /** + * 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 NOT 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 +// * @deprecated The naming of this method doesn't follow convention. +// * Please use {@link Quaternion#normalizeLocal() } instead. +// */ +// @Deprecated +// public void normalize() { +// float n = FastMath.invSqrt(norm()); +// x *= n; +// y *= n; +// z *= n; +// w *= n; +// } + + /** + * normalize normalizes the current Quaternion. + * The result is stored internally. + */ + public Quaternion normalizeLocal() { + float n = FastMath.invSqrt(norm()); + x *= n; + y *= n; + z *= n; + w *= n; + return this; + } + + /** + * 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 normal 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 displaced (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(); + vars.vect3.set(direction).normalizeLocal(); + vars.vect1.set(up).crossLocal(direction).normalizeLocal(); + vars.vect2.set(direction).crossLocal(vars.vect1).normalizeLocal(); + fromAxes(vars.vect1, vars.vect2, vars.vect3); + vars.release(); + } + + public void write(JmeExporter e) throws IOException { + OutputCapsule cap = e.getCapsule(this); + cap.write(x, "x", 0); + cap.write(y, "y", 0); + cap.write(z, "z", 0); + cap.write(w, "w", 1); + } + + public void read(JmeImporter e) throws IOException { + InputCapsule cap = e.getCapsule(this); + x = cap.readFloat("x", 0); + y = cap.readFloat("y", 0); + z = cap.readFloat("z", 0); + w = cap.readFloat("w", 1); + } + + /** + * @return A new quaternion that describes a rotation that would point you + * in the exact opposite direction of this Quaternion. + */ + public Quaternion opposite() { + return opposite(null); + } + + /** + * FIXME: This seems to have singularity type issues with angle == 0, possibly others such as PI. + * @param store + * A Quaternion to store our result in. If null, a new one is + * created. + * @return The store quaternion (or a new Quaterion, if store is null) that + * describes a rotation that would point you in the exact opposite + * direction of this Quaternion. + */ + public Quaternion opposite(Quaternion store) { + if (store == null) { + store = new Quaternion(); + } + + Vector3f axis = new Vector3f(); + float angle = toAngleAxis(axis); + + store.fromAngleAxis(FastMath.PI + angle, axis); + return store; + } + + /** + * @return This Quaternion, altered to describe a rotation that would point + * you in the exact opposite direction of where it is pointing + * currently. + */ + public Quaternion oppositeLocal() { + return opposite(this); + } + + @Override + public Quaternion clone() { + try { + return (Quaternion) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // can not happen + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/Ray.java b/jme3-core/src/main/java/com/jme3/math/Ray.java new file mode 100644 index 000000000..20da7e5d8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Ray.java @@ -0,0 +1,521 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.collision.UnsupportedCollisionException; +import com.jme3.export.*; +import com.jme3.util.TempVars; +import java.io.IOException; + +/** + * 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, java.io.Serializable { + + static final long serialVersionUID = 1; + + /** + * The ray's begining point. + */ + public Vector3f origin = new Vector3f(); + + /** + * The direction of the ray. + */ + public Vector3f direction = new Vector3f(0, 0, 1); + + + public float limit = Float.POSITIVE_INFINITY; + + /** + * Constructor instantiates a new Ray object. As default, the + * origin is (0,0,0) and the direction is (0,0,1). + * + */ + public Ray() { + } + + /** + * 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) { + setOrigin(origin); + setDirection(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(); + + Vector3f tempVa = vars.vect1, + tempVb = vars.vect2, + tempVc = vars.vect3, + tempVd = vars.vect4; + + Vector3f diff = origin.subtract(v0, tempVa); + Vector3f edge1 = v1.subtract(v0, tempVb); + Vector3f edge2 = v2.subtract(v0, tempVc); + Vector3f norm = edge1.cross(edge2, tempVd); + + float dirDotNorm = direction.dot(norm); + float sign; + if (dirDotNorm > FastMath.FLT_EPSILON) { + sign = 1; + } else if (dirDotNorm < -FastMath.FLT_EPSILON) { + sign = -1f; + dirDotNorm = -dirDotNorm; + } else { + // ray and triangle/quad are parallel + vars.release(); + return false; + } + + float dirDotDiffxEdge2 = sign * direction.dot(diff.cross(edge2, edge2)); + if (dirDotDiffxEdge2 >= 0.0f) { + float dirDotEdge1xDiff = sign + * direction.dot(edge1.crossLocal(diff)); + + if (dirDotEdge1xDiff >= 0.0f) { + if (!quad ? dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm : dirDotEdge1xDiff <= dirDotNorm) { + float diffDotNorm = -sign * diff.dot(norm); + if (diffDotNorm >= 0.0f) { + // this method always returns + vars.release(); + + // ray intersects triangle + // if storage vector is null, just return true, + if (store == null) { + return true; + } + + // else fill in. + float inv = 1f / dirDotNorm; + float t = diffDotNorm * inv; + if (!doPlanar) { + store.set(origin).addLocal(direction.x * t, + direction.y * t, direction.z * t); + } else { + // these weights can be used to determine + // interpolated values, such as texture coord. + // eg. texcoord s,t at intersection point: + // s = w0*s0 + w1*s1 + w2*s2; + // t = w0*t0 + w1*t1 + w2*t2; + float w1 = dirDotDiffxEdge2 * inv; + float w2 = dirDotEdge1xDiff * inv; + //float w0 = 1.0f - w1 - w2; + store.set(t, w1, w2); + } + return true; + } + } + } + } + vars.release(); + return false; + } + + public float intersects(Vector3f v0, Vector3f v1, Vector3f v2) { + float edge1X = v1.x - v0.x; + float edge1Y = v1.y - v0.y; + float edge1Z = v1.z - v0.z; + + float edge2X = v2.x - v0.x; + float edge2Y = v2.y - v0.y; + float edge2Z = v2.z - v0.z; + + float normX = ((edge1Y * edge2Z) - (edge1Z * edge2Y)); + float normY = ((edge1Z * edge2X) - (edge1X * edge2Z)); + float normZ = ((edge1X * edge2Y) - (edge1Y * edge2X)); + + float dirDotNorm = direction.x * normX + direction.y * normY + direction.z * normZ; + + float diffX = origin.x - v0.x; + float diffY = origin.y - v0.y; + float diffZ = origin.z - v0.z; + + float sign; + if (dirDotNorm > FastMath.FLT_EPSILON) { + sign = 1; + } else if (dirDotNorm < -FastMath.FLT_EPSILON) { + sign = -1f; + dirDotNorm = -dirDotNorm; + } else { + // ray and triangle/quad are parallel + return Float.POSITIVE_INFINITY; + } + + float diffEdge2X = ((diffY * edge2Z) - (diffZ * edge2Y)); + float diffEdge2Y = ((diffZ * edge2X) - (diffX * edge2Z)); + float diffEdge2Z = ((diffX * edge2Y) - (diffY * edge2X)); + + float dirDotDiffxEdge2 = sign * (direction.x * diffEdge2X + + direction.y * diffEdge2Y + + direction.z * diffEdge2Z); + + if (dirDotDiffxEdge2 >= 0.0f) { + diffEdge2X = ((edge1Y * diffZ) - (edge1Z * diffY)); + diffEdge2Y = ((edge1Z * diffX) - (edge1X * diffZ)); + diffEdge2Z = ((edge1X * diffY) - (edge1Y * diffX)); + + float dirDotEdge1xDiff = sign * (direction.x * diffEdge2X + + direction.y * diffEdge2Y + + direction.z * diffEdge2Z); + + if (dirDotEdge1xDiff >= 0.0f) { + if (dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm) { + float diffDotNorm = -sign * (diffX * normX + diffY * normY + diffZ * normZ); + if (diffDotNorm >= 0.0f) { + // ray intersects triangle + // fill in. + float inv = 1f / dirDotNorm; + float t = diffDotNorm * inv; + return t; + } + } + } + } + + return Float.POSITIVE_INFINITY; + } + + /** + * 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(); + + Vector3f tempVa = vars.vect1, + tempVb = vars.vect2; + + point.subtract(origin, tempVa); + float rayParam = direction.dot(tempVa); + if (rayParam > 0) { + origin.add(direction.mult(rayParam, tempVb), tempVb); + } else { + tempVb.set(origin); + rayParam = 0.0f; + } + + tempVb.subtract(point, tempVa); + float len = tempVa.lengthSquared(); + vars.release(); + return len; + } + + /** + * + * 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 of the ray, aka the length. + * If the limit is not infinity, then this ray is a line with length + * limit. + * + * @return the limit of the ray, aka the length. + */ + 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) { + assert direction.isUnitVector(); + this.direction.set(direction); + } + + /** + * Copies information from a source ray into this ray. + * + * @param source + * the ray to copy information from + */ + public void set(Ray source) { + origin.set(source.getOrigin()); + direction.set(source.getDirection()); + } + + public String toString() { + return getClass().getSimpleName() + " [Origin: " + origin + ", Direction: " + direction + "]"; + } + + public void write(JmeExporter e) throws IOException { + OutputCapsule capsule = e.getCapsule(this); + capsule.write(origin, "origin", Vector3f.ZERO); + capsule.write(direction, "direction", Vector3f.ZERO); + } + + public void read(JmeImporter e) throws IOException { + InputCapsule capsule = e.getCapsule(this); + origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone()); + direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone()); + } + + @Override + public Ray clone() { + try { + Ray r = (Ray) super.clone(); + r.direction = direction.clone(); + r.origin = origin.clone(); + return r; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/Rectangle.java b/jme3-core/src/main/java/com/jme3/math/Rectangle.java new file mode 100644 index 000000000..e8ba4c23d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Rectangle.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +import java.io.IOException; + + +/** + * + * 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 fourth point defining the rectangle ((B + C) - A. + * + * @author Mark Powell + * @author Joshua Slack + */ + +public final class Rectangle implements Savable, Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private Vector3f a, b, c; + + /** + * Constructor creates a new 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/jme3-core/src/main/java/com/jme3/math/Ring.java b/jme3-core/src/main/java/com/jme3/math/Ring.java new file mode 100644 index 000000000..1c9eebc45 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Ring.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +import java.io.IOException; + + +/** + * 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 Savable, Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private Vector3f center, up; + private float innerRadius, outerRadius; + private transient static Vector3f b1 = new Vector3f(), b2 = new Vector3f(); + + /** + * Constructor creates a new 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/jme3-core/src/main/java/com/jme3/math/Spline.java b/jme3-core/src/main/java/com/jme3/math/Spline.java new file mode 100644 index 000000000..dccab9953 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Spline.java @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * + * @author Nehon + */ +public class Spline implements Savable { + + public enum SplineType { + Linear, + CatmullRom, + Bezier, + Nurb + } + + private List controlPoints = new ArrayList(); + private List knots; //knots of NURBS spline + private float[] weights; //weights of NURBS spline + private int basisFunctionDegree; //degree of NURBS spline basis function (computed automatically) + private boolean cycle; + private List 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) { + if(splineType==SplineType.Nurb) { + throw new IllegalArgumentException("To create NURBS spline use: 'public Spline(Vector3f[] controlPoints, float[] weights, float[] nurbKnots)' constructor!"); + } + for (int i = 0; i < controlPoints.length; i++) { + Vector3f vector3f = controlPoints[i]; + this.controlPoints.add(vector3f); + } + type = splineType; + this.curveTension = curveTension; + this.cycle = cycle; + this.computeTotalLentgh(); + } + + /** + * Create a spline + * @param splineType the type of the spline @see {SplineType} + * @param controlPoints a list of vector to use as control points of the spline + * If the type of the curve is Bezier curve the control points should be provided + * in the appropriate way. Each point 'p' describing control position in the scene + * should be surrounded by two handler points. This applies to every point except + * for the border points of the curve, who should have only one handle point. + * The pattern should be as follows: + * P0 - H0 : H1 - P1 - H1 : ... : Hn - Pn + * + * n is the amount of 'P' - points. + * @param curveTension the tension of the spline + * @param cycle true if the spline cycle. + */ + public Spline(SplineType splineType, List controlPoints, float curveTension, boolean cycle) { + if(splineType==SplineType.Nurb) { + throw new IllegalArgumentException("To create NURBS spline use: 'public Spline(Vector3f[] controlPoints, float[] weights, float[] nurbKnots)' constructor!"); + } + type = splineType; + this.controlPoints.addAll(controlPoints); + this.curveTension = curveTension; + this.cycle = cycle; + this.computeTotalLentgh(); + } + + /** + * Create a NURBS spline. A spline type is automatically set to SplineType.Nurb. + * The cycle is set to false by default. + * @param controlPoints a list of vector to use as control points of the spline + * @param nurbKnots the nurb's spline knots + */ + public Spline(List controlPoints, List nurbKnots) { + //input data control + for(int i=0;inurbKnots.get(i+1)) { + throw new IllegalArgumentException("The knots values cannot decrease!"); + } + } + + //storing the data + type = SplineType.Nurb; + this.weights = new float[controlPoints.size()]; + this.knots = nurbKnots; + this.basisFunctionDegree = nurbKnots.size() - weights.length; + for(int i=0;i 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.clone()); + if (controlPoints.size() >= 2 && this.cycle) { + controlPoints.add(controlPoints.get(0).clone()); + } + if (controlPoints.size() > 1) { + this.computeTotalLentgh(); + } + } + + /** + * remove the controlPoint from the spline + * @param controlPoint the controlPoint to remove + */ + public void removeControlPoint(Vector3f controlPoint) { + controlPoints.remove(controlPoint); + if (controlPoints.size() > 1) { + this.computeTotalLentgh(); + } + } + + public void clearControlPoints(){ + controlPoints.clear(); + totalLength = 0; + } + + /** + * This method computes the total length of the curve. + */ + private void computeTotalLentgh() { + totalLength = 0; + float l = 0; + if (segmentsLength == null) { + segmentsLength = new ArrayList(); + } else { + segmentsLength.clear(); + } + if (type == SplineType.Linear) { + if (controlPoints.size() > 1) { + for (int i = 0; i < controlPoints.size() - 1; i++) { + l = controlPoints.get(i + 1).subtract(controlPoints.get(i)).length(); + segmentsLength.add(l); + totalLength += l; + } + } + } else if(type == SplineType.Bezier) { + this.computeBezierLength(); + } else if(type == SplineType.Nurb) { + this.computeNurbLength(); + } else { + this.initCatmullRomWayPoints(controlPoints); + this.computeCatmulLength(); + } + } + + /** + * This method computes the Catmull Rom curve length. + */ + private void computeCatmulLength() { + float l = 0; + if (controlPoints.size() > 1) { + for (int i = 0; i < controlPoints.size() - 1; i++) { + l = FastMath.getCatmullRomP1toP2Length(CRcontrolPoints.get(i), + CRcontrolPoints.get(i + 1), CRcontrolPoints.get(i + 2), CRcontrolPoints.get(i + 3), 0, 1, curveTension); + segmentsLength.add(l); + totalLength += l; + } + } + } + + /** + * This method calculates the Bezier curve length. + */ + private void computeBezierLength() { + float l = 0; + if (controlPoints.size() > 1) { + for (int i = 0; i < controlPoints.size() - 1; i+=3) { + l = FastMath.getBezierP1toP2Length(controlPoints.get(i), + controlPoints.get(i + 1), controlPoints.get(i + 2), controlPoints.get(i + 3)); + segmentsLength.add(l); + totalLength += l; + } + } + } + + /** + * This method calculates the NURB curve length. + */ + private void computeNurbLength() { + //TODO: implement + } + + /** + * Iterpolate a position on the spline + * @param value a value from 0 to 1 that represent the postion between the curent control point and the next one + * @param currentControlPoint the current control point + * @param store a vector to store the result (use null to create a new one that will be returned by the method) + * @return the position + */ + public Vector3f interpolate(float value, int currentControlPoint, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + switch (type) { + case CatmullRom: + FastMath.interpolateCatmullRom(value, curveTension, CRcontrolPoints.get(currentControlPoint), CRcontrolPoints.get(currentControlPoint + 1), CRcontrolPoints.get(currentControlPoint + 2), CRcontrolPoints.get(currentControlPoint + 3), store); + break; + case Linear: + FastMath.interpolateLinear(value, controlPoints.get(currentControlPoint), controlPoints.get(currentControlPoint + 1), store); + break; + case Bezier: + FastMath.interpolateBezier(value, controlPoints.get(currentControlPoint), controlPoints.get(currentControlPoint + 1), controlPoints.get(currentControlPoint + 2), controlPoints.get(currentControlPoint + 3), store); + break; + case Nurb: + CurveAndSurfaceMath.interpolateNurbs(value, this, store); + break; + default: + break; + } + return store; + } + + /** + * returns the curve tension + */ + public float getCurveTension() { + return curveTension; + } + + /** + * sets the curve tension + * + * @param curveTension the tension + */ + public void setCurveTension(float curveTension) { + this.curveTension = curveTension; + if(type==SplineType.CatmullRom) { + this.computeTotalLentgh(); + } + } + + /** + * returns true if the spline cycle + */ + public boolean isCycle() { + return cycle; + } + + /** + * set to true to make the spline cycle + * @param cycle + */ + public void setCycle(boolean cycle) { + if(type!=SplineType.Nurb) { + if (controlPoints.size() >= 2) { + if (this.cycle && !cycle) { + controlPoints.remove(controlPoints.size() - 1); + } + if (!this.cycle && cycle) { + controlPoints.add(controlPoints.get(0)); + } + this.cycle = cycle; + this.computeTotalLentgh(); + } else { + this.cycle = cycle; + } + } + } + + /** + * return the total lenght of the spline + */ + public float getTotalLength() { + return totalLength; + } + + /** + * return the type of the spline + */ + public SplineType getType() { + return type; + } + + /** + * Sets the type of the spline + * @param type + */ + public void setType(SplineType type) { + this.type = type; + this.computeTotalLentgh(); + } + + /** + * returns this spline control points + */ + public List getControlPoints() { + return controlPoints; + } + + /** + * returns a list of float representing the segments lenght + */ + public List getSegmentsLength() { + return segmentsLength; + } + + //////////// NURBS getters ///////////////////// + + /** + * This method returns the minimum nurb curve knot value. Check the nurb type before calling this method. It the curve is not of a Nurb + * type - NPE will be thrown. + * @return the minimum nurb curve knot value + */ + public float getMinNurbKnot() { + return knots.get(basisFunctionDegree - 1); + } + + /** + * This method returns the maximum nurb curve knot value. Check the nurb type before calling this method. It the curve is not of a Nurb + * type - NPE will be thrown. + * @return the maximum nurb curve knot value + */ + public float getMaxNurbKnot() { + return knots.get(weights.length); + } + + /** + * This method returns NURBS' spline knots. + * @return NURBS' spline knots + */ + public List getKnots() { + return knots; + } + + /** + * This method returns NURBS' spline weights. + * @return NURBS' spline weights + */ + public float[] getWeights() { + return weights; + } + + /** + * This method returns NURBS' spline basis function degree. + * @return NURBS' spline basis function degree + */ + public int getBasisFunctionDegree() { + return basisFunctionDegree; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.writeSavableArrayList((ArrayList) controlPoints, "controlPoints", null); + oc.write(type, "type", SplineType.CatmullRom); + float list[] = new float[segmentsLength.size()]; + for (int i = 0; i < segmentsLength.size(); i++) { + list[i] = segmentsLength.get(i); + } + oc.write(list, "segmentsLength", null); + + oc.write(totalLength, "totalLength", 0); + oc.writeSavableArrayList((ArrayList) CRcontrolPoints, "CRControlPoints", null); + oc.write(curveTension, "curveTension", 0.5f); + oc.write(cycle, "cycle", false); + oc.writeSavableArrayList((ArrayList)knots, "knots", null); + oc.write(weights, "weights", null); + oc.write(basisFunctionDegree, "basisFunctionDegree", 0); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + + controlPoints = (ArrayList) 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); + knots = in.readSavableArrayList("knots", null); + weights = in.readFloatArray("weights", null); + basisFunctionDegree = in.readInt("basisFunctionDegree", 0); + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/Transform.java b/jme3-core/src/main/java/com/jme3/math/Transform.java new file mode 100644 index 000000000..72ec73279 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Transform.java @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +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, java.io.Serializable { + + static final long serialVersionUID = 1; + + public static final Transform IDENTITY = new Transform(); + + private Quaternion rot = new Quaternion(); + private Vector3f translation = new Vector3f(); + private Vector3f scale = new Vector3f(1,1,1); + + public Transform(Vector3f translation, Quaternion rot){ + this.translation.set(translation); + this.rot.set(rot); + } + + public Transform(Vector3f translation, Quaternion rot, Vector3f scale){ + this(translation, rot); + this.scale.set(scale); + } + + public Transform(Vector3f translation){ + this(translation, Quaternion.IDENTITY); + } + + public Transform(Quaternion rot){ + this(Vector3f.ZERO, rot); + } + + public Transform(){ + this(Vector3f.ZERO, Quaternion.IDENTITY); + } + + /** + * Sets this rotation to the given Quaternion value. + * @param rot The new rotation for this matrix. + * @return this + */ + public Transform setRotation(Quaternion rot) { + this.rot.set(rot); + return this; + } + + /** + * Sets this translation to the given value. + * @param trans The new translation for this matrix. + * @return this + */ + public Transform setTranslation(Vector3f trans) { + this.translation.set(trans); + return this; + } + + /** + * Return the translation vector in this matrix. + * @return translation vector. + */ + public Vector3f getTranslation() { + return translation; + } + + /** + * Sets this scale to the given value. + * @param scale The new scale for this matrix. + * @return this + */ + public Transform setScale(Vector3f scale) { + this.scale.set(scale); + return this; + } + + /** + * Sets this scale to the given value. + * @param scale The new scale for this matrix. + * @return this + */ + public Transform setScale(float scale) { + this.scale.set(scale, scale, scale); + return this; + } + + /** + * Return the scale vector in this matrix. + * @return scale vector. + */ + public Vector3f getScale() { + return scale; + } + + /** + * Stores this translation value into the given vector3f. If trans is null, a new vector3f is created to + * hold the value. The value, once stored, is returned. + * @param trans The store location for this matrix's translation. + * @return The value of this matrix's translation. + */ + public Vector3f getTranslation(Vector3f trans) { + if (trans==null) trans=new Vector3f(); + trans.set(this.translation); + return trans; + } + + /** + * Stores this rotation value into the given Quaternion. If quat is null, a new Quaternion is created to + * hold the value. The value, once stored, is returned. + * @param quat The store location for this matrix's rotation. + * @return The value of this matrix's rotation. + */ + public Quaternion getRotation(Quaternion quat) { + if (quat==null) quat=new Quaternion(); + quat.set(rot); + return quat; + } + + /** + * Return the rotation quaternion in this matrix. + * @return rotation quaternion. + */ + public Quaternion getRotation() { + return rot; + } + + /** + * Stores this scale value into the given vector3f. If scale is null, a new vector3f is created to + * hold the value. The value, once stored, is returned. + * @param scale The store location for this matrix's scale. + * @return The value of this matrix's scale. + */ + public Vector3f getScale(Vector3f scale) { + if (scale==null) scale=new Vector3f(); + scale.set(this.scale); + return scale; + } + + /** + * Sets this matrix to the interpolation between the first matrix and the second by delta amount. + * @param t1 The begining transform. + * @param t2 The ending transform. + * @param delta An amount between 0 and 1 representing how far to interpolate from t1 to t2. + */ + public void interpolateTransforms(Transform t1, Transform t2, float delta) { + this.rot.slerp(t1.rot,t2.rot,delta); + this.translation.interpolateLocal(t1.translation,t2.translation,delta); + this.scale.interpolateLocal(t1.scale,t2.scale,delta); + } + + /** + * Changes the values of this matrix acording to it's parent. Very similar to the concept of Node/Spatial transforms. + * @param parent The parent matrix. + * @return This matrix, after combining. + */ + public Transform combineWithParent(Transform parent) { + scale.multLocal(parent.scale); +// rot.multLocal(parent.rot); + parent.rot.mult(rot, rot); + + // This here, is evil code +// parent +// .rot +// .multLocal(translation) +// .multLocal(parent.scale) +// .addLocal(parent.translation); + + translation.multLocal(parent.scale); + parent + .rot + .multLocal(translation) + .addLocal(parent.translation); + return this; + } + + /** + * Sets this matrix's translation to the given x,y,z values. + * @param x This matrix's new x translation. + * @param y This matrix's new y translation. + * @param z This matrix's new z translation. + * @return this + */ + public Transform setTranslation(float x,float y, float z) { + translation.set(x,y,z); + return this; + } + + /** + * Sets this matrix's scale to the given x,y,z values. + * @param x This matrix's new x scale. + * @param y This matrix's new y scale. + * @param z This matrix's new z scale. + * @return this + */ + public Transform setScale(float x, float y, float z) { + scale.set(x,y,z); + return this; + } + + public Vector3f transformVector(final Vector3f in, Vector3f store){ + if (store == null) + store = new Vector3f(); + + // multiply with scale first, then rotate, finally translate (cf. + // Eberly) + return rot.mult(store.set(in).multLocal(scale), store).addLocal(translation); + } + + public Vector3f transformInverseVector(final Vector3f in, Vector3f store){ + if (store == null) + store = new Vector3f(); + + // The author of this code should look above and take the inverse of that + // But for some reason, they didnt .. +// in.subtract(translation, store).divideLocal(scale); +// rot.inverse().mult(store, store); + + in.subtract(translation, store); + rot.inverse().mult(store, store); + store.divideLocal(scale); + + return store; + } + + /** + * Loads the identity. Equal to translation=0,0,0 scale=1,1,1 rot=0,0,0,1. + */ + public void loadIdentity() { + translation.set(0,0,0); + scale.set(1,1,1); + rot.set(0,0,0,1); + } + + @Override + public String toString(){ + return getClass().getSimpleName() + "[ " + translation.x + ", " + translation.y + ", " + translation.z + "]\n" + + "[ " + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "]\n" + + "[ " + scale.x + " , " + scale.y + ", " + scale.z + "]"; + } + + /** + * Sets this matrix to be equal to the given matrix. + * @param matrixQuat The matrix to be equal to. + * @return this + */ + public Transform set(Transform matrixQuat) { + this.translation.set(matrixQuat.translation); + this.rot.set(matrixQuat.rot); + this.scale.set(matrixQuat.scale); + return this; + } + + public void write(JmeExporter e) throws IOException { + OutputCapsule capsule = e.getCapsule(this); + capsule.write(rot, "rot", new Quaternion()); + capsule.write(translation, "translation", Vector3f.ZERO); + capsule.write(scale, "scale", Vector3f.UNIT_XYZ); + } + + public void read(JmeImporter e) throws IOException { + InputCapsule capsule = e.getCapsule(this); + + rot = (Quaternion)capsule.readSavable("rot", new Quaternion()); + translation = (Vector3f)capsule.readSavable("translation", Vector3f.ZERO); + scale = (Vector3f)capsule.readSavable("scale", Vector3f.UNIT_XYZ); + } + + @Override + public Transform clone() { + try { + Transform tq = (Transform) super.clone(); + tq.rot = rot.clone(); + tq.scale = scale.clone(); + tq.translation = translation.clone(); + return tq; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/Triangle.java b/jme3-core/src/main/java/com/jme3/math/Triangle.java new file mode 100644 index 000000000..4b98e77aa --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Triangle.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.Savable; +import java.io.IOException; + +/** + * Triangle defines an object for containing triangle information. + * The triangle is defined by a collection of three {@link Vector3f} + * objects. + * + * @author Mark Powell + * @author Joshua Slack + */ +public class Triangle extends AbstractTriangle implements Savable, java.io.Serializable { + + static final long serialVersionUID = 1; + + private Vector3f pointa = new Vector3f(); + private Vector3f pointb = new Vector3f(); + private Vector3f pointc = new Vector3f(); + private transient Vector3f center; + private transient Vector3f normal; + private float projection; + private int index; + + public Triangle() { + } + + /** + * Constructor instantiates a new 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 triangle's points to that specified as + * a parameter. + * @param i the index to place the point. + * @param point the point to set. + */ + public void set(int i, Vector3f point) { + switch (i) { + case 0: + pointa.set(point); + break; + case 1: + pointb.set(point); + break; + case 2: + pointc.set(point); + break; + } + } + + /** + * + * set sets one of the triangle's points to that specified as + * a parameter. + * @param i the index to place the point. + */ + public void set(int i, float x, float y, float z) { + switch (i) { + case 0: + pointa.set(x, y, z); + break; + case 1: + pointb.set(x, y, z); + break; + case 2: + pointc.set(x, y, z); + break; + } + } + + public void set1(Vector3f v) { + pointa.set(v); + } + + public void set2(Vector3f v) { + pointb.set(v); + } + + public void set3(Vector3f v) { + pointc.set(v); + } + + public void set(Vector3f v1, Vector3f v2, Vector3f v3) { + pointa.set(v1); + pointb.set(v2); + pointc.set(v3); + } + + /** + * calculateCenter finds the average point of the triangle. + * + */ + public void calculateCenter() { + if (center == null) { + center = new Vector3f(pointa); + } else { + center.set(pointa); + } + center.addLocal(pointb).addLocal(pointc).multLocal(FastMath.ONE_THIRD); + } + + /** + * calculateNormal generates the normal for this triangle + * + */ + public void calculateNormal() { + if (normal == null) { + normal = new Vector3f(pointb); + } else { + normal.set(pointb); + } + normal.subtractLocal(pointa).crossLocal(pointc.x - pointa.x, pointc.y - pointa.y, pointc.z - pointa.z); + normal.normalizeLocal(); + } + + /** + * obtains the center point of this triangle (average of the three triangles) + * @return the center point. + */ + public Vector3f getCenter() { + if (center == null) { + calculateCenter(); + } + return center; + } + + /** + * sets the center point of this triangle (average of the three triangles) + * @param center the center point. + */ + public void setCenter(Vector3f center) { + this.center = center; + } + + /** + * obtains the unit length normal vector of this triangle, if set or + * calculated + * + * @return the normal vector + */ + public Vector3f getNormal() { + if (normal == null) { + calculateNormal(); + } + return normal; + } + + /** + * sets the normal vector of this triangle (to conform, must be unit length) + * @param normal the normal vector. + */ + public void setNormal(Vector3f normal) { + this.normal = normal; + } + + /** + * obtains the projection of the vertices relative to the line origin. + * @return the projection of the triangle. + */ + public float getProjection() { + return this.projection; + } + + /** + * sets the projection of the vertices relative to the line origin. + * @param projection the projection of the triangle. + */ + public void setProjection(float projection) { + this.projection = projection; + } + + /** + * obtains an index that this triangle represents if it is contained in a OBBTree. + * @return the index in an OBBtree + */ + public int getIndex() { + return index; + } + + /** + * sets an index that this triangle represents if it is contained in a OBBTree. + * @param index the index in an OBBtree + */ + public void setIndex(int index) { + this.index = index; + } + + public static Vector3f computeTriangleNormal(Vector3f v1, Vector3f v2, Vector3f v3, Vector3f store) { + if (store == null) { + store = new Vector3f(v2); + } else { + store.set(v2); + } + + store.subtractLocal(v1).crossLocal(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z); + return store.normalizeLocal(); + } + + public void write(JmeExporter e) throws IOException { + e.getCapsule(this).write(pointa, "pointa", Vector3f.ZERO); + e.getCapsule(this).write(pointb, "pointb", Vector3f.ZERO); + e.getCapsule(this).write(pointc, "pointc", Vector3f.ZERO); + } + + public void read(JmeImporter e) throws IOException { + pointa = (Vector3f) e.getCapsule(this).readSavable("pointa", Vector3f.ZERO.clone()); + pointb = (Vector3f) e.getCapsule(this).readSavable("pointb", Vector3f.ZERO.clone()); + pointc = (Vector3f) e.getCapsule(this).readSavable("pointc", Vector3f.ZERO.clone()); + } + + @Override + public Triangle clone() { + try { + Triangle t = (Triangle) super.clone(); + t.pointa = pointa.clone(); + t.pointb = pointb.clone(); + t.pointc = pointc.clone(); + return t; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/Vector2f.java b/jme3-core/src/main/java/com/jme3/math/Vector2f.java new file mode 100644 index 000000000..c1c2bcba4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Vector2f.java @@ -0,0 +1,756 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.logging.Logger; + +/** + * Vector2f defines a Vector for a two float value vector. + * + * @author Mark Powell + * @author Joshua Slack + */ +public final class Vector2f implements Savable, Cloneable, java.io.Serializable { + + static final long serialVersionUID = 1; + private static final Logger logger = Logger.getLogger(Vector2f.class.getName()); + + public static final Vector2f ZERO = new Vector2f(0f, 0f); + public static final Vector2f UNIT_XY = new Vector2f(1f, 1f); + + /** + * the x value of the vector. + */ + public float x; + /** + * the y value of the vector. + */ + public float y; + + /** + * Creates a Vector2f with the given initial x and y values. + * + * @param x + * The x value of this Vector2f. + * @param y + * The y value of this Vector2f. + */ + public Vector2f(float x, float y) { + this.x = x; + this.y = y; + } + + /** + * Creates a Vector2f with x and y set to 0. Equivalent to Vector2f(0,0). + */ + public Vector2f() { + x = y = 0; + } + + /** + * Creates a new Vector2f that contains the passed vector's information + * + * @param vector2f + * The vector to copy + */ + public Vector2f(Vector2f vector2f) { + this.x = vector2f.x; + this.y = vector2f.y; + } + + /** + * set the x and y values of the vector + * + * @param x + * the x value of the vector. + * @param y + * the y value of the vector. + * @return this vector + */ + public Vector2f set(float x, float y) { + this.x = x; + this.y = y; + return this; + } + + /** + * set the x and y values of the vector from another vector + * + * @param vec + * the vector to copy from + * @return this vector + */ + public Vector2f set(Vector2f vec) { + this.x = vec.x; + this.y = vec.y; + return this; + } + + /** + * 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 interpolateLocal(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 interpolateLocal(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 otherX The X coordinate of the v vector + * @param otherY The Y coordinate of the v vector + * @return the distance squared between the two vectors. + */ + public float distanceSquared(float otherX, float otherY) { + double dx = x - otherX; + double dy = y - otherY; + return (float) (dx * dx + dy * dy); + } + + /** + * 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/jme3-core/src/main/java/com/jme3/math/Vector3f.java b/jme3-core/src/main/java/com/jme3/math/Vector3f.java new file mode 100644 index 000000000..b5d2d8fc6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Vector3f.java @@ -0,0 +1,1082 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.math; + +import com.jme3.export.*; +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, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Vector3f.class.getName()); + + public final static Vector3f ZERO = new Vector3f(0, 0, 0); + public final static Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN); + public final static Vector3f UNIT_X = new Vector3f(1, 0, 0); + public final static Vector3f UNIT_Y = new Vector3f(0, 1, 0); + public final static Vector3f UNIT_Z = new Vector3f(0, 0, 1); + public final static Vector3f UNIT_XYZ = new Vector3f(1, 1, 1); + public final static Vector3f POSITIVE_INFINITY = new Vector3f( + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY); + public final static Vector3f NEGATIVE_INFINITY = new Vector3f( + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY); + + + /** + * the x value of the vector. + */ + public float x; + + /** + * the y value of the vector. + */ + public float y; + + /** + * the z value of the vector. + */ + public float z; + + /** + * Constructor instantiates a new 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; + } + + /** + * Projects this vector onto another vector + * + * @param other The vector to project this vector onto + * @return A new vector with the projection result + */ + 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); + } + + /** + * Projects this vector onto another vector, stores the result in this + * vector + * + * @param other The vector to project this vector onto + * @return This Vector3f, set to the projection result + */ + public Vector3f projectLocal(Vector3f other){ + float n = this.dot(other); // A . B + float d = other.lengthSquared(); // |B|^2 + return set(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 Vector3f maxLocal(Vector3f other){ + x = other.x > x ? other.x : x; + y = other.y > y ? other.y : y; + z = other.z > z ? other.z : z; + return this; + } + + /** + * minLocal computes the minimum value for each + * component in this and other vector. The result is stored + * in this vector. + * @param other + */ + public Vector3f minLocal(Vector3f other){ + x = other.x < x ? other.x : x; + y = other.y < y ? other.y : y; + z = other.z < z ? other.z : z; + return this; + } + + /** + * 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 interpolateLocal(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 interpolateLocal(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/jme3-core/src/main/java/com/jme3/math/Vector4f.java b/jme3-core/src/main/java/com/jme3/math/Vector4f.java new file mode 100644 index 000000000..5db9d4a6d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Vector4f.java @@ -0,0 +1,1004 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import com.jme3.export.*; +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, java.io.Serializable { + + static final long serialVersionUID = 1; + + private static final Logger logger = Logger.getLogger(Vector4f.class.getName()); + + public final static Vector4f ZERO = new Vector4f(0, 0, 0, 0); + public final static Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN); + public final static Vector4f UNIT_X = new Vector4f(1, 0, 0, 0); + public final static Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0); + public final static Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0); + public final static Vector4f UNIT_W = new Vector4f(0, 0, 0, 1); + public final static Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1); + public final static Vector4f POSITIVE_INFINITY = new Vector4f( + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY); + public final static Vector4f NEGATIVE_INFINITY = new Vector4f( + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY); + + /** + * the x value of the vector. + */ + public float x; + + /** + * the y value of the vector. + */ + public float y; + + /** + * the z value of the vector. + */ + public float z; + + /** + * the w value of the vector. + */ + public float w; + + /** + * Constructor instantiates a new 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 subtractW + * the w value to subtract. + * @return this + */ + public Vector4f subtractLocal(float subtractX, float subtractY, float subtractZ, float subtractW) { + x -= subtractX; + y -= subtractY; + z -= subtractZ; + w -= subtractW; + return this; + } + + /** + * 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 Vector4f 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; + return this; + } + + /** + * minLocal computes the minimum value for each + * component in this and other vector. The result is stored + * in this vector. + * @param other + */ + public Vector4f 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; + return this; + } + + /** + * 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 interpolateLocal(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 interpolateLocal(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/jme3-core/src/main/java/com/jme3/math/package.html b/jme3-core/src/main/java/com/jme3/math/package.html new file mode 100644 index 000000000..64da8aaa6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/package.html @@ -0,0 +1,53 @@ + + + + + + + + + +The com.jme3.math package provides mathematic data structures +and utilities which are used by the rest of the engine. +The math package provides the following classes:
    +

    General purpose vectors

    +
      +
    • {@link com.jme3.math.Vector2f} - 2D general purpose vector
    • +
    • {@link com.jme3.math.Vector3f} - 3D general purpose vector
    • +
    • {@link com.jme3.math.Vector4f} - 4D general purpose vector
    • +
    +

    Special purpose vectors

    +
      +
    • {@link com.jme3.math.ColorRGBA} - Floating-point RGB color with alpha
    • +
    • {@link com.jme3.math.Quaternion} - Specialized 4D data structure to represent rotation
    • +
    +

    Matrices

    +
      +
    • {@link com.jme3.math.Matrix3f} - 3x3 matrix, usually used to represent rotation
    • +
    • {@link com.jme3.math.Matrix4f} - 4x4 matrix, used as an efficient transform representation
    • +
    +

    Shapes

    +
      +
    • {@link com.jme3.math.AbstractTriangle} - Abstract triangle. Data to be provided by implementation
    • +
    • {@link com.jme3.math.Triangle} - Concrete implementation of AbstractTriangle with center and normal vectors
    • +
    • {@link com.jme3.math.Line} - Infinite 3D line
    • +
    • {@link com.jme3.math.LineSegment} - 3D line with start and end point
    • +
    • {@link com.jme3.math.Plane} - 3D plane
    • +
    • {@link com.jme3.math.Ray} - 3D ray
    • +
    • {@link com.jme3.math.Rectangle} - 3D rectangle
    • +
    • {@link com.jme3.math.Ring} - 3D ring
    • +
    +

    Curves

    +
      +
    • {@link com.jme3.math.Spline} - 3D curve defined by control points and a function
    • +
    +

    Utility classes

    +
      +
    • {@link com.jme3.math.Transform} - Representation of a transform with translation, rotation, and scale
    • +
    • {@link com.jme3.math.FastMath} - Contains static methods for floating-point math
    • +
    • {@link com.jme3.math.CurveAndSurfaceMath} - Contains static methods specific to curve and surface math
    • +
    • {@link com.jme3.math.Eigen3f} - Provides computation of eigenvectors given a matrix
    • +
    + + + diff --git a/jme3-core/src/main/java/com/jme3/post/Filter.java b/jme3-core/src/main/java/com/jme3/post/Filter.java new file mode 100644 index 000000000..e1da6accd --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/post/Filter.java @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post; + +import com.jme3.asset.AssetManager; +import com.jme3.export.*; +import com.jme3.material.Material; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * Filters are 2D effects applied to the rendered scene.
    + * The filter is fed with the rendered scene image rendered in an offscreen frame buffer.
    + * This texture is applied on a fullscreen quad, with a special material.
    + * This material uses a shader that aplly the desired effect to the scene texture.
    + *
    + * This class is abstract, any Filter must extend it.
    + * Any filter holds a frameBuffer and a texture
    + * The getMaterial must return a Material that use a GLSL shader immplementing the desired effect
    + * + * @author Rémy Bouquet aka Nehon + */ +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; + } + + /** + * Inner class Pass + * Pass are like filters in filters. + * Some filters will need multiple passes before the final render + */ + public class Pass { + + protected FrameBuffer renderFrameBuffer; + protected Texture2D renderedTexture; + protected Texture2D depthTexture; + protected Material passMaterial; + + /** + * init the pass called internally + * @param renderer + * @param width + * @param height + * @param textureFormat + * @param depthBufferFormat + * @param numSamples + */ + public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSamples, boolean renderDepth) { + Collection caps = renderer.getCaps(); + if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample) && caps.contains(Caps.OpenGL31)) { + renderFrameBuffer = new FrameBuffer(width, height, numSamples); + renderedTexture = new Texture2D(width, height, numSamples, textureFormat); + renderFrameBuffer.setDepthBuffer(depthBufferFormat); + if (renderDepth) { + depthTexture = new Texture2D(width, height, numSamples, depthBufferFormat); + renderFrameBuffer.setDepthTexture(depthTexture); + } + } else { + renderFrameBuffer = new FrameBuffer(width, height, 1); + renderedTexture = new Texture2D(width, height, textureFormat); + renderFrameBuffer.setDepthBuffer(depthBufferFormat); + if (renderDepth) { + depthTexture = new Texture2D(width, height, depthBufferFormat); + renderFrameBuffer.setDepthTexture(depthTexture); + } + } + + renderFrameBuffer.setColorTexture(renderedTexture); + + + } + + /** + * init the pass called internally + * @param renderer + * @param width + * @param height + * @param textureFormat + * @param depthBufferFormat + */ + public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat) { + init(renderer, width, height, textureFormat, depthBufferFormat, 1); + } + + public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSamples) { + init(renderer, width, height, textureFormat, depthBufferFormat, numSamples, false); + } + + /** + * init the pass called internally + * @param renderer + * @param width + * @param height + * @param textureFormat + * @param depthBufferFormat + * @param numSample + * @param material + */ + public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSample, Material material) { + init(renderer, width, height, textureFormat, depthBufferFormat, numSample); + passMaterial = material; + } + + public boolean requiresSceneAsTexture() { + return false; + } + + public boolean requiresDepthAsTexture() { + return false; + } + + public void beforeRender() { + } + + public FrameBuffer getRenderFrameBuffer() { + return renderFrameBuffer; + } + + public void setRenderFrameBuffer(FrameBuffer renderFrameBuffer) { + this.renderFrameBuffer = renderFrameBuffer; + } + + public Texture2D getDepthTexture() { + return depthTexture; + } + + public Texture2D getRenderedTexture() { + return renderedTexture; + } + + public void setRenderedTexture(Texture2D renderedTexture) { + this.renderedTexture = renderedTexture; + } + + public Material getPassMaterial() { + return passMaterial; + } + + public void setPassMaterial(Material passMaterial) { + this.passMaterial = passMaterial; + } + + public void cleanup(Renderer r) { + r.deleteFrameBuffer(renderFrameBuffer); + r.deleteImage(renderedTexture.getImage()); + if(depthTexture!=null){ + r.deleteImage(depthTexture.getImage()); + } + } + } + + /** + * returns the default pass texture format + * @return + */ + protected Format getDefaultPassTextureFormat() { + return Format.RGBA8; + } + + /** + * returns the default pass depth format + * @return + */ + protected Format getDefaultPassDepthFormat() { + return Format.Depth; + } + + /** + * contruct a Filter + */ + protected Filter() { + this("filter"); + } + + /** + * + * initialize this filter + * use InitFilter for overriding filter initialization + * @param manager the assetManager + * @param renderManager the renderManager + * @param vp the viewport + * @param w the width + * @param h the height + */ + protected final void init(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + // cleanup(renderManager.getRenderer()); + defaultPass = new Pass(); + defaultPass.init(renderManager.getRenderer(), w, h, getDefaultPassTextureFormat(), getDefaultPassDepthFormat()); + initFilter(manager, renderManager, vp, w, h); + } + + /** + * cleanup this filter + * @param r + */ + protected final void cleanup(Renderer r) { + processor = null; + if (defaultPass != null) { + defaultPass.cleanup(r); + } + if (postRenderPasses != null) { + for (Iterator it = postRenderPasses.iterator(); it.hasNext();) { + Pass pass = it.next(); + pass.cleanup(r); + } + } + cleanUpFilter(r); + } + + /** + * Initialization of sub classes filters + * This method is called once when the filter is added to the FilterPostProcessor + * It should contain Material initializations and extra passes initialization + * @param manager the assetManager + * @param renderManager the renderManager + * @param vp the viewPort where this filter is rendered + * @param w the width of the filter + * @param h the height of the filter + */ + protected abstract void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h); + + /** + * override this method if you have some cleanup to do + * @param r the renderer + */ + protected void cleanUpFilter(Renderer r) { + } + + /** + * Must return the material used for this filter. + * this method is called every frame. + * + * @return the material used for this filter. + */ + protected abstract Material getMaterial(); + + /** + * Override if you want to do something special with the depth texture; + * @param depthTexture + */ + protected void setDepthTexture(Texture depthTexture){ + getMaterial().setTexture("DepthTexture", depthTexture); + } + + /** + * Override this method if you want to make a pre pass, before the actual rendering of the frame + * @param queue + */ + protected void postQueue(RenderQueue queue) { + } + + /** + * Override this method if you want to modify parameters according to tpf before the rendering of the frame. + * This is usefull for animated filters + * Also it can be the place to render pre passes + * @param tpf the time used to render the previous frame + */ + protected void preFrame(float tpf) { + } + + /** + * Override this method if you want to make a pass just after the frame has been rendered and just before the filter rendering + * @param renderManager + * @param viewPort + * @param prevFilterBuffer + * @param sceneBuffer + */ + protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) { + } + + /** + * Override this method if you want to save extra properties when the filter is saved else only basic properties of the filter will be saved + * This method should always begin by super.write(ex); + * @param ex + * @throws IOException + */ + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(name, "name", ""); + oc.write(enabled, "enabled", true); + } + + /** + * Override this method if you want to load extra properties when the filter + * is loaded else only basic properties of the filter will be loaded + * This method should always begin by super.read(im); + */ + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + name = ic.readString("name", ""); + enabled = ic.readBoolean("enabled", true); + } + + /** + * returns the name of the filter + * @return the Filter's name + */ + public String getName() { + return name; + } + + /** + * Sets the name of the filter + * @param name + */ + public void setName(String name) { + this.name = name; + } + + /** + * returns the default pass frame buffer + * @return + */ + protected FrameBuffer getRenderFrameBuffer() { + return defaultPass.renderFrameBuffer; + } + + /** + * sets the default pas frame buffer + * @param renderFrameBuffer + */ + protected void setRenderFrameBuffer(FrameBuffer renderFrameBuffer) { + this.defaultPass.renderFrameBuffer = renderFrameBuffer; + } + + /** + * returns the rendered texture of this filter + * @return + */ + protected Texture2D getRenderedTexture() { + return defaultPass.renderedTexture; + } + + /** + * sets the rendered texture of this filter + * @param renderedTexture + */ + protected void setRenderedTexture(Texture2D renderedTexture) { + this.defaultPass.renderedTexture = renderedTexture; + } + + /** + * Override this method and return true if your Filter needs the depth texture + * + * @return true if your Filter need the depth texture + */ + protected boolean isRequiresDepthTexture() { + return false; + } + + /** + * Override this method and return false if your Filter does not need the scene texture + * + * @return false if your Filter does not need the scene texture + */ + protected boolean isRequiresSceneTexture() { + return true; + } + + /** + * returns the list of the postRender passes + * @return + */ + protected List getPostRenderPasses() { + return postRenderPasses; + } + + /** + * Enable or disable this filter + * @param enabled true to enable + */ + public void setEnabled(boolean enabled) { + if (processor != null) { + processor.setFilterState(this, enabled); + } else { + this.enabled = enabled; + } + } + + /** + * returns ttrue if the filter is enabled + * @return enabled + */ + public boolean isEnabled() { + return enabled; + } + + /** + * sets a reference to the FilterPostProcessor ti which this filter is attached + * @param proc + */ + protected void setProcessor(FilterPostProcessor proc) { + processor = proc; + } + } diff --git a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java new file mode 100644 index 000000000..0c0b627af --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java @@ -0,0 +1,562 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post; + +import com.jme3.asset.AssetManager; +import com.jme3.export.*; +import com.jme3.material.Material; +import com.jme3.renderer.*; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture2D; +import com.jme3.ui.Picture; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * A FilterPostProcessor is a processor that can apply several {@link Filter}s to a rendered scene
    + * It manages a list of filters that will be applied in the order in which they've been added to the list + * @author Rémy Bouquet aka Nehon + */ +public class FilterPostProcessor implements SceneProcessor, Savable { + + private RenderManager renderManager; + private Renderer renderer; + private ViewPort viewPort; + private FrameBuffer renderFrameBufferMS; + private int numSamples = 1; + private FrameBuffer renderFrameBuffer; + private Texture2D filterTexture; + private Texture2D depthTexture; + private List filters = new ArrayList(); + private AssetManager assetManager; + private Picture fsQuad; + private boolean computeDepth = false; + private FrameBuffer outputBuffer; + private int width; + private int height; + private float bottom; + private float left; + private float right; + private float top; + private int originalWidth; + private int originalHeight; + private int lastFilterIndex = -1; + private boolean cameraInit = false; + private boolean multiView = false; + + /** + * Create a FilterProcessor + * @param assetManager the assetManager + */ + public FilterPostProcessor(AssetManager assetManager) { + this.assetManager = assetManager; + } + + /** + * Don't use this constructor, use {@link #FilterPostProcessor(AssetManager assetManager)}
    + * This constructor is used for serialization only + */ + public FilterPostProcessor() { + } + + /** + * Adds a filter to the filters list
    + * @param filter the filter to add + */ + public void addFilter(Filter filter) { + if (filter == null) { + throw new IllegalArgumentException("Filter cannot be null."); + } + filters.add(filter); + + if (isInitialized()) { + initFilter(filter, viewPort); + } + + setFilterState(filter, filter.isEnabled()); + + } + + /** + * removes this filters from the filters list + * @param filter + */ + public void removeFilter(Filter filter) { + if (filter == null) { + throw new IllegalArgumentException("Filter cannot be null."); + } + filters.remove(filter); + filter.cleanup(renderer); + updateLastFilterIndex(); + } + + public Iterator getFilterIterator() { + return filters.iterator(); + } + + public void initialize(RenderManager rm, ViewPort vp) { + renderManager = rm; + renderer = rm.getRenderer(); + viewPort = vp; + fsQuad = new Picture("filter full screen quad"); + fsQuad.setWidth(1); + fsQuad.setHeight(1); + + Camera cam = vp.getCamera(); + + //save view port diensions + left = cam.getViewPortLeft(); + right = cam.getViewPortRight(); + top = cam.getViewPortTop(); + bottom = cam.getViewPortBottom(); + originalWidth = cam.getWidth(); + originalHeight = cam.getHeight(); + //first call to reshape + reshape(vp, cam.getWidth(), cam.getHeight()); + } + + /** + * init the given filter + * @param filter + * @param vp + */ + private void initFilter(Filter filter, ViewPort vp) { + filter.setProcessor(this); + if (filter.isRequiresDepthTexture()) { + if (!computeDepth && renderFrameBuffer != null) { + depthTexture = new Texture2D(width, height, Format.Depth24); + renderFrameBuffer.setDepthTexture(depthTexture); + } + computeDepth = true; + filter.init(assetManager, renderManager, vp, width, height); + filter.setDepthTexture(depthTexture); + } else { + filter.init(assetManager, renderManager, vp, width, height); + } + } + + /** + * renders a filter on a fullscreen quad + * @param r + * @param buff + * @param mat + */ + private void renderProcessing(Renderer r, FrameBuffer buff, Material mat) { + if (buff == outputBuffer) { + viewPort.getCamera().resize(originalWidth, originalHeight, false); + viewPort.getCamera().setViewPort(left, right, bottom, top); + viewPort.getCamera().update(); + renderManager.setCamera( viewPort.getCamera(), false); + if (mat.getAdditionalRenderState().isDepthWrite()) { + mat.getAdditionalRenderState().setDepthTest(false); + mat.getAdditionalRenderState().setDepthWrite(false); + } + }else{ + viewPort.getCamera().resize(buff.getWidth(), buff.getHeight(), false); + viewPort.getCamera().setViewPort(0, 1, 0, 1); + viewPort.getCamera().update(); + renderManager.setCamera( viewPort.getCamera(), false); + mat.getAdditionalRenderState().setDepthTest(true); + mat.getAdditionalRenderState().setDepthWrite(true); + } + + + fsQuad.setMaterial(mat); + fsQuad.updateGeometricState(); + + 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.postQueue(rq); + } + } + + } + + /** + * iterate through the filter list and renders filters + * @param r + * @param sceneFb + */ + private void renderFilterChain(Renderer r, FrameBuffer sceneFb) { + Texture2D tex = filterTexture; + FrameBuffer buff = sceneFb; + boolean msDepth = depthTexture != null && depthTexture.getImage().getMultiSamples() > 1; + for (int i = 0; i < filters.size(); i++) { + Filter filter = filters.get(i); + if (filter.isEnabled()) { + if (filter.getPostRenderPasses() != null) { + for (Iterator it1 = filter.getPostRenderPasses().iterator(); it1.hasNext();) { + Filter.Pass pass = it1.next(); + pass.beforeRender(); + if (pass.requiresSceneAsTexture()) { + pass.getPassMaterial().setTexture("Texture", tex); + if (tex.getImage().getMultiSamples() > 1) { + pass.getPassMaterial().setInt("NumSamples", tex.getImage().getMultiSamples()); + } else { + pass.getPassMaterial().clearParam("NumSamples"); + + } + } + if (pass.requiresDepthAsTexture()) { + pass.getPassMaterial().setTexture("DepthTexture", depthTexture); + if (msDepth) { + pass.getPassMaterial().setInt("NumSamplesDepth", depthTexture.getImage().getMultiSamples()); + } else { + pass.getPassMaterial().clearParam("NumSamplesDepth"); + } + } + renderProcessing(r, pass.getRenderFrameBuffer(), pass.getPassMaterial()); + } + } + + filter.postFrame(renderManager, viewPort, buff, sceneFb); + + Material mat = filter.getMaterial(); + if (msDepth && filter.isRequiresDepthTexture()) { + mat.setInt("NumSamplesDepth", depthTexture.getImage().getMultiSamples()); + } + + if (filter.isRequiresSceneTexture()) { + mat.setTexture("Texture", tex); + if (tex.getImage().getMultiSamples() > 1) { + mat.setInt("NumSamples", tex.getImage().getMultiSamples()); + } else { + mat.clearParam("NumSamples"); + } + } + + buff = outputBuffer; + if (i != lastFilterIndex) { + buff = filter.getRenderFrameBuffer(); + tex = filter.getRenderedTexture(); + + } + renderProcessing(r, buff, mat); + } + } + } + + public void postFrame(FrameBuffer out) { + + FrameBuffer sceneBuffer = renderFrameBuffer; + if (renderFrameBufferMS != null && !renderer.getCaps().contains(Caps.OpenGL31)) { + renderer.copyFrameBuffer(renderFrameBufferMS, renderFrameBuffer); + } else if (renderFrameBufferMS != null) { + sceneBuffer = renderFrameBufferMS; + } + renderFilterChain(renderer, sceneBuffer); + renderer.setFrameBuffer(outputBuffer); + + //viewport can be null if no filters are enabled + if (viewPort != null) { + renderManager.setCamera(viewPort.getCamera(), false); + } + + } + + public void preFrame(float tpf) { + if (filters.isEmpty() || lastFilterIndex == -1) { + //If the camera is initialized and there are no filter to render, the camera viewport is restored as it was + if (cameraInit) { + viewPort.getCamera().resize(originalWidth, originalHeight, true); + viewPort.getCamera().setViewPort(left, right, bottom, top); + viewPort.setOutputFrameBuffer(outputBuffer); + cameraInit = false; + } + + } else { + setupViewPortFrameBuffer(); + //if we are ina multiview situation we need to resize the camera + //to the viewportsize so that the backbuffer is rendered correctly + if (multiView) { + viewPort.getCamera().resize(width, height, false); + viewPort.getCamera().setViewPort(0, 1, 0, 1); + viewPort.getCamera().update(); + renderManager.setCamera(viewPort.getCamera(), false); + } + } + + for (Iterator it = filters.iterator(); it.hasNext();) { + Filter filter = it.next(); + if (filter.isEnabled()) { + filter.preFrame(tpf); + } + } + + } + + /** + * sets the filter to enabled or disabled + * @param filter + * @param enabled + */ + protected void setFilterState(Filter filter, boolean enabled) { + if (filters.contains(filter)) { + filter.enabled = enabled; + updateLastFilterIndex(); + } + } + + /** + * compute the index of the last filter to render + */ + private void updateLastFilterIndex() { + lastFilterIndex = -1; + for (int i = filters.size() - 1; i >= 0 && lastFilterIndex == -1; i--) { + if (filters.get(i).isEnabled()) { + lastFilterIndex = i; + //the Fpp is initialized, but the viwport framebuffer is the + //original out framebuffer so we must recover from a situation + //where no filter was enabled. So we set th correc framebuffer + //on the viewport + if(isInitialized() && viewPort.getOutputFrameBuffer()==outputBuffer){ + setupViewPortFrameBuffer(); + } + return; + } + } + if (isInitialized() && lastFilterIndex == -1) { + //There is no enabled filter, we restore the original framebuffer + //to the viewport to bypass the fpp. + viewPort.setOutputFrameBuffer(outputBuffer); + } + } + + public void cleanup() { + if (viewPort != null) { + //reseting the viewport camera viewport to its initial value + viewPort.getCamera().resize(originalWidth, originalHeight, true); + viewPort.getCamera().setViewPort(left, right, bottom, top); + viewPort.setOutputFrameBuffer(outputBuffer); + viewPort = null; + renderManager.getRenderer().deleteFrameBuffer(renderFrameBuffer); + if(depthTexture!=null){ + renderManager.getRenderer().deleteImage(depthTexture.getImage()); + } + renderManager.getRenderer().deleteImage(filterTexture.getImage()); + if(renderFrameBufferMS != null){ + renderManager.getRenderer().deleteFrameBuffer(renderFrameBufferMS); + } + for (Filter filter : filters) { + filter.cleanup(renderer); + } + } + + } + + public void reshape(ViewPort vp, int w, int h) { + Camera cam = vp.getCamera(); + //this has no effect at first init but is useful when resizing the canvas with multi views + cam.setViewPort(left, right, bottom, top); + //resizing the camera to fit the new viewport and saving original dimensions + cam.resize(w, h, false); + left = cam.getViewPortLeft(); + right = cam.getViewPortRight(); + top = cam.getViewPortTop(); + bottom = cam.getViewPortBottom(); + originalWidth = w; + originalHeight = h; + + //computing real dimension of the viewport and resizing the camera + width = (int) (w * (Math.abs(right - left))); + height = (int) (h * (Math.abs(bottom - top))); + width = Math.max(1, width); + height = Math.max(1, height); + + //Testing original versus actual viewport dimension. + //If they are different we are in a multiview situation and + //camera must be handled differently + if(originalWidth!=width || originalHeight!=height){ + multiView = true; + } + + cameraInit = 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; + } 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); + } + setupViewPortFrameBuffer(); + } + + /** + * return the number of samples for antialiasing + * @return numSamples + */ + public int getNumSamples() { + return numSamples; + } + + /** + * + * Removes all the filters from this processor + */ + public void removeAllFilters() { + filters.clear(); + updateLastFilterIndex(); + } + + /** + * Sets the number of samples for antialiasing + * @param numSamples the number of Samples + */ + public void setNumSamples(int numSamples) { + if (numSamples <= 0) { + throw new IllegalArgumentException("numSamples must be > 0"); + } + + this.numSamples = numSamples; + } + + /** + * Sets the asset manager for this processor + * @param assetManager + */ + public void setAssetManager(AssetManager assetManager) { + this.assetManager = assetManager; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(numSamples, "numSamples", 0); + oc.writeSavableArrayList((ArrayList) filters, "filters", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + numSamples = ic.readInt("numSamples", 0); + filters = ic.readSavableArrayList("filters", null); + for (Filter filter : filters) { + filter.setProcessor(this); + setFilterState(filter, filter.isEnabled()); + } + assetManager = im.getAssetManager(); + } + + /** + * For internal use only
    + * returns the depth texture of the scene + * @return the depth texture + */ + public Texture2D getDepthTexture() { + return depthTexture; + } + + /** + * For internal use only
    + * returns the rendered texture of the scene + * @return the filter texture + */ + public Texture2D getFilterTexture() { + return filterTexture; + } + + /** + * returns the first filter in the list assignable form the given type + * @param + * @param filterType the filter type + * @return a filter assignable form the given type + */ + public T getFilter(Class filterType) { + for (Filter c : filters) { + if (filterType.isAssignableFrom(c.getClass())) { + return (T) c; + } + } + return null; + } + + /** + * returns an unmodifiable version of the filter list. + * @return the filters list + */ + public List getFilterList(){ + return Collections.unmodifiableList(filters); + } + + private void setupViewPortFrameBuffer() { + if (renderFrameBufferMS != null) { + viewPort.setOutputFrameBuffer(renderFrameBufferMS); + } else { + viewPort.setOutputFrameBuffer(renderFrameBuffer); + } + } + } diff --git a/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java b/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java new file mode 100644 index 000000000..9dab905b3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.renderer.*; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.Texture2D; +import com.jme3.ui.Picture; +import java.util.Collection; +import java.util.logging.Logger; + +public class HDRRenderer implements SceneProcessor { + + private static final int LUMMODE_NONE = 0x1, + LUMMODE_ENCODE_LUM = 0x2, + LUMMODE_DECODE_LUM = 0x3; + + private Renderer renderer; + private RenderManager renderManager; + private ViewPort viewPort; + private static final Logger logger = Logger.getLogger(HDRRenderer.class.getName()); + + private Camera fbCam = new Camera(1, 1); + + private FrameBuffer msFB; + + private FrameBuffer mainSceneFB; + private Texture2D mainScene; + private FrameBuffer scene64FB; + private Texture2D scene64; + private FrameBuffer scene8FB; + private Texture2D scene8; + private FrameBuffer scene1FB[] = new FrameBuffer[2]; + private Texture2D scene1[] = new Texture2D[2]; + + private Material hdr64; + private Material hdr8; + private Material hdr1; + private Material tone; + + private Picture fsQuad; + private float time = 0; + private int curSrc = -1; + private int oppSrc = -1; + private float blendFactor = 0; + + private int numSamples = 0; + private float exposure = 0.18f; + private float whiteLevel = 100f; + private float throttle = -1; + private int maxIterations = -1; + private Image.Format bufFormat = Format.RGB16F; + + private MinFilter fbMinFilter = MinFilter.BilinearNoMipMaps; + private MagFilter fbMagFilter = MagFilter.Bilinear; + private AssetManager manager; + + private boolean enabled = true; + + public HDRRenderer(AssetManager manager, Renderer renderer){ + this.manager = manager; + this.renderer = renderer; + + Collection caps = 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; + } + + 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.RGB8; + scene64FB = new FrameBuffer(64, 64, 1); + scene64 = new Texture2D(64, 64, lumFmt); + scene64FB.setColorTexture(scene64); + scene64.setMagFilter(fbMagFilter); + scene64.setMinFilter(fbMinFilter); + + scene8FB = new FrameBuffer(8, 8, 1); + scene8 = new Texture2D(8, 8, lumFmt); + scene8FB.setColorTexture(scene8); + scene8.setMagFilter(fbMagFilter); + scene8.setMinFilter(fbMinFilter); + + scene1FB[0] = new FrameBuffer(1, 1, 1); + scene1[0] = new Texture2D(1, 1, lumFmt); + scene1FB[0].setColorTexture(scene1[0]); + + scene1FB[1] = new FrameBuffer(1, 1, 1); + scene1[1] = new Texture2D(1, 1, lumFmt); + scene1FB[1].setColorTexture(scene1[1]); + + // prepare tonemap shader + tone = new Material(manager, "Common/MatDefs/Hdr/ToneMap.j3md"); + tone.setFloat("A", 0.18f); + tone.setFloat("White", 100); + + // load(); + int w = vp.getCamera().getWidth(); + int h = vp.getCamera().getHeight(); + reshape(vp, w, h); + + + } + + public void preFrame(float tpf) { + if (!enabled) + return; + + time += tpf; + blendFactor = (time / throttle); + } + + public void postQueue(RenderQueue rq) { + } + + public void postFrame(FrameBuffer out) { + if (!enabled) + return; + + if (msFB != null){ + // first render to multisampled FB +// renderer.setFrameBuffer(msFB); +// renderer.clearBuffers(true,true,true); +// +// renderManager.renderViewPortRaw(viewPort); + + // render back to non-multisampled FB + renderer.copyFrameBuffer(msFB, mainSceneFB); + }else{ +// renderer.setFrameBuffer(mainSceneFB); +// renderer.clearBuffers(true,true,false); +// +// renderManager.renderViewPortRaw(viewPort); + } + + // should we update avg lum? + if (throttle == -1){ + // update every frame + curSrc = 0; + oppSrc = 0; + blendFactor = 0; + time = 0; + updateAverageLuminance(renderer); + }else{ + if (curSrc == -1){ + curSrc = 0; + oppSrc = 0; + + // initial update + updateAverageLuminance(renderer); + + blendFactor = 0; + time = 0; + }else if (time > throttle){ + + // time to switch + oppSrc = curSrc; + curSrc = opposite(curSrc); + + updateAverageLuminance(renderer); + + blendFactor = 0; + time = 0; + } + } + + // since out == mainSceneFB, tonemap into the main screen instead + //renderToneMap(renderer, out); + renderToneMap(renderer, null); + + renderManager.setCamera(viewPort.getCamera(), false); + } + + public void cleanup() { + if (!enabled) + return; + + if (msFB != null) + renderer.deleteFrameBuffer(msFB); + if (mainSceneFB != null) + renderer.deleteFrameBuffer(mainSceneFB); + if (scene64FB != null){ + renderer.deleteFrameBuffer(scene64FB); + renderer.deleteFrameBuffer(scene8FB); + renderer.deleteFrameBuffer(scene1FB[0]); + renderer.deleteFrameBuffer(scene1FB[1]); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java b/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java new file mode 100644 index 000000000..3b318b514 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post; + +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/jme3-core/src/main/java/com/jme3/post/SceneProcessor.java b/jme3-core/src/main/java/com/jme3/post/SceneProcessor.java new file mode 100644 index 000000000..4f90e52ff --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/post/SceneProcessor.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; + +/** + * Scene processors are used to compute/render things before and after the classic render of the scene. + * They have to be added to a viewport and are rendered in the order they've been added + * + * @author Kirill Vainer + */ +public interface SceneProcessor { + + /** + * Called in the render thread to initialize the scene processor. + * + * @param rm The render manager to which the SP was added to + * @param vp The viewport to which the SP is assigned + */ + public void initialize(RenderManager rm, ViewPort vp); + + /** + * Called when the resolution of the viewport has been changed. + * @param vp + */ + public void reshape(ViewPort vp, int w, int h); + + /** + * @return True if initialize() has been called on this SceneProcessor, + * false if otherwise. + */ + public boolean isInitialized(); + + /** + * Called before a frame + * + * @param tpf Time per frame + */ + public void preFrame(float tpf); + + /** + * Called after the scene graph has been queued, but before it is flushed. + * + * @param rq The render queue + */ + public void postQueue(RenderQueue rq); + + /** + * Called after a frame has been rendered and the queue flushed. + * + * @param out The FB to which the scene was rendered. + */ + public void postFrame(FrameBuffer out); + + /** + * Called when the SP is removed from the RM. + */ + public void cleanup(); + +} diff --git a/jme3-core/src/main/java/com/jme3/post/package.html b/jme3-core/src/main/java/com/jme3/post/package.html new file mode 100644 index 000000000..632e7153d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/post/package.html @@ -0,0 +1,24 @@ + + + + + + + + + +The com.jme3.post package provides utilities for +render processing. +

    +The {@link com.jme3.post.SceneProcessor} interface is used as the base interface +for all render processing. The SceneProcessor contains hooks for various rendering +events. +

    +One use of render processing is post-processing, which is applying effects +on an already-rendered scene. The engine's post-processing system is implemented +in the {@link com.jme3.post.FilterPostProcessor} class, which contains a list +of {@link com.jme3.post.Filter filters}. Each are invoked in order to apply +various effects on the rendered scene. + + + diff --git a/jme3-core/src/main/java/com/jme3/renderer/Camera.java b/jme3-core/src/main/java/com/jme3/renderer/Camera.java new file mode 100644 index 000000000..2cbf811c3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/Camera.java @@ -0,0 +1,1468 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingVolume; +import com.jme3.export.*; +import com.jme3.math.*; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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 necessary to render objects + * with the graphics library. Two matrices are generated, the view matrix + * transforms objects from world space into eye space, while the projection + * matrix transforms objects from eye space into clip space. + *

    + *

    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()); + + /** + * The FrustumIntersect enum is returned as a result + * of a culling check operation, + * see {@link #contains(com.jme3.bounding.BoundingVolume) } + */ + public enum FrustumIntersect { + + /** + * defines a constant assigned to spatials that are completely outside + * of this camera's view frustum. + */ + Outside, + /** + * defines a constant assigned to spatials that are completely inside + * the camera's view frustum. + */ + Inside, + /** + * defines a constant assigned to spatials that are intersecting one of + * the six planes that define the view frustum. + */ + Intersects; + } + /** + * LEFT_PLANE represents the left plane of the camera frustum. + */ + private static final int LEFT_PLANE = 0; + /** + * RIGHT_PLANE represents the right plane of the camera frustum. + */ + private static final int RIGHT_PLANE = 1; + /** + * BOTTOM_PLANE represents the bottom plane of the camera frustum. + */ + private static final int BOTTOM_PLANE = 2; + /** + * TOP_PLANE represents the top plane of the camera frustum. + */ + private static final int TOP_PLANE = 3; + /** + * FAR_PLANE represents the far plane of the camera frustum. + */ + private static final int FAR_PLANE = 4; + /** + * NEAR_PLANE represents the near plane of the camera frustum. + */ + private static final int NEAR_PLANE = 5; + /** + * FRUSTUM_PLANES represents the number of planes of the camera frustum. + */ + private static final int FRUSTUM_PLANES = 6; + /** + * MAX_WORLD_PLANES holds the maximum planes allowed by the system. + */ + private static final int MAX_WORLD_PLANES = 6; + /** + * Camera's location + */ + protected Vector3f location; + /** + * The orientation of the camera. + */ + protected Quaternion rotation; + /** + * Distance from camera to near frustum plane. + */ + protected float frustumNear; + /** + * Distance from camera to far frustum plane. + */ + protected float frustumFar; + /** + * Distance from camera to left frustum plane. + */ + protected float frustumLeft; + /** + * Distance from camera to right frustum plane. + */ + protected float frustumRight; + /** + * Distance from camera to top frustum plane. + */ + protected float frustumTop; + /** + * Distance from camera to bottom frustum plane. + */ + protected float frustumBottom; + //Temporary values computed in onFrustumChange that are needed if a + //call is made to onFrameChange. + protected float[] coeffLeft; + protected float[] coeffRight; + protected float[] coeffBottom; + protected float[] coeffTop; + //view port coordinates + /** + * Percent value on display where horizontal viewing starts for this camera. + * Default is 0. + */ + protected float viewPortLeft; + /** + * Percent value on display where horizontal viewing ends for this camera. + * Default is 1. + */ + protected float viewPortRight; + /** + * Percent value on display where vertical viewing ends for this camera. + * Default is 1. + */ + protected float viewPortTop; + /** + * Percent value on display where vertical viewing begins for this camera. + * Default is 0. + */ + protected float viewPortBottom; + /** + * Array holding the planes that this camera will check for culling. + */ + protected Plane[] worldPlane; + /** + * A mask value set during contains() that allows fast culling of a Node's + * children. + */ + private int planeState; + protected int width; + protected int height; + protected boolean viewportChanged = true; + /** + * store the value for field parallelProjection + */ + private boolean parallelProjection = true; + protected Matrix4f projectionMatrixOverride; + protected Matrix4f viewMatrix = new Matrix4f(); + protected Matrix4f projectionMatrix = new Matrix4f(); + protected Matrix4f viewProjectionMatrix = new Matrix4f(); + private BoundingBox guiBounding = new BoundingBox(); + /** The camera's name. */ + protected String name; + + /** + * Serialization only. Do not use. + */ + public Camera() { + worldPlane = new Plane[MAX_WORLD_PLANES]; + for (int i = 0; i < MAX_WORLD_PLANES; i++) { + worldPlane[i] = new Plane(); + } + } + + /** + * Constructor instantiates a new Camera object. All + * values of the camera are set to default. + */ + public Camera(int width, int height) { + this(); + location = new Vector3f(); + rotation = new Quaternion(); + + frustumNear = 1.0f; + frustumFar = 2.0f; + frustumLeft = -0.5f; + frustumRight = 0.5f; + frustumTop = 0.5f; + frustumBottom = -0.5f; + + coeffLeft = new float[2]; + coeffRight = new float[2]; + coeffBottom = new float[2]; + coeffTop = new float[2]; + + viewPortLeft = 0.0f; + viewPortRight = 1.0f; + viewPortTop = 1.0f; + viewPortBottom = 0.0f; + + this.width = width; + this.height = height; + + onFrustumChange(); + onViewPortChange(); + onFrameChange(); + + logger.log(Level.FINE, "Camera created (W: {0}, H: {1})", new Object[]{width, height}); + } + + @Override + public Camera clone() { + try { + Camera cam = (Camera) super.clone(); + cam.viewportChanged = true; + cam.planeState = 0; + + cam.worldPlane = new Plane[MAX_WORLD_PLANES]; + for (int i = 0; i < worldPlane.length; i++) { + cam.worldPlane[i] = worldPlane[i].clone(); + } + + cam.coeffLeft = new float[2]; + cam.coeffRight = new float[2]; + cam.coeffBottom = new float[2]; + cam.coeffTop = new float[2]; + + cam.location = location.clone(); + cam.rotation = rotation.clone(); + + if (projectionMatrixOverride != null) { + cam.projectionMatrixOverride = projectionMatrixOverride.clone(); + } + + cam.viewMatrix = viewMatrix.clone(); + cam.projectionMatrix = projectionMatrix.clone(); + cam.viewProjectionMatrix = viewProjectionMatrix.clone(); + cam.guiBounding = (BoundingBox) guiBounding.clone(); + + cam.update(); + + return cam; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * This method copise the settings of the given camera. + * + * @param cam + * the camera we copy the settings from + */ + public void copyFrom(Camera cam) { + location.set(cam.location); + rotation.set(cam.rotation); + + frustumNear = cam.frustumNear; + frustumFar = cam.frustumFar; + frustumLeft = cam.frustumLeft; + frustumRight = cam.frustumRight; + frustumTop = cam.frustumTop; + frustumBottom = cam.frustumBottom; + + coeffLeft[0] = cam.coeffLeft[0]; + coeffLeft[1] = cam.coeffLeft[1]; + coeffRight[0] = cam.coeffRight[0]; + coeffRight[1] = cam.coeffRight[1]; + coeffBottom[0] = cam.coeffBottom[0]; + coeffBottom[1] = cam.coeffBottom[1]; + coeffTop[0] = cam.coeffTop[0]; + coeffTop[1] = cam.coeffTop[1]; + + viewPortLeft = cam.viewPortLeft; + viewPortRight = cam.viewPortRight; + viewPortTop = cam.viewPortTop; + viewPortBottom = cam.viewPortBottom; + + this.width = cam.width; + this.height = cam.height; + + this.planeState = 0; + this.viewportChanged = true; + for (int i = 0; i < MAX_WORLD_PLANES; ++i) { + worldPlane[i].setNormal(cam.worldPlane[i].getNormal()); + worldPlane[i].setConstant(cam.worldPlane[i].getConstant()); + } + + this.parallelProjection = cam.parallelProjection; + if (cam.projectionMatrixOverride != null) { + if (this.projectionMatrixOverride == null) { + this.projectionMatrixOverride = cam.projectionMatrixOverride.clone(); + } else { + this.projectionMatrixOverride.set(cam.projectionMatrixOverride); + } + } else { + this.projectionMatrixOverride = null; + } + this.viewMatrix.set(cam.viewMatrix); + this.projectionMatrix.set(cam.projectionMatrix); + this.viewProjectionMatrix.set(cam.viewProjectionMatrix); + + this.guiBounding.setXExtent(cam.guiBounding.getXExtent()); + this.guiBounding.setYExtent(cam.guiBounding.getYExtent()); + this.guiBounding.setZExtent(cam.guiBounding.getZExtent()); + this.guiBounding.setCenter(cam.guiBounding.getCenter()); + this.guiBounding.setCheckPlane(cam.guiBounding.getCheckPlane()); + + this.name = cam.name; + } + + /** + * This method sets the cameras name. + * @param name the cameras name + */ + public void setName(String name) { + this.name = name; + } + + /** + * This method returns the cameras name. + * @return the cameras name + */ + public String getName() { + return name; + } + + /** + * Sets a clipPlane for this camera. + * The cliPlane is used to recompute the projectionMatrix using the plane as the near plane + * This technique is known as the oblique near-plane clipping method introduced by Eric Lengyel + * more info here + * + * + * 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 + * + * + * Note that this will work properly only if it's called on each update, and be aware that it won't work properly with the sky bucket. + * if you want to handle the sky bucket, look at how it's done in SimpleWaterProcessor.java + * @param clipPlane the plane + */ + public void setClipPlane(Plane clipPlane) { + setClipPlane(clipPlane, clipPlane.whichSide(location)); + } + + /** + * Resizes this camera's view with the given width and height. This is + * similar to constructing a new camera, but reusing the same Object. This + * method is called by an associated {@link RenderManager} to notify the camera of + * changes in the display dimensions. + * + * @param width the view width + * @param height the view height + * @param fixAspect If true, the camera's aspect ratio will be recomputed. + * Recomputing the aspect ratio requires changing the frustum values. + */ + public void resize(int width, int height, boolean fixAspect) { + this.width = width; + this.height = height; + onViewPortChange(); + + if (fixAspect /*&& !parallelProjection*/) { + frustumRight = frustumTop * ((float) width / height); + frustumLeft = -frustumRight; + onFrustumChange(); + } + } + + /** + * 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. + */ + 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(); + } + + /** + * lookAtDirection sets the direction the camera is facing + * given a direction and an up vector. + * + * @param direction the direction this camera is facing. + */ + public void lookAtDirection(Vector3f direction, Vector3f up) { + this.rotation.lookAt(direction, up); + onFrameChange(); + } + + /** + * 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.jme3.math.Quaternion) + */ + 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.normalizeLocal(); + 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; + + // Camera is no longer parallel projection even if it was before + parallelProjection = false; + + 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(); + Vector3f newDirection = vars.vect1; + Vector3f newUp = vars.vect2; + Vector3f newLeft = vars.vect3; + + newDirection.set(pos).subtractLocal(location).normalizeLocal(); + + newUp.set(worldUpVector).normalizeLocal(); + if (newUp.equals(Vector3f.ZERO)) { + newUp.set(Vector3f.UNIT_Y); + } + + newLeft.set(newUp).crossLocal(newDirection).normalizeLocal(); + if (newLeft.equals(Vector3f.ZERO)) { + if (newDirection.x != 0) { + newLeft.set(newDirection.y, -newDirection.x, 0f); + } else { + newLeft.set(0f, newDirection.z, -newDirection.y); + } + } + + newUp.set(newDirection).crossLocal(newLeft).normalizeLocal(); + + this.rotation.fromAxes(newLeft, newUp, newDirection); + this.rotation.normalizeLocal(); + vars.release(); + + onFrameChange(); + } + + /** + * 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) { + this.viewPortLeft = left; + this.viewPortRight = right; + this.viewPortBottom = bottom; + this.viewPortTop = top; + onViewPortChange(); + } + + /** + * Returns the pseudo distance from the given position to the near + * plane of the camera. This is used for render queue sorting. + * @param pos The position to compute a distance to. + * @return Distance from the far plane to the point. + */ + public float distanceToNearPlane(Vector3f pos) { + return worldPlane[NEAR_PLANE].pseudoDistance(pos); + } + + /** + * 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; + } + + /** + * containsGui tests a bounding volume against the ortho + * bounding box of the camera. A bounding box spanning from + * 0, 0 to Width, Height. Constrained by the viewport settings on the + * camera. + * + * @param bound the bound to check for culling + * @return True if the camera contains the gui element bounding volume. + */ + public boolean containsGui(BoundingVolume bound) { + if (bound == null) { + return true; + } + return guiBounding.intersects(bound); + } + + /** + * @return the view matrix of the camera. + * The view matrix transforms world space into eye space. + * This matrix is usually defined by the position and + * orientation of the camera. + */ + public Matrix4f getViewMatrix() { + return viewMatrix; + } + + /** + * Overrides the projection matrix used by the camera. Will + * use the matrix for computing the view projection matrix as well. + * Use null argument to return to normal functionality. + * + * @param projMatrix + */ + public void setProjectionMatrix(Matrix4f projMatrix) { + projectionMatrixOverride = projMatrix; + updateViewProjection(); + } + + /** + * @return the projection matrix of the camera. + * The view projection matrix transforms eye space into clip space. + * This matrix is usually defined by the viewport and perspective settings + * of the camera. + */ + public Matrix4f getProjectionMatrix() { + if (projectionMatrixOverride != null) { + return projectionMatrixOverride; + } + + return projectionMatrix; + } + + /** + * Updates the view projection matrix. + */ + public void updateViewProjection() { + if (projectionMatrixOverride != null) { + viewProjectionMatrix.set(projectionMatrixOverride).multLocal(viewMatrix); + } else { + //viewProjectionMatrix.set(viewMatrix).multLocal(projectionMatrix); + viewProjectionMatrix.set(projectionMatrix).multLocal(viewMatrix); + } + } + + /** + * @return The result of multiplying the projection matrix by the view + * matrix. This matrix is required for rendering an object. It is + * precomputed so as to not compute it every time an object is rendered. + */ + public Matrix4f getViewProjectionMatrix() { + return viewProjectionMatrix; + } + + /** + * @return True if the viewport (width, height, left, right, bottom, up) + * has been changed. This is needed in the renderer so that the proper + * viewport can be set-up. + */ + public boolean isViewportChanged() { + return viewportChanged; + } + + /** + * Clears the viewport changed flag once it has been updated inside + * the renderer. + */ + public void clearViewportChanged() { + viewportChanged = false; + } + + /** + * Called when the viewport has been changed. + */ + public void onViewPortChange() { + viewportChanged = true; + setGuiBounding(); + } + + private void setGuiBounding() { + float sx = width * viewPortLeft; + float ex = width * viewPortRight; + float sy = height * viewPortBottom; + float ey = height * viewPortTop; + float xExtent = Math.max(0f, (ex - sx) / 2f); + float yExtent = Math.max(0f, (ey - sy) / 2f); + guiBounding.setCenter(new Vector3f(sx + xExtent, sy + yExtent, 0)); + guiBounding.setXExtent(xExtent); + guiBounding.setYExtent(yExtent); + guiBounding.setZExtent(Float.MAX_VALUE); + } + + /** + * 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() { + TempVars vars = TempVars.get(); + + Vector3f left = getLeft(vars.vect1); + Vector3f direction = getDirection(vars.vect2); + Vector3f up = getUp(vars.vect3); + + float dirDotLocation = direction.dot(location); + + // left plane + Vector3f leftPlaneNormal = worldPlane[LEFT_PLANE].getNormal(); + leftPlaneNormal.x = left.x * coeffLeft[0]; + leftPlaneNormal.y = left.y * coeffLeft[0]; + leftPlaneNormal.z = left.z * coeffLeft[0]; + leftPlaneNormal.addLocal(direction.x * coeffLeft[1], direction.y + * coeffLeft[1], direction.z * coeffLeft[1]); + worldPlane[LEFT_PLANE].setConstant(location.dot(leftPlaneNormal)); + + // right plane + Vector3f rightPlaneNormal = worldPlane[RIGHT_PLANE].getNormal(); + rightPlaneNormal.x = left.x * coeffRight[0]; + rightPlaneNormal.y = left.y * coeffRight[0]; + rightPlaneNormal.z = left.z * coeffRight[0]; + rightPlaneNormal.addLocal(direction.x * coeffRight[1], direction.y + * coeffRight[1], direction.z * coeffRight[1]); + worldPlane[RIGHT_PLANE].setConstant(location.dot(rightPlaneNormal)); + + // bottom plane + Vector3f bottomPlaneNormal = worldPlane[BOTTOM_PLANE].getNormal(); + bottomPlaneNormal.x = up.x * coeffBottom[0]; + bottomPlaneNormal.y = up.y * coeffBottom[0]; + bottomPlaneNormal.z = up.z * coeffBottom[0]; + bottomPlaneNormal.addLocal(direction.x * coeffBottom[1], direction.y + * coeffBottom[1], direction.z * coeffBottom[1]); + worldPlane[BOTTOM_PLANE].setConstant(location.dot(bottomPlaneNormal)); + + // top plane + Vector3f topPlaneNormal = worldPlane[TOP_PLANE].getNormal(); + topPlaneNormal.x = up.x * coeffTop[0]; + topPlaneNormal.y = up.y * coeffTop[0]; + topPlaneNormal.z = up.z * coeffTop[0]; + topPlaneNormal.addLocal(direction.x * coeffTop[1], direction.y + * coeffTop[1], direction.z * coeffTop[1]); + worldPlane[TOP_PLANE].setConstant(location.dot(topPlaneNormal)); + + if (isParallelProjection()) { + worldPlane[LEFT_PLANE].setConstant(worldPlane[LEFT_PLANE].getConstant() + frustumLeft); + worldPlane[RIGHT_PLANE].setConstant(worldPlane[RIGHT_PLANE].getConstant() - frustumRight); + worldPlane[TOP_PLANE].setConstant(worldPlane[TOP_PLANE].getConstant() - frustumTop); + worldPlane[BOTTOM_PLANE].setConstant(worldPlane[BOTTOM_PLANE].getConstant() + frustumBottom); + } + + // far plane + worldPlane[FAR_PLANE].setNormal(left); + worldPlane[FAR_PLANE].setNormal(-direction.x, -direction.y, -direction.z); + worldPlane[FAR_PLANE].setConstant(-(dirDotLocation + frustumFar)); + + // near plane + worldPlane[NEAR_PLANE].setNormal(direction.x, direction.y, direction.z); + worldPlane[NEAR_PLANE].setConstant(dirDotLocation + frustumNear); + + viewMatrix.fromFrame(location, direction, up, left); + + vars.release(); + +// viewMatrix.transposeLocal(); + updateViewProjection(); + } + + /** + * @return true if parallel projection is enable, false if in normal perspective mode + * @see #setParallelProjection(boolean) + */ + public boolean isParallelProjection() { + return this.parallelProjection; + } + + /** + * Enable/disable parallel projection. + * + * @param value true to set up this camera for parallel projection is enable, false to enter normal perspective mode + */ + public void setParallelProjection(final boolean value) { + this.parallelProjection = value; + onFrustumChange(); + } + + /** + * Computes the z value in projection space from the z value in view space + * Note that the returned value is going non linearly from 0 to 1. + * for more explanations on non linear z buffer see + * http://www.sjbaker.org/steve/omniv/love_your_z_buffer.html + * @param viewZPos the z value in view space. + * @return the z value in projection space. + */ + public float getViewToProjectionZ(float viewZPos) { + float far = getFrustumFar(); + float near = getFrustumNear(); + float a = far / (far - near); + float b = far * near / (near - far); + return a + b / viewZPos; + } + + /** + * Computes a position in World space given a screen position in screen space (0,0 to width, height) + * and a z position in projection space ( 0 to 1 non linear). + * This former value is also known as the Z buffer value or non linear depth buffer. + * for more explanations on non linear z buffer see + * http://www.sjbaker.org/steve/omniv/love_your_z_buffer.html + * + * To compute the projection space z from the view space z (distance from cam to object) @see Camera#getViewToProjectionZ + * + * @param screenPos 2d coordinate in screen space + * @param projectionZPos non linear z value in projection space + * @return the position in world space. + */ + public Vector3f getWorldCoordinates(Vector2f screenPos, float projectionZPos) { + return getWorldCoordinates(screenPos, projectionZPos, null); + } + + /** + * @see Camera#getWorldCoordinates + */ + public Vector3f getWorldCoordinates(Vector2f screenPosition, + float projectionZPos, 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, + projectionZPos * 2 - 1); + + float w = inverseMat.multProj(store, store); + store.multLocal(1f / w); + + return store; + } + + /** + * Converts the given position from world space to screen space. + * + * @see Camera#getScreenCoordinates + */ + public Vector3f getScreenCoordinates(Vector3f worldPos) { + return getScreenCoordinates(worldPos, null); + } + + /** + * Converts the given position from world space to screen space. + * + * @see Camera#getScreenCoordinates(Vector3f, Vector3f) + */ + public Vector3f getScreenCoordinates(Vector3f worldPosition, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + +// TempVars vars = vars.lock(); +// Quaternion tmp_quat = vars.quat1; +// tmp_quat.set( worldPosition.x, worldPosition.y, worldPosition.z, 1 ); +// viewProjectionMatrix.mult(tmp_quat, tmp_quat); +// tmp_quat.multLocal( 1.0f / tmp_quat.getW() ); +// store.x = ( ( tmp_quat.getX() + 1 ) * ( viewPortRight - viewPortLeft ) / 2 + viewPortLeft ) * getWidth(); +// store.y = ( ( tmp_quat.getY() + 1 ) * ( viewPortTop - viewPortBottom ) / 2 + viewPortBottom ) * getHeight(); +// store.z = ( tmp_quat.getZ() + 1 ) / 2; +// vars.release(); + + float w = viewProjectionMatrix.multProj(worldPosition, store); + store.divideLocal(w); + + store.x = ((store.x + 1f) * (viewPortRight - viewPortLeft) / 2f + viewPortLeft) * getWidth(); + store.y = ((store.y + 1f) * (viewPortTop - viewPortBottom) / 2f + viewPortBottom) * getHeight(); + store.z = (store.z + 1f) / 2f; + + return store; + } + + /** + * @return the width/resolution of the display. + */ + public int getWidth() { + return width; + } + + /** + * @return the height/resolution of the display. + */ + public int getHeight() { + return height; + } + + @Override + public String toString() { + return "Camera[location=" + location + "\n, direction=" + getDirection() + "\n" + + "res=" + width + "x" + height + ", parallel=" + parallelProjection + "\n" + + "near=" + frustumNear + ", far=" + frustumFar + "]"; + } + + public void write(JmeExporter e) throws IOException { + OutputCapsule capsule = e.getCapsule(this); + capsule.write(location, "location", Vector3f.ZERO); + capsule.write(rotation, "rotation", Quaternion.DIRECTION_Z); + capsule.write(frustumNear, "frustumNear", 1); + capsule.write(frustumFar, "frustumFar", 2); + capsule.write(frustumLeft, "frustumLeft", -0.5f); + capsule.write(frustumRight, "frustumRight", 0.5f); + capsule.write(frustumTop, "frustumTop", 0.5f); + capsule.write(frustumBottom, "frustumBottom", -0.5f); + capsule.write(coeffLeft, "coeffLeft", new float[2]); + capsule.write(coeffRight, "coeffRight", new float[2]); + capsule.write(coeffBottom, "coeffBottom", new float[2]); + capsule.write(coeffTop, "coeffTop", new float[2]); + capsule.write(viewPortLeft, "viewPortLeft", 0); + capsule.write(viewPortRight, "viewPortRight", 1); + capsule.write(viewPortTop, "viewPortTop", 1); + capsule.write(viewPortBottom, "viewPortBottom", 0); + capsule.write(width, "width", 0); + capsule.write(height, "height", 0); + capsule.write(name, "name", null); + } + + public void read(JmeImporter e) throws IOException { + InputCapsule capsule = e.getCapsule(this); + location = (Vector3f) capsule.readSavable("location", Vector3f.ZERO.clone()); + rotation = (Quaternion) capsule.readSavable("rotation", Quaternion.DIRECTION_Z.clone()); + frustumNear = capsule.readFloat("frustumNear", 1); + frustumFar = capsule.readFloat("frustumFar", 2); + frustumLeft = capsule.readFloat("frustumLeft", -0.5f); + frustumRight = capsule.readFloat("frustumRight", 0.5f); + frustumTop = capsule.readFloat("frustumTop", 0.5f); + frustumBottom = capsule.readFloat("frustumBottom", -0.5f); + coeffLeft = capsule.readFloatArray("coeffLeft", new float[2]); + coeffRight = capsule.readFloatArray("coeffRight", new float[2]); + coeffBottom = capsule.readFloatArray("coeffBottom", new float[2]); + coeffTop = capsule.readFloatArray("coeffTop", new float[2]); + viewPortLeft = capsule.readFloat("viewPortLeft", 0); + viewPortRight = capsule.readFloat("viewPortRight", 1); + viewPortTop = capsule.readFloat("viewPortTop", 1); + viewPortBottom = capsule.readFloat("viewPortBottom", 0); + width = capsule.readInt("width", 1); + height = capsule.readInt("height", 1); + name = capsule.readString("name", null); + onFrustumChange(); + onViewPortChange(); + onFrameChange(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/Caps.java b/jme3-core/src/main/java/com/jme3/renderer/Caps.java new file mode 100644 index 000000000..55a94c6ee --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +import com.jme3.shader.Shader; +import com.jme3.shader.Shader.ShaderSource; +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; + +/** + * Caps is an enum specifying a capability that the {@link Renderer} + * supports. + * + * @author Kirill Vainer + */ +public enum Caps { + + /** + * Supports {@link FrameBuffer FrameBuffers}. + *

    + * OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.
    + * OpenGL ES: Renderer supports OpenGL ES 2.0. + */ + FrameBuffer, + + /** + * Supports framebuffer Multiple Render Targets (MRT) + *

    + * OpenGL: Renderer exposes the GL_ARB_draw_buffers extension + */ + FrameBufferMRT, + + /** + * Supports framebuffer multi-sampling + *

    + * OpenGL: Renderer exposes the GL EXT framebuffer multisample extension
    + * OpenGL ES: Renderer exposes GL_APPLE_framebuffer_multisample or + * GL_ANGLE_framebuffer_multisample. + */ + FrameBufferMultisample, + + /** + * Supports texture multi-sampling + *

    + * OpenGL: Renderer exposes the GL_ARB_texture_multisample extension
    + * OpenGL ES: Renderer exposes the GL_IMG_multisampled_render_to_texture + * extension. + */ + TextureMultisample, + + /** + * Supports OpenGL 2.0 or OpenGL ES 2.0. + */ + OpenGL20, + + /** + * Supports OpenGL 2.1 + */ + OpenGL21, + + /** + * Supports OpenGL 3.0 + */ + OpenGL30, + + /** + * Supports OpenGL 3.1 + */ + OpenGL31, + + /** + * Supports OpenGL 3.2 + */ + OpenGL32, + + /** + * Supports OpenGL ARB program. + *

    + * OpenGL: Renderer exposes ARB_vertex_program and ARB_fragment_program + * extensions. + */ + ARBprogram, + + /** + * Supports GLSL 1.0 + */ + GLSL100, + + /** + * Supports GLSL 1.1 + */ + GLSL110, + + /** + * Supports GLSL 1.2 + */ + GLSL120, + + /** + * Supports GLSL 1.3 + */ + GLSL130, + + /** + * Supports GLSL 1.4 + */ + GLSL140, + + /** + * Supports GLSL 1.5 + */ + GLSL150, + + /** + * Supports GLSL 3.3 + */ + GLSL330, + + /** + * Supports reading from textures inside the vertex shader. + */ + VertexTextureFetch, + + /** + * Supports geometry shader. + */ + GeometryShader, + + /** + * Supports texture arrays + */ + TextureArray, + + /** + * Supports texture buffers + */ + TextureBuffer, + + /** + * Supports floating point textures (Format.RGB16F) + */ + FloatTexture, + + /** + * Supports floating point FBO color buffers (Format.RGB16F) + */ + FloatColorBuffer, + + /** + * Supports floating point depth buffer + */ + FloatDepthBuffer, + + /** + * Supports Format.RGB111110F for textures + */ + PackedFloatTexture, + + /** + * Supports Format.RGB9E5 for textures + */ + SharedExponentTexture, + + /** + * Supports Format.RGB111110F for FBO color buffers + */ + PackedFloatColorBuffer, + + /** + * Supports Format.RGB9E5 for FBO color buffers + */ + SharedExponentColorBuffer, + + /** + * Supports Format.LATC for textures, this includes + * support for ATI's 3Dc texture compression. + */ + TextureCompressionLATC, + + /** + * Supports Non-Power-Of-Two (NPOT) textures and framebuffers + */ + NonPowerOfTwoTextures, + + /// Vertex Buffer features + MeshInstancing, + + /** + * Supports VAO, or vertex buffer arrays + */ + VertexBufferArray, + + /** + * Supports multisampling on the screen + */ + Multisample, + + /** + * Supports FBO with Depth24Stencil8 image format + */ + PackedDepthStencilBuffer; + + /** + * Returns true if given the renderer capabilities, the texture + * can be supported by the renderer. + *

    + * This only checks the format of the texture, non-power-of-2 + * textures are scaled automatically inside the renderer + * if are not supported natively. + * + * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }. + * @param tex The texture to check + * @return True if it is supported, false otherwise. + */ + public static boolean supports(Collection caps, 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 Depth24Stencil8: + return caps.contains(Caps.PackedDepthStencilBuffer); + 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; + } + } + + private static boolean supportsColorBuffer(Collection caps, RenderBuffer colorBuf){ + 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; + } + } + + /** + * Returns true if given the renderer capabilities, the framebuffer + * can be supported by the renderer. + * + * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }. + * @param fb The framebuffer to check + * @return True if it is supported, false otherwise. + */ + public static boolean supports(Collection caps, FrameBuffer fb){ + if (!caps.contains(Caps.FrameBuffer)) + return false; + + if (fb.getSamples() > 1 + && !caps.contains(Caps.FrameBufferMultisample)) + return false; + + 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 (depthFmt == Format.Depth24Stencil8 + && !caps.contains(Caps.PackedDepthStencilBuffer)) + return false; + } + } + for (int i = 0; i < fb.getNumColorBuffers(); i++){ + if (!supportsColorBuffer(caps, fb.getColorBuffer(i))){ + return false; + } + } + return true; + } + + /** + * Returns true if given the renderer capabilities, the shader + * can be supported by the renderer. + * + * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }. + * @param shader The shader to check + * @return True if it is supported, false otherwise. + */ + public static boolean supports(Collection caps, Shader shader){ + for (ShaderSource source : shader.getSources()) { + if (source.getLanguage().startsWith("GLSL")) { + int ver = Integer.parseInt(source.getLanguage().substring(4)); + switch (ver) { + case 100: + if (!caps.contains(Caps.GLSL100)) return false; + case 110: + if (!caps.contains(Caps.GLSL110)) return false; + case 120: + if (!caps.contains(Caps.GLSL120)) return false; + case 130: + if (!caps.contains(Caps.GLSL130)) return false; + case 140: + if (!caps.contains(Caps.GLSL140)) return false; + case 150: + if (!caps.contains(Caps.GLSL150)) return false; + case 330: + if (!caps.contains(Caps.GLSL330)) return false; + default: + return false; + } + } + } + return true; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/GL1Renderer.java b/jme3-core/src/main/java/com/jme3/renderer/GL1Renderer.java new file mode 100644 index 000000000..9e626e791 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/GL1Renderer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +import com.jme3.material.FixedFuncBinding; + +/** + * Renderer sub-interface that is used for non-shader based renderers. + *

    + * The GL1Renderer provides a single call, + * {@link #setFixedFuncBinding(com.jme3.material.FixedFuncBinding, java.lang.Object) } + * which allows to set fixed functionality state. + * + * @author Kirill Vainer + */ +public interface GL1Renderer extends Renderer { + + /** + * Set the fixed functionality state. + *

    + * See {@link FixedFuncBinding} for various values that + * can be set. + * + * @param ffBinding The binding to set + * @param val The value + */ + public void setFixedFuncBinding(FixedFuncBinding ffBinding, Object val); +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/IDList.java b/jme3-core/src/main/java/com/jme3/renderer/IDList.java new file mode 100644 index 000000000..70fea4218 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/IDList.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +import java.util.Arrays; + +/** + * A specialized data-structure used to optimize state changes of "slot" + * based state. + */ +public class IDList { + + public int[] newList = new int[16]; + public int[] oldList = new int[16]; + public int newLen = 0; + public int oldLen = 0; + + /** + * Reset all states to zero + */ + public void reset(){ + newLen = 0; + oldLen = 0; + Arrays.fill(newList, 0); + Arrays.fill(oldList, 0); + } + + /** + * Adds an index to the new list. + * If the index was not in the old list, false is returned, + * if the index was in the old list, it is removed from the old + * list and true is returned. + * + * @param idx The index to move + * @return True if it existed in old list and was removed + * from there, false otherwise. + */ + public boolean moveToNew(int idx){ + if (newLen == 0 || newList[newLen-1] != idx) + // add item to newList first + newList[newLen++] = idx; + + // find idx in oldList, if removed successfuly, return true. + for (int i = 0; i < oldLen; i++){ + if (oldList[i] == idx){ + // found index in slot i + // delete index from old list + oldLen --; + for (int j = i; j < oldLen; j++){ + oldList[j] = oldList[j+1]; + } + return true; + } + } + return false; + } + + /** + * Copies the new list to the old list, and clears the new list. + */ + public void copyNewToOld(){ + System.arraycopy(newList, 0, oldList, 0, newLen); + oldLen = newLen; + newLen = 0; + } + + /** + * Prints the contents of the lists + */ + public void print(){ + if (newLen > 0){ + System.out.print("New List: "); + for (int i = 0; i < newLen; i++){ + if (i == newLen -1) + System.out.println(newList[i]); + else + System.out.print(newList[i]+", "); + } + } + if (oldLen > 0){ + System.out.print("Old List: "); + for (int i = 0; i < oldLen; i++){ + if (i == oldLen -1) + System.out.println(oldList[i]); + else + System.out.print(oldList[i]+", "); + } + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java new file mode 100644 index 000000000..ea89ab58c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; + +/** + * Represents the current state of the graphics library. This class is used + * internally to reduce state changes. NOTE: This class is specific to OpenGL. + */ +public class RenderContext { + + /** + * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode) + */ + public RenderState.FaceCullMode cullMode = RenderState.FaceCullMode.Off; + + /** + * @see RenderState#setDepthTest(boolean) + */ + public boolean depthTestEnabled = false; + + /** + * @see RenderState#setAlphaFallOff(float) + */ + public float alphaTestFallOff = 0f; + + /** + * @see RenderState#setAlphaTest(boolean) + */ + public boolean alphaTestEnabled = false; + + /** + * @see RenderState#setDepthWrite(boolean) + */ + public boolean depthWriteEnabled = true; + + /** + * @see RenderState#setColorWrite(boolean) + */ + public boolean colorWriteEnabled = true; + + /** + * @see Renderer#setClipRect(int, int, int, int) + */ + public boolean clipRectEnabled = false; + + /** + * @see RenderState#setPolyOffset(float, float) + */ + public boolean polyOffsetEnabled = false; + + /** + * @see RenderState#setPolyOffset(float, float) + */ + public float polyOffsetFactor = 0; + + /** + * @see RenderState#setPolyOffset(float, float) + */ + public float polyOffsetUnits = 0; + + /** + * For normals only. Uses GL_NORMALIZE. + * + * @see VertexBuffer#setNormalized(boolean) + */ + public boolean normalizeEnabled = false; + + /** + * For glMatrixMode. + * + * @see Renderer#setWorldMatrix(com.jme3.math.Matrix4f) + * @see Renderer#setViewProjectionMatrices(com.jme3.math.Matrix4f, com.jme3.math.Matrix4f) + */ + public int matrixMode = -1; + + /** + * @see Mesh#setPointSize(float) + */ + public float pointSize = 1; + + /** + * @see Mesh#setLineWidth(float) + */ + public float lineWidth = 1; + + /** + * @see RenderState#setBlendMode(com.jme3.material.RenderState.BlendMode) + */ + public RenderState.BlendMode blendMode = RenderState.BlendMode.Off; + + /** + * @see RenderState#setWireframe(boolean) + */ + public boolean wireframe = false; + + /** + * @see RenderState#setPointSprite(boolean) + */ + public boolean pointSprite = false; + + /** + * @see Renderer#setShader(com.jme3.shader.Shader) + */ + public int boundShaderProgram; + + /** + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + */ + public int boundFBO = 0; + + /** + * Currently bound Renderbuffer + * + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + */ + public int boundRB = 0; + + /** + * Currently bound draw buffer + * -2 = GL_NONE + * -1 = GL_BACK + * 0 = GL_COLOR_ATTACHMENT0 + * n = GL_COLOR_ATTACHMENTn + * where n is an integer greater than 1 + * + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + * @see FrameBuffer#setTargetIndex(int) + */ + public int boundDrawBuf = -1; + + /** + * Currently bound read buffer + * + * @see RenderContext#boundDrawBuf + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + * @see FrameBuffer#setTargetIndex(int) + */ + public int boundReadBuf = -1; + + /** + * Currently bound element array vertex buffer. + * + * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int) + */ + public int boundElementArrayVBO; + + /** + * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int) + */ + public int boundVertexArray; + + /** + * Currently bound array vertex buffer. + * + * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int) + */ + public int boundArrayVBO; + + public int numTexturesSet = 0; + + /** + * Current bound texture IDs for each texture unit. + * + * @see Renderer#setTexture(int, com.jme3.texture.Texture) + */ + public Image[] boundTextures = new Image[16]; + + /** + * IDList for texture units + * + * @see Renderer#setTexture(int, com.jme3.texture.Texture) + */ + public IDList textureIndexList = new IDList(); + + /** + * Currently bound texture unit + * + * @see Renderer#setTexture(int, com.jme3.texture.Texture) + */ + public int boundTextureUnit = 0; + + /** + * Stencil Buffer state + */ + public boolean stencilTest = false; + public RenderState.StencilOperation frontStencilStencilFailOperation = RenderState.StencilOperation.Keep; + public RenderState.StencilOperation frontStencilDepthFailOperation = RenderState.StencilOperation.Keep; + public RenderState.StencilOperation frontStencilDepthPassOperation = RenderState.StencilOperation.Keep; + public RenderState.StencilOperation backStencilStencilFailOperation = RenderState.StencilOperation.Keep; + public RenderState.StencilOperation backStencilDepthFailOperation = RenderState.StencilOperation.Keep; + public RenderState.StencilOperation backStencilDepthPassOperation = RenderState.StencilOperation.Keep; + public RenderState.TestFunction frontStencilFunction = RenderState.TestFunction.Always; + public RenderState.TestFunction backStencilFunction = RenderState.TestFunction.Always; + + /** + * Vertex attribs currently bound and enabled. If a slot is null, then + * it is disabled. + */ + public VertexBuffer[] boundAttribs = new VertexBuffer[16]; + + /** + * IDList for vertex attributes + */ + public IDList attribIndexList = new IDList(); + + /** + * Ambient color (GL1 only) + */ + public ColorRGBA ambient; + + /** + * Diffuse color (GL1 only) + */ + public ColorRGBA diffuse; + + /** + * Specular color (GL1 only) + */ + public ColorRGBA specular; + + /** + * Material color (GL1 only) + */ + public ColorRGBA color; + + /** + * Shininess (GL1 only) + */ + public float shininess; + + /** + * Use vertex color (GL1 only) + */ + public boolean useVertexColor; + + /** + * depth tets function + */ + public RenderState.TestFunction depthFunc = RenderState.TestFunction.LessOrEqual; + + /** + * alpha tets function + */ + public RenderState.TestFunction alphaFunc = RenderState.TestFunction.Greater; + + /** + * Reset the RenderContext to default GL state + */ + public void reset(){ + cullMode = RenderState.FaceCullMode.Off; + depthTestEnabled = false; + alphaTestFallOff = 0f; + depthWriteEnabled = false; + colorWriteEnabled = false; + clipRectEnabled = false; + polyOffsetEnabled = false; + polyOffsetFactor = 0; + polyOffsetUnits = 0; + normalizeEnabled = false; + matrixMode = -1; + pointSize = 1; + blendMode = RenderState.BlendMode.Off; + wireframe = false; + boundShaderProgram = 0; + boundFBO = 0; + boundRB = 0; + boundDrawBuf = -1; + boundReadBuf = -1; + boundElementArrayVBO = 0; + boundVertexArray = 0; + boundArrayVBO = 0; + numTexturesSet = 0; + for (int i = 0; i < boundTextures.length; i++) + boundTextures[i] = null; + + textureIndexList.reset(); + boundTextureUnit = 0; + for (int i = 0; i < boundAttribs.length; i++) + boundAttribs[i] = null; + + attribIndexList.reset(); + + stencilTest = false; + frontStencilStencilFailOperation = RenderState.StencilOperation.Keep; + frontStencilDepthFailOperation = RenderState.StencilOperation.Keep; + frontStencilDepthPassOperation = RenderState.StencilOperation.Keep; + backStencilStencilFailOperation = RenderState.StencilOperation.Keep; + backStencilDepthFailOperation = RenderState.StencilOperation.Keep; + backStencilDepthPassOperation = RenderState.StencilOperation.Keep; + frontStencilFunction = RenderState.TestFunction.Always; + backStencilFunction = RenderState.TestFunction.Always; + + ambient = diffuse = specular = color = null; + shininess = 0; + useVertexColor = false; + depthFunc = RenderState.TestFunction.LessOrEqual; + alphaFunc = RenderState.TestFunction.Greater; + } +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java new file mode 100644 index 000000000..5ff367798 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -0,0 +1,1039 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +import com.jme3.material.Material; +import com.jme3.material.MaterialDef; +import com.jme3.material.RenderState; +import com.jme3.material.Technique; +import com.jme3.math.*; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.queue.GeometryList; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.*; +import com.jme3.shader.Uniform; +import com.jme3.shader.UniformBinding; +import com.jme3.shader.UniformBindingManager; +import com.jme3.system.NullRenderer; +import com.jme3.system.Timer; +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 UniformBindingManager uniformBindingManager = new UniformBindingManager(); + 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 boolean shader; + private int viewX, viewY, viewWidth, viewHeight; + private Matrix4f orthoMatrix = new Matrix4f(); + private String tmpTech; + private boolean handleTranlucentBucket = true; + + /** + * Create a high-level rendering interface over the + * low-level rendering interface. + * @param renderer + */ + public RenderManager(Renderer renderer) { + this.renderer = renderer; + } + + /** + * Returns the pre ViewPort with the given name. + * + * @param viewName The name of the pre ViewPort to look up + * @return The ViewPort, or null if not found. + * + * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) + */ + public ViewPort getPreView(String viewName) { + for (int i = 0; i < preViewPorts.size(); i++) { + if (preViewPorts.get(i).getName().equals(viewName)) { + return preViewPorts.get(i); + } + } + return null; + } + + /** + * Removes the specified pre ViewPort. + * + * @param view The pre ViewPort to remove + * @return True if the ViewPort was removed successfully. + * + * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) + */ + public boolean removePreView(ViewPort view) { + return preViewPorts.remove(view); + } + + /** + * Returns the main ViewPort with the given name. + * + * @param viewName The name of the main ViewPort to look up + * @return The ViewPort, or null if not found. + * + * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) + */ + public ViewPort getMainView(String viewName) { + for (int i = 0; i < viewPorts.size(); i++) { + if (viewPorts.get(i).getName().equals(viewName)) { + return viewPorts.get(i); + } + } + return null; + } + + /** + * Removes the main ViewPort with the specified name. + * + * @param viewName The main ViewPort name to remove + * @return True if the ViewPort was removed successfully. + * + * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) + */ + public boolean removeMainView(String viewName) { + for (int i = 0; i < viewPorts.size(); i++) { + if (viewPorts.get(i).getName().equals(viewName)) { + viewPorts.remove(i); + return true; + } + } + return false; + } + + /** + * Removes the specified main ViewPort. + * + * @param view The main ViewPort to remove + * @return True if the ViewPort was removed successfully. + * + * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) + */ + public boolean removeMainView(ViewPort view) { + return viewPorts.remove(view); + } + + /** + * Returns the post ViewPort with the given name. + * + * @param viewName The name of the post ViewPort to look up + * @return The ViewPort, or null if not found. + * + * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) + */ + public ViewPort getPostView(String viewName) { + for (int i = 0; i < postViewPorts.size(); i++) { + if (postViewPorts.get(i).getName().equals(viewName)) { + return postViewPorts.get(i); + } + } + return null; + } + + /** + * Removes the post ViewPort with the specified name. + * + * @param viewName The post ViewPort name to remove + * @return True if the ViewPort was removed successfully. + * + * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) + */ + public boolean removePostView(String viewName) { + for (int i = 0; i < postViewPorts.size(); i++) { + if (postViewPorts.get(i).getName().equals(viewName)) { + postViewPorts.remove(i); + + return true; + } + } + return false; + } + + /** + * Removes the specified post ViewPort. + * + * @param view The post ViewPort to remove + * @return True if the ViewPort was removed successfully. + * + * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) + */ + public boolean removePostView(ViewPort view) { + return postViewPorts.remove(view); + } + + /** + * Returns a read-only list of all pre ViewPorts + * @return a read-only list of all pre ViewPorts + * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) + */ + public List getPreViews() { + return Collections.unmodifiableList(preViewPorts); + } + + /** + * Returns a read-only list of all main ViewPorts + * @return a read-only list of all main ViewPorts + * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) + */ + public List getMainViews() { + return Collections.unmodifiableList(viewPorts); + } + + /** + * Returns a read-only list of all post ViewPorts + * @return a read-only list of all post ViewPorts + * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) + */ + public List getPostViews() { + return Collections.unmodifiableList(postViewPorts); + } + + /** + * Creates a new pre ViewPort, to display the given camera's content. + *

    + * The view will be processed before the main and post viewports. + */ + public ViewPort createPreView(String viewName, Camera cam) { + ViewPort vp = new ViewPort(viewName, cam); + preViewPorts.add(vp); + return vp; + } + + /** + * Creates a new main ViewPort, to display the given camera's content. + *

    + * The view will be processed before the post viewports but after + * the pre viewports. + */ + public ViewPort createMainView(String viewName, Camera cam) { + ViewPort vp = new ViewPort(viewName, cam); + viewPorts.add(vp); + return vp; + } + + /** + * Creates a new post ViewPort, to display the given camera's content. + *

    + * The view will be processed after the pre and main viewports. + */ + public ViewPort createPostView(String viewName, Camera cam) { + ViewPort vp = new ViewPort(viewName, cam); + postViewPorts.add(vp); + return vp; + } + + private void notifyReshape(ViewPort vp, int w, int h) { + List processors = vp.getProcessors(); + for (SceneProcessor proc : processors) { + if (!proc.isInitialized()) { + proc.initialize(this, vp); + } else { + proc.reshape(vp, w, h); + } + } + } + + /** + * Internal use only. + * Updates the resolution of all on-screen cameras to match + * the given width and height. + */ + public void notifyReshape(int w, int h) { + for (ViewPort vp : preViewPorts) { + if (vp.getOutputFrameBuffer() == null) { + Camera cam = vp.getCamera(); + cam.resize(w, h, true); + } + notifyReshape(vp, w, h); + } + for (ViewPort vp : viewPorts) { + if (vp.getOutputFrameBuffer() == null) { + Camera cam = vp.getCamera(); + cam.resize(w, h, true); + } + notifyReshape(vp, w, h); + } + for (ViewPort vp : postViewPorts) { + if (vp.getOutputFrameBuffer() == null) { + Camera cam = vp.getCamera(); + cam.resize(w, h, true); + } + notifyReshape(vp, w, h); + } + } + + /** + * Set the material to use to render all future objects. + * This overrides the material set on the geometry and renders + * with the provided material instead. + * Use null to clear the material and return renderer to normal + * functionality. + * @param mat The forced material to set, or null to return to normal + */ + public void setForcedMaterial(Material mat) { + forcedMaterial = mat; + } + + /** + * Returns the forced render state previously set with + * {@link #setForcedRenderState(com.jme3.material.RenderState) }. + * @return the forced render state + */ + public RenderState getForcedRenderState() { + return forcedRenderState; + } + + /** + * Set the render state to use for all future objects. + * This overrides the render state set on the material and instead + * forces this render state to be applied for all future materials + * rendered. Set to null to return to normal functionality. + * + * @param forcedRenderState The forced render state to set, or null + * to return to normal + */ + public void setForcedRenderState(RenderState forcedRenderState) { + this.forcedRenderState = forcedRenderState; + } + + /** + * Set the timer that should be used to query the time based + * {@link UniformBinding}s for material world parameters. + * + * @param timer The timer to query time world parameters + */ + public void setTimer(Timer timer) { + uniformBindingManager.setTimer(timer); + } + + /** + * Returns the forced technique name set. + * + * @return the forced technique name set. + * + * @see #setForcedTechnique(java.lang.String) + */ + public String getForcedTechnique() { + return forcedTechnique; + } + + /** + * Sets the forced technique to use when rendering geometries. + *

    + * If the specified technique name is available on the geometry's + * material, then it is used, otherwise, the + * {@link #setForcedMaterial(com.jme3.material.Material) forced material} is used. + * If a forced material is not set and the forced technique name cannot + * be found on the material, the geometry will not be rendered. + * + * @param forcedTechnique The forced technique name to use, set to null + * to return to normal functionality. + * + * @see #renderGeometry(com.jme3.scene.Geometry) + */ + public void setForcedTechnique(String forcedTechnique) { + this.forcedTechnique = forcedTechnique; + } + + /** + * Enable or disable alpha-to-coverage. + *

    + * When alpha to coverage is enabled and the renderer implementation + * supports it, then alpha blending will be replaced with alpha dissolve + * if multi-sampling is also set on the renderer. + * This feature allows avoiding of alpha blending artifacts due to + * lack of triangle-level back-to-front sorting. + * + * @param value True to enable alpha-to-coverage, false otherwise. + */ + public void setAlphaToCoverage(boolean value) { + renderer.setAlphaToCoverage(value); + } + + /** + * True if the translucent bucket should automatically be rendered + * by the RenderManager. + * + * @return Whether or not the translucent bucket is rendered. + * + * @see #setHandleTranslucentBucket(boolean) + */ + public boolean isHandleTranslucentBucket() { + return handleTranlucentBucket; + } + + /** + * Enable or disable rendering of the + * {@link Bucket#Translucent translucent bucket} + * by the RenderManager. The default is enabled. + * + * @param handleTranslucentBucket Whether or not the translucent bucket should + * be rendered. + */ + public void setHandleTranslucentBucket(boolean handleTranslucentBucket) { + this.handleTranlucentBucket = handleTranslucentBucket; + } + + /** + * Internal use only. Sets the world matrix to use for future + * rendering. This has no effect unless objects are rendered manually + * using {@link Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) }. + * Using {@link #renderGeometry(com.jme3.scene.Geometry) } will + * override this value. + * + * @param mat The world matrix to set + */ + public void setWorldMatrix(Matrix4f mat) { + if (shader) { + uniformBindingManager.setWorldMatrix(mat); + } else { + renderer.setWorldMatrix(mat); + } + } + + /** + * Internal use only. + * Updates the given list of uniforms with {@link UniformBinding uniform bindings} + * based on the current world state. + */ + public void updateUniformBindings(List params) { + uniformBindingManager.updateUniformBindings(params); + } + + /** + * Renders the given geometry. + *

    + * First the proper world matrix is set, if + * the geometry's {@link Geometry#setIgnoreTransform(boolean) ignore transform} + * feature is enabled, the identity world matrix is used, otherwise, the + * geometry's {@link Geometry#getWorldMatrix() world transform matrix} is used. + *

    + * Once the world matrix is applied, the proper material is chosen for rendering. + * If a {@link #setForcedMaterial(com.jme3.material.Material) forced material} is + * set on this RenderManager, then it is used for rendering the geometry, + * otherwise, the {@link Geometry#getMaterial() geometry's material} is used. + *

    + * If a {@link #setForcedTechnique(java.lang.String) forced technique} is + * set on this RenderManager, then it is selected automatically + * on the geometry's material and is used for rendering. Otherwise, one + * of the {@link MaterialDef#getDefaultTechniques() default techniques} is + * used. + *

    + * If a {@link #setForcedRenderState(com.jme3.material.RenderState) forced + * render state} is set on this RenderManager, then it is used + * for rendering the material, and the material's own render state is ignored. + * Otherwise, the material's render state is used as intended. + * + * @param g The geometry to render + * + * @see Technique + * @see RenderState + * @see Material#selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) + * @see Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) + */ + public void renderGeometry(Geometry g) { + if (g.isIgnoreTransform()) { + setWorldMatrix(Matrix4f.IDENTITY); + } else { + setWorldMatrix(g.getWorldMatrix()); + } + + //if forcedTechnique we try to force it for render, + //if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null + //else the geom is not rendered + if (forcedTechnique != null) { + if (g.getMaterial().getMaterialDef().getTechniqueDef(forcedTechnique) != null) { + tmpTech = g.getMaterial().getActiveTechnique() != null ? g.getMaterial().getActiveTechnique().getDef().getName() : "Default"; + g.getMaterial().selectTechnique(forcedTechnique, this); + //saving forcedRenderState for future calls + RenderState tmpRs = forcedRenderState; + if (g.getMaterial().getActiveTechnique().getDef().getForcedRenderState() != null) { + //forcing forced technique renderState + forcedRenderState = g.getMaterial().getActiveTechnique().getDef().getForcedRenderState(); + } + // use geometry's material + g.getMaterial().render(g, this); + g.getMaterial().selectTechnique(tmpTech, this); + + //restoring forcedRenderState + forcedRenderState = tmpRs; + + //Reverted this part from revision 6197 + //If forcedTechnique does not exists, and frocedMaterial is not set, the geom MUST NOT be rendered + } else if (forcedMaterial != null) { + // use forced material + forcedMaterial.render(g, this); + } + } else if (forcedMaterial != null) { + // use forced material + forcedMaterial.render(g, this); + } else { + g.getMaterial().render(g, this); + } + } + + /** + * Renders the given GeometryList. + *

    + * For every geometry in the list, the + * {@link #renderGeometry(com.jme3.scene.Geometry) } method is called. + * + * @param gl The geometry list to render. + * + * @see GeometryList + * @see #renderGeometry(com.jme3.scene.Geometry) + */ + public void renderGeometryList(GeometryList gl) { + for (int i = 0; i < gl.size(); i++) { + renderGeometry(gl.get(i)); + } + } + + /** + * If a spatial is not inside the eye frustum, it + * is still rendered in the shadow frustum (shadow casting queue) + * through this recursive method. + */ + private void renderShadow(Spatial s, RenderQueue rq) { + if (s instanceof Node) { + Node n = (Node) s; + List children = n.getChildren(); + for (int i = 0; i < children.size(); i++) { + renderShadow(children.get(i), rq); + } + } else if (s instanceof Geometry) { + Geometry gm = (Geometry) s; + + RenderQueue.ShadowMode shadowMode = s.getShadowMode(); + if (shadowMode != RenderQueue.ShadowMode.Off && shadowMode != RenderQueue.ShadowMode.Receive) { + //forcing adding to shadow cast mode, culled objects doesn't have to be in the receiver queue + rq.addToShadowQueue(gm, RenderQueue.ShadowMode.Cast); + } + } + } + + /** + * Preloads a scene for rendering. + *

    + * After invocation of this method, the underlying + * renderer would have uploaded any textures, shaders and meshes + * used by the given scene to the video driver. + * Using this method is useful when wishing to avoid the initial pause + * when rendering a scene for the first time. Note that it is not + * guaranteed that the underlying renderer will actually choose to upload + * the data to the GPU so some pause is still to be expected. + * + * @param scene The scene to preload + */ + public void preloadScene(Spatial scene) { + if (scene instanceof Node) { + // recurse for all children + Node n = (Node) scene; + List 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 (VertexBuffer vb : mesh.getBufferList().getArray()) { + if (vb.getData() != null && vb.getUsage() != VertexBuffer.Usage.CpuOnly) { + renderer.updateBufferData(vb); + } + } + } + } + } + + /** + * Flattens the given scene graph into the ViewPort's RenderQueue, + * checking for culling as the call goes down the graph recursively. + *

    + * First, the scene is checked for culling based on the Spatials + * {@link Spatial#setCullHint(com.jme3.scene.Spatial.CullHint) cull hint}, + * if the camera frustum contains the scene, then this method is recursively + * called on its children. + *

    + * When the scene's leaves or {@link Geometry geometries} are reached, + * they are each enqueued into the + * {@link ViewPort#getQueue() ViewPort's render queue}. + *

    + * In addition to enqueuing the visible geometries, this method + * also scenes which cast or receive shadows, by putting them into the + * RenderQueue's + * {@link RenderQueue#addToShadowQueue(com.jme3.scene.Geometry, com.jme3.renderer.queue.RenderQueue.ShadowMode) + * shadow queue}. Each Spatial which has its + * {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode} + * set to not off, will be put into the appropriate shadow queue, note that + * this process does not check for frustum culling on any + * {@link ShadowMode#Cast shadow casters}, as they don't have to be + * in the eye camera frustum to cast shadows on objects that are inside it. + * + * @param scene The scene to flatten into the queue + * @param vp The ViewPort provides the {@link ViewPort#getCamera() camera} + * used for culling and the {@link ViewPort#getQueue() queue} used to + * contain the flattened scene graph. + */ + public void renderScene(Spatial scene, ViewPort vp) { + //reset of the camera plane state for proper culling (must be 0 for the first note of the scene to be rendered) + vp.getCamera().setPlaneState(0); + //rendering the scene + renderSubScene(scene, vp); + } + + // recursively renders the scene + private void renderSubScene(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) && scene.getCullHint() != Spatial.CullHint.Always) { + renderShadow(scene, vp.getQueue()); + } + return; + } + + scene.runControlRender(this, vp); + if (scene instanceof Node) { + // Recurse for all children + Node n = (Node) scene; + List children = n.getChildren(); + // Saving cam state for culling + int camState = vp.getCamera().getPlaneState(); + for (int i = 0; i < children.size(); i++) { + // Restoring cam state before proceeding children recusively + vp.getCamera().setPlaneState(camState); + renderSubScene(children.get(i), vp); + } + } else if (scene instanceof Geometry) { + // add to the render queue + Geometry gm = (Geometry) scene; + if (gm.getMaterial() == null) { + throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); + } + + vp.getQueue().addToQueue(gm, scene.getQueueBucket()); + + // add to shadow queue if needed + RenderQueue.ShadowMode shadowMode = scene.getShadowMode(); + if (shadowMode != RenderQueue.ShadowMode.Off) { + vp.getQueue().addToShadowQueue(gm, shadowMode); + } + } + } + + /** + * Returns the camera currently used for rendering. + *

    + * The camera can be set with {@link #setCamera(com.jme3.renderer.Camera, boolean) }. + * + * @return the camera currently used for rendering. + */ + public Camera getCurrentCamera() { + return prevCam; + } + + /** + * The renderer implementation used for rendering operations. + * + * @return The renderer implementation + * + * @see #RenderManager(com.jme3.renderer.Renderer) + * @see Renderer + */ + public Renderer getRenderer() { + return renderer; + } + + /** + * Flushes the ViewPort's {@link ViewPort#getQueue() render queue} + * by rendering each of its visible buckets. + * By default the queues will automatically be cleared after rendering, + * so there's no need to clear them manually. + * + * @param vp The ViewPort of which the queue will be flushed + * + * @see RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera) + * @see #renderGeometryList(com.jme3.renderer.queue.GeometryList) + */ + public void flushQueue(ViewPort vp) { + renderViewPortQueues(vp, true); + } + + /** + * Clears the queue of the given ViewPort. + * Simply calls {@link RenderQueue#clear() } on the ViewPort's + * {@link ViewPort#getQueue() render queue}. + * + * @param vp The ViewPort of which the queue will be cleared. + * + * @see RenderQueue#clear() + * @see ViewPort#getQueue() + */ + public void clearQueue(ViewPort vp) { + vp.getQueue().clear(); + } + + /** + * Render the given viewport queues. + *

    + * Changes the {@link Renderer#setDepthRange(float, float) depth range} + * appropriately as expected by each queue and then calls + * {@link RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean) } + * on the queue. Makes sure to restore the depth range to [0, 1] + * at the end of the call. + * Note that the {@link Bucket#Translucent translucent bucket} is NOT + * rendered by this method. Instead the user should call + * {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) } + * after this call. + * + * @param vp the viewport of which queue should be rendered + * @param flush If true, the queues will be cleared after + * rendering. + * + * @see RenderQueue + * @see #renderTranslucentQueue(com.jme3.renderer.ViewPort) + */ + public void renderViewPortQueues(ViewPort vp, boolean flush) { + RenderQueue rq = vp.getQueue(); + Camera cam = vp.getCamera(); + boolean depthRangeChanged = false; + + // render opaque objects with default depth range + // opaque objects are sorted front-to-back, reducing overdraw + rq.renderQueue(Bucket.Opaque, this, cam, flush); + + // render the sky, with depth range set to the farthest + if (!rq.isQueueEmpty(Bucket.Sky)) { + renderer.setDepthRange(1, 1); + rq.renderQueue(Bucket.Sky, this, cam, flush); + depthRangeChanged = true; + } + + + // transparent objects are last because they require blending with the + // rest of the scene's objects. Consequently, they are sorted + // back-to-front. + if (!rq.isQueueEmpty(Bucket.Transparent)) { + if (depthRangeChanged) { + renderer.setDepthRange(0, 1); + depthRangeChanged = false; + } + + rq.renderQueue(Bucket.Transparent, this, cam, flush); + } + + if (!rq.isQueueEmpty(Bucket.Gui)) { + renderer.setDepthRange(0, 0); + setCamera(cam, true); + rq.renderQueue(Bucket.Gui, this, cam, flush); + setCamera(cam, false); + depthRangeChanged = true; + } + + // restore range to default + if (depthRangeChanged) { + renderer.setDepthRange(0, 1); + } + } + + /** + * Renders the {@link Bucket#Translucent translucent queue} on the viewPort. + *

    + * This call does nothing unless {@link #setHandleTranslucentBucket(boolean) } + * is set to true. This method clears the translucent queue after rendering + * it. + * + * @param vp The viewport of which the translucent queue should be rendered. + * + * @see #renderViewPortQueues(com.jme3.renderer.ViewPort, boolean) + * @see #setHandleTranslucentBucket(boolean) + */ + public void renderTranslucentQueue(ViewPort vp) { + RenderQueue rq = vp.getQueue(); + if (!rq.isQueueEmpty(Bucket.Translucent) && handleTranlucentBucket) { + rq.renderQueue(Bucket.Translucent, this, vp.getCamera(), true); + } + } + + private void setViewPort(Camera cam) { + // this will make sure to update viewport only if needed + if (cam != prevCam || cam.isViewportChanged()) { + viewX = (int) (cam.getViewPortLeft() * cam.getWidth()); + viewY = (int) (cam.getViewPortBottom() * cam.getHeight()); + viewWidth = (int) ((cam.getViewPortRight() - cam.getViewPortLeft()) * cam.getWidth()); + viewHeight = (int) ((cam.getViewPortTop() - cam.getViewPortBottom()) * cam.getHeight()); + uniformBindingManager.setViewPort(viewX, viewY, viewWidth, viewHeight); + renderer.setViewPort(viewX, viewY, viewWidth, viewHeight); + renderer.setClipRect(viewX, viewY, viewWidth, viewHeight); + cam.clearViewportChanged(); + prevCam = cam; + +// float translateX = viewWidth == viewX ? 0 : -(viewWidth + viewX) / (viewWidth - viewX); +// float translateY = viewHeight == viewY ? 0 : -(viewHeight + viewY) / (viewHeight - viewY); +// float scaleX = viewWidth == viewX ? 1f : 2f / (viewWidth - viewX); +// float scaleY = viewHeight == viewY ? 1f : 2f / (viewHeight - viewY); +// +// orthoMatrix.loadIdentity(); +// orthoMatrix.setTranslation(translateX, translateY, 0); +// orthoMatrix.setScale(scaleX, scaleY, 0); + + orthoMatrix.loadIdentity(); + orthoMatrix.setTranslation(-1f, -1f, 0f); + orthoMatrix.setScale(2f / cam.getWidth(), 2f / cam.getHeight(), 0f); + } + } + + private void setViewProjection(Camera cam, boolean ortho) { + if (shader) { + if (ortho) { + uniformBindingManager.setCamera(cam, Matrix4f.IDENTITY, orthoMatrix, orthoMatrix); + } else { + uniformBindingManager.setCamera(cam, cam.getViewMatrix(), cam.getProjectionMatrix(), cam.getViewProjectionMatrix()); + } + } else { + if (ortho) { + renderer.setViewProjectionMatrices(Matrix4f.IDENTITY, orthoMatrix); + } else { + renderer.setViewProjectionMatrices(cam.getViewMatrix(), + cam.getProjectionMatrix()); + } + } + } + + /** + * Set the camera to use for rendering. + *

    + * First, the camera's + * {@link Camera#setViewPort(float, float, float, float) view port parameters} + * are applied. Then, the camera's {@link Camera#getViewMatrix() view} and + * {@link Camera#getProjectionMatrix() projection} matrices are set + * on the renderer. If ortho is true, then + * instead of using the camera's view and projection matrices, an ortho + * matrix is computed and used instead of the view projection matrix. + * The ortho matrix converts from the range (0 ~ Width, 0 ~ Height, -1 ~ +1) + * to the clip range (-1 ~ +1, -1 ~ +1, -1 ~ +1). + * + * @param cam The camera to set + * @param ortho True if to use orthographic projection (for GUI rendering), + * false if to use the camera's view and projection matrices. + */ + public void setCamera(Camera cam, boolean ortho) { + setViewPort(cam); + setViewProjection(cam, ortho); + } + + /** + * Draws the viewport but without notifying {@link SceneProcessor scene + * processors} of any rendering events. + * + * @param vp The ViewPort to render + * + * @see #renderViewPort(com.jme3.renderer.ViewPort, float) + */ + public void renderViewPortRaw(ViewPort vp) { + setCamera(vp.getCamera(), false); + List scenes = vp.getScenes(); + for (int i = scenes.size() - 1; i >= 0; i--) { + renderScene(scenes.get(i), vp); + } + flushQueue(vp); + } + + /** + * Renders the {@link ViewPort}. + *

    + * If the ViewPort is {@link ViewPort#isEnabled() disabled}, this method + * returns immediately. Otherwise, the ViewPort is rendered by + * the following process:
    + *

      + *
    • All {@link SceneProcessor scene processors} that are attached + * to the ViewPort are {@link SceneProcessor#initialize(com.jme3.renderer.RenderManager, com.jme3.renderer.ViewPort) initialized}. + *
    • + *
    • The SceneProcessors' {@link SceneProcessor#preFrame(float) } method + * is called.
    • + *
    • The ViewPort's {@link ViewPort#getOutputFrameBuffer() output framebuffer} + * is set on the Renderer
    • + *
    • The camera is set on the renderer, including its view port parameters. + * (see {@link #setCamera(com.jme3.renderer.Camera, boolean) })
    • + *
    • Any buffers that the ViewPort requests to be cleared are cleared + * and the {@link ViewPort#getBackgroundColor() background color} is set
    • + *
    • Every scene that is attached to the ViewPort is flattened into + * the ViewPort's render queue + * (see {@link #renderViewPortQueues(com.jme3.renderer.ViewPort, boolean) }) + *
    • + *
    • The SceneProcessors' {@link SceneProcessor#postQueue(com.jme3.renderer.queue.RenderQueue) } + * method is called.
    • + *
    • The render queue is sorted and then flushed, sending + * rendering commands to the underlying Renderer implementation. + * (see {@link #flushQueue(com.jme3.renderer.ViewPort) })
    • + *
    • The SceneProcessors' {@link SceneProcessor#postFrame(com.jme3.texture.FrameBuffer) } + * method is called.
    • + *
    • The translucent queue of the ViewPort is sorted and then flushed + * (see {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) })
    • + *
    • If any objects remained in the render queue, they are removed + * from the queue. This is generally objects added to the + * {@link RenderQueue#renderShadowQueue(com.jme3.renderer.queue.RenderQueue.ShadowMode, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean) + * shadow queue} + * which were not rendered because of a missing shadow renderer.
    • + *
    + * + * @param vp + * @param tpf + */ + public void renderViewPort(ViewPort vp, float tpf) { + if (!vp.isEnabled()) { + return; + } + List processors = vp.getProcessors(); + if (processors.isEmpty()) { + processors = null; + } + + if (processors != null) { + for (SceneProcessor proc : processors) { + if (!proc.isInitialized()) { + proc.initialize(this, vp); + } + proc.preFrame(tpf); + } + } + + renderer.setFrameBuffer(vp.getOutputFrameBuffer()); + setCamera(vp.getCamera(), false); + if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) { + if (vp.isClearColor()) { + renderer.setBackgroundColor(vp.getBackgroundColor()); + } + renderer.clearBuffers(vp.isClearColor(), + vp.isClearDepth(), + vp.isClearStencil()); + } + + List scenes = vp.getScenes(); + for (int i = scenes.size() - 1; i >= 0; i--) { + renderScene(scenes.get(i), vp); + } + + if (processors != null) { + for (SceneProcessor proc : processors) { + proc.postQueue(vp.getQueue()); + } + } + + flushQueue(vp); + + if (processors != null) { + for (SceneProcessor proc : processors) { + proc.postFrame(vp.getOutputFrameBuffer()); + } + } + //renders the translucent objects queue after processors have been rendered + renderTranslucentQueue(vp); + // clear any remaining spatials that were not rendered. + clearQueue(vp); + } + + public void setUsingShaders(boolean usingShaders) { + this.shader = usingShaders; + } + + /** + * Called by the application to render any ViewPorts + * added to this RenderManager. + *

    + * Renders any viewports that were added using the following methods: + *

      + *
    • {@link #createPreView(java.lang.String, com.jme3.renderer.Camera) }
    • + *
    • {@link #createMainView(java.lang.String, com.jme3.renderer.Camera) }
    • + *
    • {@link #createPostView(java.lang.String, com.jme3.renderer.Camera) }
    • + *
    + * + * @param tpf Time per frame value + */ + public void render(float tpf, boolean mainFrameBufferActive) { + if (renderer instanceof NullRenderer) { + return; + } + + this.shader = renderer.getCaps().contains(Caps.GLSL100); + + for (int i = 0; i < preViewPorts.size(); i++) { + ViewPort vp = preViewPorts.get(i); + if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive) { + renderViewPort(vp, tpf); + } + } + for (int i = 0; i < viewPorts.size(); i++) { + ViewPort vp = viewPorts.get(i); + if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive) { + renderViewPort(vp, tpf); + } + } + for (int i = 0; i < postViewPorts.size(); i++) { + ViewPort vp = postViewPorts.get(i); + if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive) { + renderViewPort(vp, tpf); + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java new file mode 100644 index 000000000..cf3640dda --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +import com.jme3.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 com.jme3.util.NativeObject; +import java.nio.ByteBuffer; +import java.util.EnumSet; + +/** + * The Renderer is responsible for taking rendering commands and + * executing them on the underlying video hardware. + * + * @author Kirill Vainer + */ +public interface Renderer { + + /** + * Get the capabilities of the renderer. + * @return The capabilities of the renderer. + */ + public EnumSet getCaps(); + + /** + * The statistics allow tracking of how data + * per frame, such as number of objects rendered, number of triangles, etc. + * These are updated when the Renderer's methods are used, make sure + * to call {@link Statistics#clearFrame() } at the appropriate time + * to get accurate info per frame. + */ + public Statistics getStatistics(); + + /** + * Invalidates the current rendering state. Should be called after + * the GL state was changed manually or through an external library. + */ + public void invalidateState(); + + /** + * Clears certain channels of the currently bound framebuffer. + * + * @param color True if to clear colors (RGBA) + * @param depth True if to clear depth/z + * @param stencil True if to clear stencil buffer (if available, otherwise + * ignored) + */ + public void clearBuffers(boolean color, boolean depth, boolean stencil); + + /** + * Sets the background (aka clear) color. + * + * @param color The background color to set + */ + public void setBackgroundColor(ColorRGBA color); + + /** + * Applies the given {@link RenderState}, making the necessary + * GL calls so that the state is applied. + */ + public void applyRenderState(RenderState state); + + /** + * Set the range of the depth values for objects. All rendered + * objects will have their depth clamped to this range. + * + * @param start The range start + * @param end The range end + */ + public void setDepthRange(float start, float end); + + /** + * Called when a new frame has been rendered. + */ + public void onFrame(); + + /** + * Set the world matrix to use. Does nothing if the Renderer is + * shader based. + * + * @param worldMatrix World matrix to use. + */ + public void setWorldMatrix(Matrix4f worldMatrix); + + /** + * Sets the view and projection matrices to use. Does nothing if the Renderer + * is shader based. + * + * @param viewMatrix The view matrix to use. + * @param projMatrix The projection matrix to use. + */ + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix); + + /** + * Set the viewport location and resolution on the screen. + * + * @param x The x coordinate of the viewport + * @param y The y coordinate of the viewport + * @param width Width of the viewport + * @param height Height of the viewport + */ + public void setViewPort(int x, int y, int width, int height); + + /** + * Specifies a clipping rectangle. + * For all future rendering commands, no pixels will be allowed + * to be rendered outside of the clip rectangle. + * + * @param x The x coordinate of the clip rect + * @param y The y coordinate of the clip rect + * @param width Width of the clip rect + * @param height Height of the clip rect + */ + public void setClipRect(int x, int y, int width, int height); + + /** + * Clears the clipping rectangle set with + * {@link #setClipRect(int, int, int, int) }. + */ + public void clearClipRect(); + + /** + * Set lighting state. + * Does nothing if the renderer is shader based. + * The lights should be provided in world space. + * Specify null to disable lighting. + * + * @param lights The light list to set. + */ + public void setLighting(LightList lights); + + /** + * Sets the shader to use for rendering. + * If the shader has not been uploaded yet, it is compiled + * and linked. If it has been uploaded, then the + * uniform data is updated and the shader is set. + * + * @param shader The shader to use for rendering. + */ + public void setShader(Shader shader); + + /** + * Deletes a shader. This method also deletes + * the attached shader sources. + * + * @param shader Shader to delete. + */ + public void deleteShader(Shader shader); + + /** + * Deletes the provided shader source. + * + * @param source The ShaderSource to delete. + */ + public void deleteShaderSource(ShaderSource source); + + /** + * Copies contents from src to dst, scaling if necessary. + */ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst); + + /** + * Copies contents from src to dst, scaling if necessary. + * set copyDepth to false to only copy the color buffers. + */ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth); + + /** + * Sets the framebuffer that will be drawn to. + */ + public void setFrameBuffer(FrameBuffer fb); + + /** + * Set the framebuffer that will be set instead of the main framebuffer + * when a call to setFrameBuffer(null) is made. + * + * @param fb + */ + public void setMainFrameBufferOverride(FrameBuffer fb); + + /** + * Reads the pixels currently stored in the specified framebuffer + * into the given ByteBuffer object. + * Only color pixels are transferred, the format is BGRA with 8 bits + * per component. The given byte buffer should have at least + * fb.getWidth() * fb.getHeight() * 4 bytes remaining. + * + * @param fb The framebuffer to read from + * @param byteBuf The bytebuffer to transfer color data to + */ + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf); + + /** + * Deletes a framebuffer and all attached renderbuffers + */ + public void deleteFrameBuffer(FrameBuffer fb); + + /** + * Sets the texture to use for the given texture unit. + */ + public void setTexture(int unit, Texture tex); + + /** + * Modify the given Texture tex with the given Image. The image will be put at x and y into the texture. + * + * @param tex the Texture that will be modified + * @param pixels the source Image data to copy data from + * @param x the x position to put the image into the texture + * @param y the y position to put the image into the texture + */ + public void modifyTexture(Texture tex, Image pixels, int x, int y); + + /** + * Deletes a texture from the GPU. + */ + public void deleteImage(Image image); + + /** + * Uploads a vertex buffer to the GPU. + * + * @param vb The vertex buffer to upload + */ + public void updateBufferData(VertexBuffer vb); + + /** + * Deletes a vertex buffer from the GPU. + * @param vb The vertex buffer to delete + */ + public void deleteBuffer(VertexBuffer vb); + + /** + * Renders 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 The mesh to render + * @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }. + * @param count Number of mesh instances to render + */ + public void renderMesh(Mesh mesh, int lod, int count); + + /** + * Resets all previously used {@link NativeObject Native Objects} on this Renderer. + * The state of the native objects is reset in such way, that using + * them again will cause the renderer to reupload them. + * Call this method when you know the GL context is going to shutdown. + * + * @see NativeObject#resetObject() + */ + public void resetGLObjects(); + + /** + * Deletes all previously used {@link NativeObject Native Objects} on this Renderer, and + * then resets the native objects. + * + * @see #resetGLObjects() + * @see NativeObject#deleteObject(java.lang.Object) + */ + public void cleanup(); + + /** + * Sets the alpha to coverage state. + *

    + * When alpha coverage and multi-sampling is enabled, + * each pixel will contain alpha coverage in all + * of its subsamples, which is then combined when + * other future alpha-blended objects are rendered. + *

    + *

    + * Alpha-to-coverage is useful for rendering transparent objects + * without having to worry about sorting them. + *

    + */ + public void setAlphaToCoverage(boolean value); +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/RendererException.java b/jme3-core/src/main/java/com/jme3/renderer/RendererException.java new file mode 100644 index 000000000..7781fae96 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/RendererException.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +/** + * RendererException is raised when a renderer encounters + * a fatal rendering error. + * + * @author Kirill Vainer + */ +public class RendererException extends RuntimeException { + + /** + * Creates a new instance of RendererException + */ + public RendererException(String message){ + super(message); + } +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/Statistics.java b/jme3-core/src/main/java/com/jme3/renderer/Statistics.java new file mode 100644 index 000000000..db75d0b74 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/Statistics.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +import com.jme3.scene.Mesh; +import com.jme3.shader.Shader; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import java.util.HashSet; + +/** + * The statistics class allows tracking of real-time rendering statistics. + *

    + * The Statistics can be retrieved by using {@link Renderer#getStatistics() }. + * + * @author Kirill Vainer + */ +public class Statistics { + + protected boolean enabled = false; + + 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(); + + /** + * Returns a list of labels corresponding to each statistic. + * + * @return a list of labels corresponding to each statistic. + * + * @see #getData(int[]) + */ + public String[] getLabels(){ + return new String[]{ "Vertices", + "Triangles", + "Uniforms", + + "Objects", + + "Shaders (S)", + "Shaders (F)", + "Shaders (M)", + + "Textures (S)", + "Textures (F)", + "Textures (M)", + + "FrameBuffers (S)", + "FrameBuffers (F)", + "FrameBuffers (M)" }; + + } + + /** + * Retrieves the statistics data into the given array. + * The array should be as large as the array given in + * {@link #getLabels() }. + * + * @param data The data array to write to + */ + public void getData(int[] data){ + data[0] = numVertices; + data[1] = numTriangles; + data[2] = numUniformsSet; + data[3] = numObjects; + + data[4] = numShaderSwitches; + data[5] = shadersUsed.size(); + data[6] = memoryShaders; + + data[7] = numTextureBinds; + data[8] = texturesUsed.size(); + data[9] = memoryTextures; + + data[10] = numFboSwitches; + data[11] = fbosUsed.size(); + data[12] = memoryFrameBuffers; + } + + /** + * Called by the Renderer when a mesh has been drawn. + * + */ + public void onMeshDrawn(Mesh mesh, int lod){ + if( !enabled ) + return; + + numObjects ++; + numTriangles += mesh.getTriangleCount(lod); + numVertices += mesh.getVertexCount(); + } + + /** + * Called by the Renderer when a shader has been utilized. + * + * @param shader The shader that was used + * @param wasSwitched If true, the shader has required a state switch + */ + public void onShaderUse(Shader shader, boolean wasSwitched){ + assert shader.getId() >= 1; + + if( !enabled ) + return; + + if (!shadersUsed.contains(shader.getId())) + shadersUsed.add(shader.getId()); + + if (wasSwitched) + numShaderSwitches++; + } + + /** + * Called by the Renderer when a uniform was set. + */ + public void onUniformSet(){ + if( !enabled ) + return; + numUniformsSet ++; + } + + /** + * Called by the Renderer when a texture has been set. + * + * @param image The image that was set + * @param wasSwitched If true, the texture has required a state switch + */ + public void onTextureUse(Image image, boolean wasSwitched){ + assert image.getId() >= 1; + + if( !enabled ) + return; + + if (!texturesUsed.contains(image.getId())) + texturesUsed.add(image.getId()); + + if (wasSwitched) + numTextureBinds ++; + } + + /** + * Called by the Renderer when a framebuffer has been set. + * + * @param fb The framebuffer that was set + * @param wasSwitched If true, the framebuffer required a state switch + */ + public void onFrameBufferUse(FrameBuffer fb, boolean wasSwitched){ + if( !enabled ) + return; + + if (fb != null){ + assert fb.getId() >= 1; + + if (!fbosUsed.contains(fb.getId())) + fbosUsed.add(fb.getId()); + } + + if (wasSwitched) + numFboSwitches ++; + } + + /** + * Clears all frame-specific statistics such as objects used per frame. + */ + public void clearFrame(){ + shadersUsed.clear(); + texturesUsed.clear(); + fbosUsed.clear(); + + numObjects = 0; + numTriangles = 0; + numVertices = 0; + numShaderSwitches = 0; + numTextureBinds = 0; + numFboSwitches = 0; + numUniformsSet = 0; + } + + /** + * Called by the Renderer when it creates a new shader + */ + public void onNewShader(){ + if( !enabled ) + return; + memoryShaders ++; + } + + /** + * Called by the Renderer when it creates a new texture + */ + public void onNewTexture(){ + if( !enabled ) + return; + memoryTextures ++; + } + + /** + * Called by the Renderer when it creates a new framebuffer + */ + public void onNewFrameBuffer(){ + if( !enabled ) + return; + memoryFrameBuffers ++; + } + + /** + * Called by the Renderer when it deletes a shader + */ + public void onDeleteShader(){ + if( !enabled ) + return; + memoryShaders --; + } + + /** + * Called by the Renderer when it deletes a texture + */ + public void onDeleteTexture(){ + if( !enabled ) + return; + memoryTextures --; + } + + /** + * Called by the Renderer when it deletes a framebuffer + */ + public void onDeleteFrameBuffer(){ + if( !enabled ) + return; + memoryFrameBuffers --; + } + + /** + * Called when video memory is cleared. + */ + public void clearMemory(){ + memoryFrameBuffers = 0; + memoryShaders = 0; + memoryTextures = 0; + } + + public void setEnabled( boolean f ) { + this.enabled = f; + } + + public boolean isEnabled() { + return enabled; + } +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java new file mode 100644 index 000000000..27109ce25 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +import com.jme3.math.ColorRGBA; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.texture.FrameBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * A ViewPort represents a view inside the display + * window or a {@link FrameBuffer} to which scenes will be rendered. + *

    + * A viewport has a {@link #ViewPort(java.lang.String, com.jme3.renderer.Camera) camera} + * which is used to render a set of {@link #attachScene(com.jme3.scene.Spatial) scenes}. + * A view port has a location on the screen as set by the + * {@link Camera#setViewPort(float, float, float, float) } method. + * By default, a view port does not clear the framebuffer, but it can be + * set to {@link #setClearFlags(boolean, boolean, boolean) clear the framebuffer}. + * The background color which the color buffer is cleared to can be specified + * via the {@link #setBackgroundColor(com.jme3.math.ColorRGBA)} method. + *

    + * A ViewPort has a list of {@link SceneProcessor}s which can + * control how the ViewPort is rendered by the {@link RenderManager}. + * + * @author Kirill Vainer + * + * @see RenderManager + * @see SceneProcessor + * @see Spatial + * @see Camera + */ +public class ViewPort { + + protected final String name; + protected final Camera cam; + protected final RenderQueue queue = new RenderQueue(); + protected final ArrayList 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 clearDepth = false, clearColor = false, clearStencil = false; + private boolean enabled = true; + + /** + * Create a new viewport. User code should generally use these methods instead:
    + *

      + *
    • {@link RenderManager#createPreView(java.lang.String, com.jme3.renderer.Camera) }
    • + *
    • {@link RenderManager#createMainView(java.lang.String, com.jme3.renderer.Camera) }
    • + *
    • {@link RenderManager#createPostView(java.lang.String, com.jme3.renderer.Camera) }
    • + *
    + * + * @param name The name of the viewport. Used for debugging only. + * @param cam The camera through which the viewport is rendered. The camera + * cannot be swapped to a different one after creating the viewport. + */ + public ViewPort(String name, Camera cam) { + this.name = name; + this.cam = cam; + } + + /** + * Returns the name of the viewport as set in the constructor. + * + * @return the name of the viewport + * + * @see #ViewPort(java.lang.String, com.jme3.renderer.Camera) + */ + public String getName() { + return name; + } + + /** + * Get the list of {@link SceneProcessor scene processors} that were + * added to this ViewPort + * + * @return the list of processors attached to this ViewPort + * + * @see #addProcessor(com.jme3.post.SceneProcessor) + */ + public List getProcessors(){ + return processors; + } + + /** + * Adds a {@link SceneProcessor} to this ViewPort. + *

    + * SceneProcessors that are added to the ViewPort will be notified + * of events as the ViewPort is being rendered by the {@link RenderManager}. + * + * @param processor The processor to add + * + * @see SceneProcessor + */ + public void addProcessor(SceneProcessor processor){ + if (processor == null) { + throw new IllegalArgumentException( "Processor cannot be null." ); + } + processors.add(processor); + } + + /** + * Removes a {@link SceneProcessor} from this ViewPort. + *

    + * The processor will no longer receive events occurring to this ViewPort. + * + * @param processor The processor to remove + * + * @see SceneProcessor + */ + public void removeProcessor(SceneProcessor processor){ + if (processor == null) { + throw new IllegalArgumentException( "Processor cannot be null." ); + } + processors.remove(processor); + processor.cleanup(); + } + + /** + * Removes all {@link SceneProcessor scene processors} from this + * ViewPort. + * + * @see SceneProcessor + */ + public void clearProcessors() { + for (SceneProcessor proc : processors) { + proc.cleanup(); + } + processors.clear(); + } + + /** + * Check if depth buffer clearing is enabled. + * + * @return true if depth buffer clearing is enabled. + * + * @see #setClearDepth(boolean) + */ + public boolean isClearDepth() { + return clearDepth; + } + + /** + * Enable or disable clearing of the depth buffer for this ViewPort. + *

    + * By default depth clearing is disabled. + * + * @param clearDepth Enable/disable depth buffer clearing. + */ + public void setClearDepth(boolean clearDepth) { + this.clearDepth = clearDepth; + } + + /** + * Check if color buffer clearing is enabled. + * + * @return true if color buffer clearing is enabled. + * + * @see #setClearColor(boolean) + */ + public boolean isClearColor() { + return clearColor; + } + + /** + * Enable or disable clearing of the color buffer for this ViewPort. + *

    + * By default color clearing is disabled. + * + * @param clearColor Enable/disable color buffer clearing. + */ + public void setClearColor(boolean clearColor) { + this.clearColor = clearColor; + } + + /** + * Check if stencil buffer clearing is enabled. + * + * @return true if stencil buffer clearing is enabled. + * + * @see #setClearStencil(boolean) + */ + public boolean isClearStencil() { + return clearStencil; + } + + /** + * Enable or disable clearing of the stencil buffer for this ViewPort. + *

    + * By default stencil clearing is disabled. + * + * @param clearStencil Enable/disable stencil buffer clearing. + */ + public void setClearStencil(boolean clearStencil) { + this.clearStencil = clearStencil; + } + + /** + * Set the clear flags (color, depth, stencil) in one call. + * + * @param color If color buffer clearing should be enabled. + * @param depth If depth buffer clearing should be enabled. + * @param stencil If stencil buffer clearing should be enabled. + * + * @see #setClearColor(boolean) + * @see #setClearDepth(boolean) + * @see #setClearStencil(boolean) + */ + public void setClearFlags(boolean color, boolean depth, boolean stencil){ + this.clearColor = color; + this.clearDepth = depth; + this.clearStencil = stencil; + } + + /** + * Returns the framebuffer where this ViewPort's scenes are + * rendered to. + * + * @return the framebuffer where this ViewPort's scenes are + * rendered to. + * + * @see #setOutputFrameBuffer(com.jme3.texture.FrameBuffer) + */ + public FrameBuffer getOutputFrameBuffer() { + return out; + } + + /** + * Sets the output framebuffer for the ViewPort. + *

    + * The output framebuffer specifies where the scenes attached + * to this ViewPort are rendered to. By default this is null + * which indicates the scenes are rendered to the display window. + * + * @param out The framebuffer to render scenes to, or null if to render + * to the screen. + */ + public void setOutputFrameBuffer(FrameBuffer out) { + this.out = out; + } + + /** + * Returns the camera which renders the attached scenes. + * + * @return the camera which renders the attached scenes. + * + * @see Camera + */ + public Camera getCamera() { + return cam; + } + + /** + * Internal use only. + */ + public RenderQueue getQueue() { + return queue; + } + + /** + * Attaches a new scene to render in this ViewPort. + * + * @param scene The scene to attach + * + * @see Spatial + */ + public void attachScene(Spatial scene){ + if (scene == null) { + throw new IllegalArgumentException( "Scene cannot be null." ); + } + sceneList.add(scene); + if (scene instanceof Geometry) { + scene.forceRefresh(true, false, true); + } + } + + /** + * Detaches a scene from rendering. + * + * @param scene The scene to detach + * + * @see #attachScene(com.jme3.scene.Spatial) + */ + public void detachScene(Spatial scene){ + if (scene == null) { + throw new IllegalArgumentException( "Scene cannot be null." ); + } + sceneList.remove(scene); + if (scene instanceof Geometry) { + scene.forceRefresh(true, false, true); + } + } + + /** + * Removes all attached scenes. + * + * @see #attachScene(com.jme3.scene.Spatial) + */ + public void clearScenes() { + sceneList.clear(); + } + + /** + * Returns a list of all attached scenes. + * + * @return a list of all attached scenes. + * + * @see #attachScene(com.jme3.scene.Spatial) + */ + public List getScenes(){ + return sceneList; + } + + /** + * Sets the background color. + *

    + * When the ViewPort's color buffer is cleared + * (if {@link #setClearColor(boolean) color clearing} is enabled), + * this specifies the color to which the color buffer is set to. + * By default the background color is black without alpha. + * + * @param background the background color. + */ + public void setBackgroundColor(ColorRGBA background){ + backColor.set(background); + } + + /** + * Returns the background color of this ViewPort + * + * @return the background color of this ViewPort + * + * @see #setBackgroundColor(com.jme3.math.ColorRGBA) + */ + public ColorRGBA getBackgroundColor(){ + return backColor; + } + + /** + * Enable or disable this ViewPort. + *

    + * Disabled ViewPorts are skipped by the {@link RenderManager} when + * rendering. By default all ViewPorts are enabled. + * + * @param enable If the viewport should be disabled or enabled. + */ + public void setEnabled(boolean enable) { + this.enabled = enable; + } + + /** + * Returns true if the viewport is enabled, false otherwise. + * @return true if the viewport is enabled, false otherwise. + * @see #setEnabled(boolean) + */ + public boolean isEnabled() { + return enabled; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/package.html b/jme3-core/src/main/java/com/jme3/renderer/package.html new file mode 100644 index 000000000..39a0a2b8c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/package.html @@ -0,0 +1,38 @@ + + + + + + + + + +The com.jme3.renderer package provides classes responsible for +rendering. +

    +The most critical classes are the {@link com.jme3.renderer.Renderer}, +which is the low-level rendering implementation and is abstract, and the +{@link com.jme3.renderer.RenderManager} class, which provides the high-level +rendering logic on top of the Renderer. +

    +To accompany rendering, several helper classes are available. +

      +
    • The {@link com.jme3.renderer.Camera} is used to specify the point-of-view + from which scenes are rendered.
    • +
    • The {@link com.jme3.renderer.ViewPort} is the +aggregation of a Camera and a set of {@link com.jme3.scene.Spatial scenes} +which are to be rendered, as well as additional info.
    • +
    • The {@link com.jme3.renderer.Caps} class contains renderer capabilities +which the user can query to find out what features are available in the +rendering implementation.
    • +
    • The {@link com.jme3.renderer.Statistics} class is updated in real time + by the Renderer, and is used to find out various statistics about + the rendering
    • +
    • The {@link com.jme3.util.NativeObjectManager} and {@link com.jme3.util.NativeObject} classes + provide a link between the renderer's native objects and Java's garbage collected objects, + allowing the engine to track when the Java object counterpart is garbage collected + and then delete the native object counterpart from the renderer.
    • +
    + + + diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryComparator.java b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryComparator.java new file mode 100644 index 000000000..39a8fdf82 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryComparator.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.queue; + +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import java.util.Comparator; + +/** + * GeometryComparator is a special version of {@link Comparator} + * that is used to sort geometries for rendering in the {@link RenderQueue}. + * + * @author Kirill Vainer + */ +public interface GeometryComparator extends Comparator { + + /** + * Set the camera to use for sorting. + * + * @param cam The camera to use for sorting + */ + public void setCamera(Camera cam); +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java new file mode 100644 index 000000000..1016f8bfd --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.queue; + +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import com.jme3.util.SortUtil; + +/** + * This class is a special purpose list of {@link Geometry} objects for render + * queuing. + * + * @author Jack Lindamood + * @author Three Rings - better sorting alg. + * @author Kirill Vainer + */ +public class GeometryList { + + private static final int DEFAULT_SIZE = 32; + + private Geometry[] geometries; + private Geometry[] geometries2; + private int size; + private GeometryComparator comparator; + + /** + * Initializes the GeometryList to use the given {@link GeometryComparator} + * to use for comparing geometries. + * + * @param comparator The comparator to use. + */ + public GeometryList(GeometryComparator comparator) { + size = 0; + geometries = new Geometry[DEFAULT_SIZE]; + geometries2 = new Geometry[DEFAULT_SIZE]; + this.comparator = comparator; + } + + public void setComparator(GeometryComparator comparator) { + this.comparator = comparator; + } + + /** + * Returns the GeometryComparator that this Geometry list uses + * for sorting. + */ + public GeometryComparator getComparator() { + return comparator; + } + + /** + * Set the camera that will be set on the geometry comparators + * via {@link GeometryComparator#setCamera(com.jme3.renderer.Camera)}. + * + * @param cam Camera to use for sorting. + */ + public void setCamera(Camera cam){ + this.comparator.setCamera(cam); + } + + /** + * Returns the number of elements in this GeometryList. + * + * @return Number of elements in the list + */ + public int size(){ + return size; + } + + /** + * Returns the element at the given index. + * + * @param index The index to lookup + * @return Geometry at the index + */ + public Geometry get(int index){ + return geometries[index]; + } + + /** + * Adds a geometry to the list. + * List size is doubled if there is no room. + * + * @param g + * The geometry to add. + */ + public void add(Geometry g) { + if (size == geometries.length) { + Geometry[] temp = new Geometry[size * 2]; + System.arraycopy(geometries, 0, temp, 0, size); + geometries = temp; // original list replaced by double-size list + + geometries2 = new Geometry[size * 2]; + } + geometries[size++] = g; + } + + /** + * Resets list size to 0. + */ + public void clear() { + for (int i = 0; i < size; i++){ + geometries[i] = null; + } + + size = 0; + } + + /** + * Sorts the elements in the list according to their Comparator. + */ + public void sort() { + if (size > 1) { + // sort the spatial list using the comparator + +// SortUtil.qsort(geometries, 0, size, comparator); +// Arrays.sort(geometries, 0, size, comparator); + + System.arraycopy(geometries, 0, geometries2, 0, size); + SortUtil.msort(geometries2, geometries, 0, size-1, comparator); + + + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/GuiComparator.java b/jme3-core/src/main/java/com/jme3/renderer/queue/GuiComparator.java new file mode 100644 index 000000000..541b7eb12 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/GuiComparator.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.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/jme3-core/src/main/java/com/jme3/renderer/queue/NullComparator.java b/jme3-core/src/main/java/com/jme3/renderer/queue/NullComparator.java new file mode 100644 index 000000000..bd810ca81 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/NullComparator.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.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/jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java b/jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java new file mode 100644 index 000000000..6ffff5702 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.queue; + +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; + +public class OpaqueComparator implements GeometryComparator { + + private Camera cam; + private final Vector3f tempVec = new Vector3f(); + private final Vector3f tempVec2 = new Vector3f(); + + public void setCamera(Camera cam){ + this.cam = cam; + } + + public float distanceToCam(Geometry spat){ + if (spat == null) + return Float.NEGATIVE_INFINITY; + + if (spat.queueDistance != Float.NEGATIVE_INFINITY) + return spat.queueDistance; + + Vector3f camPosition = cam.getLocation(); + Vector3f viewVector = cam.getDirection(tempVec2); + Vector3f spatPosition = null; + + if (spat.getWorldBound() != null){ + spatPosition = spat.getWorldBound().getCenter(); + }else{ + spatPosition = spat.getWorldTranslation(); + } + + spatPosition.subtract(camPosition, tempVec); + spat.queueDistance = tempVec.dot(viewVector); + + return spat.queueDistance; + } + + public int compare(Geometry o1, Geometry o2) { + Material m1 = o1.getMaterial(); + Material m2 = o2.getMaterial(); + + int compareResult = m2.getSortId() - m1.getSortId(); + if (compareResult == 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 compareResult; + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java b/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java new file mode 100644 index 000000000..98323c7a3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.queue; + +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; + +/** + * RenderQueue is used to queue up and sort + * {@link Geometry geometries} for rendering. + * + * @author Kirill Vainer + */ +public class RenderQueue { + + private GeometryList opaqueList; + private GeometryList guiList; + private GeometryList transparentList; + private GeometryList translucentList; + private GeometryList skyList; + private GeometryList shadowRecv; + private GeometryList shadowCast; + + /** + * Creates a new RenderQueue, the default {@link GeometryComparator comparators} + * are used for all {@link GeometryList geometry lists}. + */ + public RenderQueue() { + this.opaqueList = new GeometryList(new OpaqueComparator()); + this.guiList = new GeometryList(new GuiComparator()); + this.transparentList = new GeometryList(new TransparentComparator()); + this.translucentList = new GeometryList(new TransparentComparator()); + this.skyList = new GeometryList(new NullComparator()); + this.shadowRecv = new GeometryList(new OpaqueComparator()); + this.shadowCast = new GeometryList(new OpaqueComparator()); + } + + /** + * The render queue Bucket specifies the bucket + * to which the spatial will be placed when rendered. + *

    + * The behavior of the rendering will differ depending on which + * bucket the spatial is placed. A spatial's queue bucket can be set + * via {@link Spatial#setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket) }. + */ + public enum Bucket { + /** + * The renderer will try to find the optimal order for rendering all + * objects using this mode. + * You should use this mode for most normal objects, except transparent + * ones, as it could give a nice performance boost to your application. + */ + Opaque, + + /** + * This is the mode you should use for object with + * transparency in them. It will ensure the objects furthest away are + * rendered first. That ensures when another transparent object is drawn on + * top of previously drawn objects, you can see those (and the object drawn + * using Opaque) through the transparent parts of the newly drawn + * object. + */ + Transparent, + + /** + * A special mode used for rendering really far away, flat objects - + * e.g. skies. In this mode, the depth is set to infinity so + * spatials in this bucket will appear behind everything, the downside + * to this bucket is that 3D objects will not be rendered correctly + * due to lack of depth testing. + */ + Sky, + + /** + * A special mode used for rendering transparent objects that + * should not be effected by {@link SceneProcessor}. + * Generally this would contain translucent objects, and + * also objects that do not write to the depth buffer such as + * particle emitters. + */ + Translucent, + + /** + * This is a special mode, for drawing 2D object + * without perspective (such as GUI or HUD parts). + * The spatial's world coordinate system has the range + * of [0, 0, -1] to [Width, Height, 1] where Width/Height is + * the resolution of the screen rendered to. Any spatials + * outside of that range are culled. + */ + Gui, + + /** + * A special mode, that will ensure that this spatial uses the same + * mode as the parent Node does. + */ + Inherit, + } + + /** + * ShadowMode is a marker used to specify how shadow + * effects should treat the spatial. + */ + public enum ShadowMode { + /** + * Disable both shadow casting and shadow receiving for this spatial. + * Generally used for special effects like particle emitters. + */ + Off, + + /** + * Enable casting of shadows but not receiving them. + */ + Cast, + + /** + * Enable receiving of shadows but not casting them. + */ + Receive, + + /** + * Enable both receiving and casting of shadows. + */ + CastAndReceive, + + /** + * Inherit the ShadowMode from the parent node. + */ + Inherit + } + + /** + * Sets a different geometry comparator for the specified bucket, one + * of Gui, Opaque, Sky, Transparent, or Translucent. The GeometryComparators are + * used to sort the accumulated list of geometries before actual rendering + * occurs. + * + *

    The most significant comparator is the one for the transparent + * bucket since there is no correct way to sort the transparent bucket + * that will handle all geometry all the time. In certain cases, the + * application may know the best way to sort and now has the option of + * configuring a specific implementation.

    + * + *

    The default comparators are:

    + *
      + *
    • Bucket.Opaque: {@link com.jme3.renderer.queue.OpaqueComparator} which sorts + * by material first and front to back within the same material. + *
    • Bucket.Transparent: {@link com.jme3.renderer.queue.TransparentComparator} which + * sorts purely back to front by leading bounding edge with no material sort. + *
    • Bucket.Translucent: {@link com.jme3.renderer.queue.TransparentComparator} which + * sorts purely back to front by leading bounding edge with no material sort. this bucket is rendered after post processors. + *
    • Bucket.Sky: {@link com.jme3.renderer.queue.NullComparator} which does no sorting + * at all. + *
    • Bucket.Gui: {@link com.jme3.renderer.queue.GuiComparator} sorts geometries back to + * front based on their Z values. + */ + public void setGeometryComparator(Bucket bucket, GeometryComparator c) { + switch (bucket) { + case Gui: + guiList = new GeometryList(c); + break; + case Opaque: + opaqueList = new GeometryList(c); + break; + case Sky: + skyList = new GeometryList(c); + break; + case Transparent: + transparentList = new GeometryList(c); + break; + case Translucent: + translucentList = new GeometryList(c); + break; + default: + throw new UnsupportedOperationException("Unknown bucket type: " + bucket); + } + } + + /** + * Returns the current GeometryComparator used by the specified bucket, + * one of Gui, Opaque, Sky, Transparent, or Translucent. + */ + public GeometryComparator getGeometryComparator(Bucket bucket) { + switch (bucket) { + case Gui: + return guiList.getComparator(); + case Opaque: + return opaqueList.getComparator(); + case Sky: + return skyList.getComparator(); + case Transparent: + return transparentList.getComparator(); + case Translucent: + return translucentList.getComparator(); + default: + throw new UnsupportedOperationException("Unknown bucket type: " + bucket); + } + } + + /** + * Adds a geometry to a shadow bucket. + * Note that this operation is done automatically by the + * {@link RenderManager}. {@link SceneProcessor}s that handle + * shadow rendering should fetch the queue by using + * {@link #getShadowQueueContent(com.jme3.renderer.queue.RenderQueue.ShadowMode) }, + * by default no action is taken on the shadow queues. + * + * @param g The geometry to add + * @param shadBucket The shadow bucket type, if it is + * {@link ShadowMode#CastAndReceive}, it is added to both the cast + * and the receive buckets. + */ + public void addToShadowQueue(Geometry g, ShadowMode shadBucket) { + switch (shadBucket) { + case Inherit: + break; + case Off: + break; + case Cast: + shadowCast.add(g); + break; + case Receive: + shadowRecv.add(g); + break; + case CastAndReceive: + shadowCast.add(g); + shadowRecv.add(g); + break; + default: + throw new UnsupportedOperationException("Unrecognized shadow bucket type: " + shadBucket); + } + } + + /** + * Adds a geometry to the given bucket. + * The {@link RenderManager} automatically handles this task + * when flattening the scene graph. The bucket to add + * the geometry is determined by {@link Geometry#getQueueBucket() }. + * + * @param g The geometry to add + * @param bucket The bucket to add to, usually + * {@link Geometry#getQueueBucket() }. + */ + public void addToQueue(Geometry g, Bucket bucket) { + switch (bucket) { + case Gui: + guiList.add(g); + break; + case Opaque: + opaqueList.add(g); + break; + case Sky: + skyList.add(g); + break; + case Transparent: + transparentList.add(g); + break; + case Translucent: + translucentList.add(g); + break; + default: + throw new UnsupportedOperationException("Unknown bucket type: " + bucket); + } + } + + /** + * + * @param shadBucket The shadow mode to retrieve the {@link GeometryList + * queue content} for. Only {@link ShadowMode#Cast Cast} and + * {@link ShadowMode#Receive Receive} are valid. + * @return The cast or receive {@link GeometryList} + */ + public GeometryList getShadowQueueContent(ShadowMode shadBucket) { + switch (shadBucket) { + case Cast: + return shadowCast; + case Receive: + return shadowRecv; + default: + throw new IllegalArgumentException("Only Cast or Receive are allowed"); + } + } + + private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean clear) { + list.setCamera(cam); // select camera for sorting + list.sort(); + for (int i = 0; i < list.size(); i++) { + Geometry obj = list.get(i); + assert obj != null; + rm.renderGeometry(obj); + obj.queueDistance = Float.NEGATIVE_INFINITY; + } + if (clear) { + list.clear(); + } + } + + public void renderShadowQueue(GeometryList list, RenderManager rm, Camera cam, boolean clear) { + renderGeometryList(list, rm, cam, clear); + } + + public void renderShadowQueue(ShadowMode shadBucket, RenderManager rm, Camera cam, boolean clear) { + switch (shadBucket) { + case Cast: + renderGeometryList(shadowCast, rm, cam, clear); + break; + case Receive: + renderGeometryList(shadowRecv, rm, cam, clear); + break; + default: + throw new IllegalArgumentException("Unexpected shadow bucket: " + shadBucket); + } + } + + public boolean isQueueEmpty(Bucket bucket) { + switch (bucket) { + case Gui: + return guiList.size() == 0; + case Opaque: + return opaqueList.size() == 0; + case Sky: + return skyList.size() == 0; + case Transparent: + return transparentList.size() == 0; + case Translucent: + return translucentList.size() == 0; + default: + throw new UnsupportedOperationException("Unsupported bucket type: " + bucket); + } + } + + public void renderQueue(Bucket bucket, RenderManager rm, Camera cam) { + renderQueue(bucket, rm, cam, true); + } + + public void renderQueue(Bucket bucket, RenderManager rm, Camera cam, boolean clear) { + switch (bucket) { + case Gui: + renderGeometryList(guiList, rm, cam, clear); + break; + case Opaque: + renderGeometryList(opaqueList, rm, cam, clear); + break; + case Sky: + renderGeometryList(skyList, rm, cam, clear); + break; + case Transparent: + renderGeometryList(transparentList, rm, cam, clear); + break; + case Translucent: + renderGeometryList(translucentList, rm, cam, clear); + break; + + default: + throw new UnsupportedOperationException("Unsupported bucket type: " + bucket); + } + } + + public void clear() { + opaqueList.clear(); + guiList.clear(); + transparentList.clear(); + translucentList.clear(); + skyList.clear(); + shadowCast.clear(); + shadowRecv.clear(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/TransparentComparator.java b/jme3-core/src/main/java/com/jme3/renderer/queue/TransparentComparator.java new file mode 100644 index 000000000..c5c3fc79f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/TransparentComparator.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.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/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java new file mode 100644 index 000000000..bf376006e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetManager; +import com.jme3.asset.ModelKey; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.util.SafeArrayList; +import java.io.IOException; +import java.util.*; +import java.util.Map.Entry; +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, "Cannot locate {0} for asset link node {1}", + new Object[]{ modelKey, key }); + } + } + } + + @Override + public void write(JmeExporter e) throws IOException { + SafeArrayList childs = children; + children = new SafeArrayList(Spatial.class); + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.writeSavableArrayList(assetLoaderKeys, "assetLoaderKeyList", null); + children = childs; + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java new file mode 100644 index 000000000..656c8f8c2 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -0,0 +1,757 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import com.jme3.export.*; +import com.jme3.material.Material; +import com.jme3.math.Matrix4f; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.SafeArrayList; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * BatchNode holds geometries that are a batched version of all the geometries that are in its sub scenegraph. + * There is one geometry per different material in the sub tree. + * The geometries are directly attached to the node in the scene graph. + * Usage is like any other node except you have to call the {@link #batch()} method once all the geometries have been attached to the sub scene graph and their material set + * (see todo more automagic for further enhancements) + * All the geometries that have been batched are set to {@link CullHint#Always} to not render them. + * The sub geometries can be transformed as usual, their transforms are used to update the mesh of the geometryBatch. + * Sub geoms can be removed but it may be slower than the normal spatial removing + * Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries. + * To integrate them in the batch you have to call the batch() method again on the batchNode. + * + * TODO normal or tangents or both looks a bit weird + * TODO more automagic (batch when needed in the updateLogicalState) + * @author Nehon + */ +public class BatchNode extends Node implements Savable { + + private static final Logger logger = Logger.getLogger(BatchNode.class.getName()); + /** + * the list of geometry holding the batched meshes + */ + protected SafeArrayList batches = new SafeArrayList(Batch.class); + /** + * a map storing he batches by geometry to quickly acces the batch when updating + */ + protected Map batchesByGeom = new HashMap(); + /** + * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer + */ + private float[] tmpFloat; + private float[] tmpFloatN; + private float[] tmpFloatT; + int maxVertCount = 0; + boolean useTangents = false; + boolean needsFullRebatch = true; + + /** + * Construct a batchNode + */ + public BatchNode() { + super(); + } + + public BatchNode(String name) { + super(name); + } + + @Override + public void updateGeometricState() { + if ((refreshFlags & RF_LIGHTLIST) != 0) { + updateWorldLightList(); + } + + if ((refreshFlags & RF_TRANSFORM) != 0) { + // combine with parent transforms- same for all spatial + // subclasses. + updateWorldTransforms(); + } + + if (!children.isEmpty()) { + // the important part- make sure child geometric state is refreshed + // first before updating own world bound. This saves + // a round-trip later on. + // NOTE 9/19/09 + // Although it does save a round trip, + + for (Spatial child : children.getArray()) { + child.updateGeometricState(); + } + + for (Batch batch : batches.getArray()) { + if (batch.needMeshUpdate) { + batch.geometry.updateModelBound(); + batch.geometry.updateWorldBound(); + batch.needMeshUpdate = false; + + } + } + + + } + + if ((refreshFlags & RF_BOUND) != 0) { + updateWorldBound(); + } + + assert refreshFlags == 0; + } + + protected Matrix4f getTransformMatrix(Geometry g){ + return g.cachedWorldMat; + } + + protected void updateSubBatch(Geometry bg) { + Batch batch = batchesByGeom.get(bg); + if (batch != null) { + Mesh mesh = batch.geometry.getMesh(); + Mesh origMesh = bg.getMesh(); + + VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position); + FloatBuffer posBuf = (FloatBuffer) pvb.getData(); + VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal); + FloatBuffer normBuf = (FloatBuffer) nvb.getData(); + + VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position); + FloatBuffer oposBuf = (FloatBuffer) opvb.getData(); + VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal); + FloatBuffer onormBuf = (FloatBuffer) onvb.getData(); + Matrix4f transformMat = getTransformMatrix(bg); + + if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) { + + VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent); + FloatBuffer tanBuf = (FloatBuffer) tvb.getData(); + VertexBuffer otvb = origMesh.getBuffer(VertexBuffer.Type.Tangent); + FloatBuffer otanBuf = (FloatBuffer) otvb.getData(); + doTransformsTangents(oposBuf, onormBuf, otanBuf, posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), transformMat); + tvb.updateData(tanBuf); + } else { + doTransforms(oposBuf, onormBuf, posBuf, normBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), transformMat); + } + pvb.updateData(posBuf); + nvb.updateData(normBuf); + + + batch.needMeshUpdate = true; + } + } + + /** + * Batch this batchNode + * every geometry of the sub scene graph of this node will be batched into a single mesh that will be rendered in one call + */ + public void batch() { + doBatch(); + //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice + for (Batch batch : batches.getArray()) { + batch.geometry.setIgnoreTransform(true); + batch.geometry.setUserData(UserData.JME_PHYSICSIGNORE, true); + } + updateGeometricState(); + } + + protected void doBatch() { + Map> matMap = new HashMap>(); + int nbGeoms = 0; + + gatherGeomerties(matMap, this, needsFullRebatch); + if (needsFullRebatch) { + for (Batch batch : batches.getArray()) { + batch.geometry.removeFromParent(); + } + batches.clear(); + batchesByGeom.clear(); + } + //only reset maxVertCount if there is something new to batch + if (matMap.size() > 0) { + maxVertCount = 0; + } + + for (Map.Entry> entry : matMap.entrySet()) { + Mesh m = new Mesh(); + Material material = entry.getKey(); + List list = entry.getValue(); + nbGeoms += list.size(); + String batchName = name + "-batch" + batches.size(); + Batch batch; + if (!needsFullRebatch) { + batch = findBatchByMaterial(material); + if (batch != null) { + list.add(0, batch.geometry); + batchName = batch.geometry.getName(); + batch.geometry.removeFromParent(); + } else { + batch = new Batch(); + } + } else { + batch = new Batch(); + } + mergeGeometries(m, list); + m.setDynamic(); + + batch.updateGeomList(list); + + batch.geometry = new Geometry(batchName); + batch.geometry.setMaterial(material); + this.attachChild(batch.geometry); + + + batch.geometry.setMesh(m); + batch.geometry.getMesh().updateCounts(); + batch.geometry.getMesh().updateBound(); + batches.add(batch); + } + if (batches.size() > 0) { + needsFullRebatch = false; + } + + + logger.log(Level.FINE, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()}); + + //init the temp arrays if something has been batched only. + if(matMap.size()>0){ + //TODO these arrays should be allocated by chunk instead to avoid recreating them each time the batch is changed. + //init temp float arrays + tmpFloat = new float[maxVertCount * 3]; + tmpFloatN = new float[maxVertCount * 3]; + if (useTangents) { + tmpFloatT = new float[maxVertCount * 4]; + } + } + } + + //in case the detached spatial is a node, we unbatch all geometries in its subegraph + @Override + public Spatial detachChildAt(int index) { + Spatial s = super.detachChildAt(index); + if (s instanceof Node) { + unbatchSubGraph(s); + } + return s; + } + + /** + * recursively visit the subgraph and unbatch geometries + * @param s + */ + private void unbatchSubGraph(Spatial s) { + if (s instanceof Node) { + for (Spatial sp : ((Node) s).getChildren()) { + unbatchSubGraph(sp); + } + } else if (s instanceof Geometry) { + Geometry g = (Geometry) s; + if (g.isBatched()) { + g.unBatch(); + } + } + } + + + private void gatherGeomerties(Map> map, Spatial n, boolean rebatch) { + + if (n instanceof Geometry) { + + if (!isBatch(n) && n.getBatchHint() != BatchHint.Never) { + Geometry g = (Geometry) n; + if (!g.isBatched() || rebatch) { + if (g.getMaterial() == null) { + throw new IllegalStateException("No material is set for Geometry: " + g.getName() + " please set a material before batching"); + } + List list = map.get(g.getMaterial()); + if (list == null) { + //trying to compare materials with the isEqual method + for (Map.Entry> mat : map.entrySet()) { + if (g.getMaterial().contentEquals(mat.getKey())) { + list = mat.getValue(); + } + } + } + if (list == null) { + list = new ArrayList(); + map.put(g.getMaterial(), list); + } + g.setTransformRefresh(); + list.add(g); + } + } + + } else if (n instanceof Node) { + for (Spatial child : ((Node) n).getChildren()) { + if (child instanceof BatchNode) { + continue; + } + gatherGeomerties(map, child, rebatch); + } + } + + } + + private Batch findBatchByMaterial(Material m) { + for (Batch batch : batches.getArray()) { + if (batch.geometry.getMaterial().contentEquals(m)) { + return batch; + } + } + return null; + } + + private boolean isBatch(Spatial s) { + for (Batch batch : batches.getArray()) { + if (batch.geometry == s) { + return true; + } + } + return false; + } + + /** + * Sets the material to the all the batches of this BatchNode + * use setMaterial(Material material,int batchIndex) to set a material to a specific batch + * + * @param material the material to use for this geometry + */ + @Override + public void setMaterial(Material material) { +// for (Batch batch : batches.values()) { +// batch.geometry.setMaterial(material); +// } + throw new UnsupportedOperationException("Unsupported for now, please set the material on the geoms before batching"); + } + + /** + * Returns the material that is used for the first batch of this BatchNode + * + * use getMaterial(Material material,int batchIndex) to get a material from a specific batch + * + * @return the material that is used for the first batch of this BatchNode + * + * @see #setMaterial(com.jme3.material.Material) + */ + public Material getMaterial() { + if (!batches.isEmpty()) { + Batch b = batches.iterator().next(); + return b.geometry.getMaterial(); + } + return null;//material; + } + +// /** +// * Sets the material to the a specific batch of this BatchNode +// * +// * +// * @param material the material to use for this geometry +// */ +// public void setMaterial(Material material,int batchIndex) { +// if (!batches.isEmpty()) { +// +// } +// +// } +// +// /** +// * Returns the material that is used for the first batch of this BatchNode +// * +// * use getMaterial(Material material,int batchIndex) to get a material from a specific batch +// * +// * @return the material that is used for the first batch of this BatchNode +// * +// * @see #setMaterial(com.jme3.material.Material) +// */ +// public Material getMaterial(int batchIndex) { +// if (!batches.isEmpty()) { +// Batch b = batches.get(batches.keySet().iterator().next()); +// return b.geometry.getMaterial(); +// } +// return null;//material; +// } + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); +// +// if (material != null) { +// oc.write(material.getAssetName(), "materialName", null); +// } +// oc.write(material, "material", null); + + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + + +// material = null; +// String matName = ic.readString("materialName", null); +// if (matName != null) { +// // Material name is set, +// // Attempt to load material via J3M +// try { +// material = im.getAssetManager().loadMaterial(matName); +// } catch (AssetNotFoundException ex) { +// // Cannot find J3M file. +// logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.", +// matName); +// } +// } +// // If material is NULL, try to load it from the geometry +// if (material == null) { +// material = (Material) ic.readSavable("material", null); +// } + + } + + /** + * Merges all geometries in the collection into + * the output mesh. Does not take into account materials. + * + * @param geometries + * @param outMesh + */ + private void mergeGeometries(Mesh outMesh, List geometries) { + int[] compsForBuf = new int[VertexBuffer.Type.values().length]; + VertexBuffer.Format[] formatForBuf = new VertexBuffer.Format[compsForBuf.length]; + + int totalVerts = 0; + int totalTris = 0; + int totalLodLevels = 0; + int maxWeights = -1; + + Mesh.Mode mode = null; + for (Geometry geom : geometries) { + totalVerts += geom.getVertexCount(); + totalTris += geom.getTriangleCount(); + totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels()); + if (maxVertCount < geom.getVertexCount()) { + maxVertCount = geom.getVertexCount(); + } + Mesh.Mode listMode; + int components; + switch (geom.getMesh().getMode()) { + case Points: + listMode = Mesh.Mode.Points; + components = 1; + break; + case LineLoop: + case LineStrip: + case Lines: + listMode = Mesh.Mode.Lines; + components = 2; + break; + case TriangleFan: + case TriangleStrip: + case Triangles: + listMode = Mesh.Mode.Triangles; + components = 3; + break; + default: + throw new UnsupportedOperationException(); + } + + for (VertexBuffer vb : geom.getMesh().getBufferList().getArray()) { + compsForBuf[vb.getBufferType().ordinal()] = vb.getNumComponents(); + formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat(); + } + + maxWeights = Math.max(maxWeights, geom.getMesh().getMaxNumWeights()); + + if (mode != null && mode != listMode) { + throw new UnsupportedOperationException("Cannot combine different" + + " primitive types: " + mode + " != " + listMode); + } + mode = listMode; + compsForBuf[VertexBuffer.Type.Index.ordinal()] = components; + } + + outMesh.setMaxNumWeights(maxWeights); + outMesh.setMode(mode); + if (totalVerts >= 65536) { + // make sure we create an UnsignedInt buffer so + // we can fit all of the meshes + formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt; + } else { + formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort; + } + + // generate output buffers based on retrieved info + for (int i = 0; i < compsForBuf.length; i++) { + if (compsForBuf[i] == 0) { + continue; + } + + Buffer data; + if (i == VertexBuffer.Type.Index.ordinal()) { + data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris); + } else { + data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts); + } + + VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.values()[i]); + vb.setupData(VertexBuffer.Usage.Dynamic, compsForBuf[i], formatForBuf[i], data); + outMesh.setBuffer(vb); + } + + int globalVertIndex = 0; + int globalTriIndex = 0; + + for (Geometry geom : geometries) { + Mesh inMesh = geom.getMesh(); + if (!isBatch(geom)) { + geom.batch(this, globalVertIndex); + } + + int geomVertCount = inMesh.getVertexCount(); + int geomTriCount = inMesh.getTriangleCount(); + + for (int bufType = 0; bufType < compsForBuf.length; bufType++) { + VertexBuffer inBuf = inMesh.getBuffer(VertexBuffer.Type.values()[bufType]); + + VertexBuffer outBuf = outMesh.getBuffer(VertexBuffer.Type.values()[bufType]); + + if (outBuf == null) { + continue; + } + + if (VertexBuffer.Type.Index.ordinal() == bufType) { + int components = compsForBuf[bufType]; + + IndexBuffer inIdx = inMesh.getIndicesAsList(); + IndexBuffer outIdx = outMesh.getIndexBuffer(); + + for (int tri = 0; tri < geomTriCount; tri++) { + for (int comp = 0; comp < components; comp++) { + int idx = inIdx.get(tri * components + comp) + globalVertIndex; + outIdx.put((globalTriIndex + tri) * components + comp, idx); + } + } + } else if (VertexBuffer.Type.Position.ordinal() == bufType) { + FloatBuffer inPos = (FloatBuffer) inBuf.getData(); + FloatBuffer outPos = (FloatBuffer) outBuf.getData(); + doCopyBuffer(inPos, globalVertIndex, outPos, 3); + } else if (VertexBuffer.Type.Normal.ordinal() == bufType || VertexBuffer.Type.Tangent.ordinal() == bufType) { + FloatBuffer inPos = (FloatBuffer) inBuf.getData(); + FloatBuffer outPos = (FloatBuffer) outBuf.getData(); + doCopyBuffer(inPos, globalVertIndex, outPos, compsForBuf[bufType]); + if (VertexBuffer.Type.Tangent.ordinal() == bufType) { + useTangents = true; + } + } else { + inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount); +// for (int vert = 0; vert < geomVertCount; vert++) { +// int curGlobalVertIndex = globalVertIndex + vert; +// inBuf.copyElement(vert, outBuf, curGlobalVertIndex); +// } + } + } + + globalVertIndex += geomVertCount; + globalTriIndex += geomTriCount; + } + } + + private void doTransforms(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, FloatBuffer bufPos, FloatBuffer bufNorm, int start, int end, Matrix4f transform) { + TempVars vars = TempVars.get(); + Vector3f pos = vars.vect1; + Vector3f norm = vars.vect2; + + int length = (end - start) * 3; + + // offset is given in element units + // convert to be in component units + int offset = start * 3; + bindBufPos.rewind(); + bindBufNorm.rewind(); + //bufPos.position(offset); + //bufNorm.position(offset); + bindBufPos.get(tmpFloat, 0, length); + bindBufNorm.get(tmpFloatN, 0, length); + int index = 0; + while (index < length) { + pos.x = tmpFloat[index]; + norm.x = tmpFloatN[index++]; + pos.y = tmpFloat[index]; + norm.y = tmpFloatN[index++]; + pos.z = tmpFloat[index]; + norm.z = tmpFloatN[index]; + + transform.mult(pos, pos); + transform.multNormal(norm, norm); + + index -= 2; + tmpFloat[index] = pos.x; + tmpFloatN[index++] = norm.x; + tmpFloat[index] = pos.y; + tmpFloatN[index++] = norm.y; + tmpFloat[index] = pos.z; + tmpFloatN[index++] = norm.z; + + } + vars.release(); + bufPos.position(offset); + //using bulk put as it's faster + bufPos.put(tmpFloat, 0, length); + bufNorm.position(offset); + //using bulk put as it's faster + bufNorm.put(tmpFloatN, 0, length); + } + + private void doTransformsTangents(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, FloatBuffer bindBufTangents,FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform) { + TempVars vars = TempVars.get(); + Vector3f pos = vars.vect1; + Vector3f norm = vars.vect2; + Vector3f tan = vars.vect3; + + int length = (end - start) * 3; + int tanLength = (end - start) * 4; + + // offset is given in element units + // convert to be in component units + int offset = start * 3; + int tanOffset = start * 4; + + + bindBufPos.rewind(); + bindBufNorm.rewind(); + bindBufTangents.rewind(); + bindBufPos.get(tmpFloat, 0, length); + bindBufNorm.get(tmpFloatN, 0, length); + bindBufTangents.get(tmpFloatT, 0, tanLength); + + int index = 0; + int tanIndex = 0; + while (index < length) { + pos.x = tmpFloat[index]; + norm.x = tmpFloatN[index++]; + pos.y = tmpFloat[index]; + norm.y = tmpFloatN[index++]; + pos.z = tmpFloat[index]; + norm.z = tmpFloatN[index]; + + tan.x = tmpFloatT[tanIndex++]; + tan.y = tmpFloatT[tanIndex++]; + tan.z = tmpFloatT[tanIndex++]; + + transform.mult(pos, pos); + transform.multNormal(norm, norm); + transform.multNormal(tan, tan); + + index -= 2; + tanIndex -= 3; + + tmpFloat[index] = pos.x; + tmpFloatN[index++] = norm.x; + tmpFloat[index] = pos.y; + tmpFloatN[index++] = norm.y; + tmpFloat[index] = pos.z; + tmpFloatN[index++] = norm.z; + + tmpFloatT[tanIndex++] = tan.x; + tmpFloatT[tanIndex++] = tan.y; + tmpFloatT[tanIndex++] = tan.z; + + //Skipping 4th element of tangent buffer (handedness) + tanIndex++; + + } + vars.release(); + bufPos.position(offset); + //using bulk put as it's faster + bufPos.put(tmpFloat, 0, length); + bufNorm.position(offset); + //using bulk put as it's faster + bufNorm.put(tmpFloatN, 0, length); + bufTangents.position(tanOffset); + //using bulk put as it's faster + bufTangents.put(tmpFloatT, 0, tanLength); + } + + private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) { + TempVars vars = TempVars.get(); + Vector3f pos = vars.vect1; + + // offset is given in element units + // convert to be in component units + offset *= componentSize; + + for (int i = 0; i < inBuf.limit() / componentSize; i++) { + pos.x = inBuf.get(i * componentSize + 0); + pos.y = inBuf.get(i * componentSize + 1); + pos.z = inBuf.get(i * componentSize + 2); + + outBuf.put(offset + i * componentSize + 0, pos.x); + outBuf.put(offset + i * componentSize + 1, pos.y); + outBuf.put(offset + i * componentSize + 2, pos.z); + } + vars.release(); + } + + protected class Batch { + + /** + * update the batchesByGeom map for this batch with the given List of geometries + * @param list + */ + void updateGeomList(List list) { + for (Geometry geom : list) { + if (!isBatch(geom)) { + batchesByGeom.put(geom, this); + } + } + } + Geometry geometry; + boolean needMeshUpdate = false; + } + + protected void setNeedsFullRebatch(boolean needsFullRebatch) { + this.needsFullRebatch = needsFullRebatch; + } + + public int getOffsetIndex(Geometry batchedGeometry) { + return batchedGeometry.startIndex; + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/CameraNode.java b/jme3-core/src/main/java/com/jme3/scene/CameraNode.java new file mode 100644 index 000000000..36cde482c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/CameraNode.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.renderer.Camera; +import com.jme3.scene.control.CameraControl; +import com.jme3.scene.control.CameraControl.ControlDirection; +import java.io.IOException; + +/** + * CameraNode simply uses {@link CameraControl} to implement + * linking of camera and node data. + * + * @author Tim8Dev + */ +public class CameraNode extends Node { + + private CameraControl camControl; + + /** + * Serialization only. Do not use. + */ + public CameraNode() { + } + + public CameraNode(String name, Camera camera) { + this(name, new CameraControl(camera)); + } + + public CameraNode(String name, CameraControl control) { + super(name); + addControl(control); + camControl = control; + } + + public void setEnabled(boolean enabled) { + camControl.setEnabled(enabled); + } + + public boolean isEnabled() { + return camControl.isEnabled(); + } + + public void setControlDir(ControlDirection controlDir) { + camControl.setControlDir(controlDir); + } + + public void setCamera(Camera camera) { + camControl.setCamera(camera); + } + + public ControlDirection getControlDir() { + return camControl.getControlDir(); + } + + public Camera getCamera() { + return camControl.getCamera(); + } + +// @Override +// public void lookAt(Vector3f position, Vector3f upVector) { +// this.lookAt(position, upVector); +// camControl.getCamera().lookAt(position, upVector); +// } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + camControl = (CameraControl)im.getCapsule(this).readSavable("camControl", null); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + ex.getCapsule(this).write(camControl, "camControl", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/CollisionData.java b/jme3-core/src/main/java/com/jme3/scene/CollisionData.java new file mode 100644 index 000000000..50309d5ea --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/CollisionData.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import com.jme3.bounding.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 with bounding volumes and rays. + * + * @author Kirill Vainer + */ +public interface CollisionData extends Savable, Cloneable { + public int collideWith(Collidable other, + Matrix4f worldMatrix, + BoundingVolume worldBound, + CollisionResults results); +} diff --git a/jme3-core/src/main/java/com/jme3/scene/Geometry.java b/jme3-core/src/main/java/com/jme3/scene/Geometry.java new file mode 100644 index 000000000..7c381c15e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -0,0 +1,530 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import com.jme3.asset.AssetNotFoundException; +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.math.Matrix4f; +import com.jme3.math.Transform; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.Queue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Geometry defines a leaf node of the scene graph. The leaf node + * contains the geometric data for rendering objects. It manages all rendering + * information such as a {@link Material} object to define how the surface + * should be shaded and the {@link Mesh} data to contain the actual geometry. + * + * @author Kirill Vainer + */ +public class Geometry extends Spatial { + + // Version #1: removed shared meshes. + // models loaded with shared mesh will be automatically fixed. + public static final int SAVABLE_VERSION = 1; + private static final Logger logger = Logger.getLogger(Geometry.class.getName()); + protected Mesh mesh; + protected transient int lodLevel = 0; + protected Material material; + /** + * When true, the geometry's transform will not be applied. + */ + protected boolean ignoreTransform = false; + protected transient Matrix4f cachedWorldMat = new Matrix4f(); + /** + * used when geometry is batched + */ + protected BatchNode batchNode = null; + /** + * the start index of this geom's mesh in the batchNode mesh + */ + protected int startIndex; + /** + * Serialization only. Do not use. + */ + public Geometry() { + } + + /** + * Create a geometry node without any mesh data. + * Both the mesh and the material are null, the geometry + * cannot be rendered until those are set. + * + * @param name The name of this geometry + */ + public Geometry(String name) { + super(name); + } + + /** + * Create a geometry node with mesh data. + * The material of the geometry is null, it cannot + * be rendered until it is set. + * + * @param name The name of this geometry + * @param mesh The mesh data for this geometry + */ + public Geometry(String name, Mesh mesh) { + this(name); + if (mesh == null) { + throw new NullPointerException(); + } + + this.mesh = mesh; + } + + /** + * @return If ignoreTransform mode is set. + * + * @see Geometry#setIgnoreTransform(boolean) + */ + public boolean isIgnoreTransform() { + return ignoreTransform; + } + + /** + * @param ignoreTransform If true, the geometry's transform will not be applied. + */ + public void setIgnoreTransform(boolean ignoreTransform) { + this.ignoreTransform = ignoreTransform; + } + + /** + * Sets the LOD level to use when rendering the mesh of this geometry. + * Level 0 indicates that the default index buffer should be used, + * levels [1, LodLevels + 1] represent the levels set on the mesh + * with {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }. + * + * @param lod The lod level to set + */ + @Override + public void setLodLevel(int lod) { + if (mesh.getNumLodLevels() == 0) { + throw new IllegalStateException("LOD levels are not set on this mesh"); + } + + if (lod < 0 || lod >= mesh.getNumLodLevels()) { + throw new IllegalArgumentException("LOD level is out of range: " + lod); + } + + lodLevel = lod; + } + + /** + * Returns the LOD level set with {@link #setLodLevel(int) }. + * + * @return the LOD level set + */ + public int getLodLevel() { + return lodLevel; + } + + /** + * Returns this geometry's mesh vertex count. + * + * @return this geometry's mesh vertex count. + * + * @see Mesh#getVertexCount() + */ + public int getVertexCount() { + return mesh.getVertexCount(); + } + + /** + * Returns this geometry's mesh triangle count. + * + * @return this geometry's mesh triangle count. + * + * @see Mesh#getTriangleCount() + */ + public int getTriangleCount() { + return mesh.getTriangleCount(); + } + + /** + * Sets the mesh to use for this geometry when rendering. + * + * @param mesh the mesh to use for this geometry + * + * @throws IllegalArgumentException If mesh is null + */ + public void setMesh(Mesh mesh) { + if (mesh == null) { + throw new IllegalArgumentException(); + } + if (isBatched()) { + throw new UnsupportedOperationException("Cannot set the mesh of a batched geometry"); + } + + this.mesh = mesh; + setBoundRefresh(); + } + + /** + * Returns the mseh to use for this geometry + * + * @return the mseh to use for this geometry + * + * @see #setMesh(com.jme3.scene.Mesh) + */ + public Mesh getMesh() { + return mesh; + } + + /** + * Sets the material to use for this geometry. + * + * @param material the material to use for this geometry + */ + @Override + public void setMaterial(Material material) { + if (isBatched()) { + throw new UnsupportedOperationException("Cannot set the material of a batched geometry, change the material of the parent BatchNode."); + } + this.material = material; + } + + /** + * Returns the material that is used for this geometry. + * + * @return the material that is used for this geometry + * + * @see #setMaterial(com.jme3.material.Material) + */ + public Material getMaterial() { + return material; + } + + /** + * @return The bounding volume of the mesh, in model space. + */ + public BoundingVolume getModelBound() { + return mesh.getBound(); + } + + /** + * Updates the bounding volume of the mesh. Should be called when the + * mesh has been modified. + */ + public void updateModelBound() { + mesh.updateBound(); + setBoundRefresh(); + } + + /** + * 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 Spatial#updateWorldBound() + */ + @Override + protected void updateWorldBound() { + super.updateWorldBound(); + if (mesh == null) { + throw new NullPointerException("Geometry: " + getName() + " has null mesh"); + } + + if (mesh.getBound() != null) { + if (ignoreTransform) { + // we do not transform the model bound by the world transform, + // just use the model bound as-is + worldBound = mesh.getBound().clone(worldBound); + } else { + worldBound = mesh.getBound().transform(worldTransform, worldBound); + } + } + } + + @Override + protected void updateWorldTransforms() { + + super.updateWorldTransforms(); + computeWorldMatrix(); + + if (isBatched()) { + batchNode.updateSubBatch(this); + } + // geometry requires lights to be sorted + worldLights.sort(true); + } + + /** + * Batch this geometry, should only be called by the BatchNode. + * @param node the batchNode + * @param startIndex the starting index of this geometry in the batched mesh + */ + protected void batch(BatchNode node, int startIndex) { + this.batchNode = node; + this.startIndex = startIndex; + setCullHint(CullHint.Always); + } + + /** + * unBatch this geometry. + */ + protected void unBatch() { + this.startIndex = 0; + //once the geometry is removed from the screnegraph the batchNode needs to be rebatched. + if (batchNode != null) { + this.batchNode.setNeedsFullRebatch(true); + this.batchNode = null; + } + setCullHint(CullHint.Dynamic); + } + + @Override + public boolean removeFromParent() { + return super.removeFromParent(); + } + + @Override + protected void setParent(Node parent) { + super.setParent(parent); + //if the geometry is batched we also have to unbatch it + if (parent == null && isBatched()) { + unBatch(); + } + } + + + /** + * Indicate that the transform of this spatial has changed and that + * a refresh is required. + */ + // NOTE: Spatial has an identical implementation of this method, + // thus it was commented out. +// @Override +// protected void setTransformRefresh() { +// refreshFlags |= RF_TRANSFORM; +// setBoundRefresh(); +// } + /** + * Recomputes the matrix returned by {@link Geometry#getWorldMatrix() }. + * This will require a localized transform update for this geometry. + */ + public void computeWorldMatrix() { + // Force a local update of the geometry's transform + checkDoTransformUpdate(); + + // Compute the cached world matrix + cachedWorldMat.loadIdentity(); + cachedWorldMat.setRotationQuaternion(worldTransform.getRotation()); + cachedWorldMat.setTranslation(worldTransform.getTranslation()); + + TempVars vars = TempVars.get(); + Matrix4f scaleMat = vars.tempMat4; + scaleMat.loadIdentity(); + scaleMat.scale(worldTransform.getScale()); + cachedWorldMat.multLocal(scaleMat); + vars.release(); + } + + /** + * A {@link Matrix4f matrix} that transforms the {@link Geometry#getMesh() mesh} + * from model space to world space. This matrix is computed based on the + * {@link Geometry#getWorldTransform() world transform} of this geometry. + * In order to receive updated values, you must call {@link Geometry#computeWorldMatrix() } + * before using this method. + * + * @return Matrix to transform from local space to world space + */ + public Matrix4f getWorldMatrix() { + return cachedWorldMat; + } + + /** + * Sets the model bound to use for this geometry. + * This alters the bound used on the mesh as well via + * {@link Mesh#setBound(com.jme3.bounding.BoundingVolume) } and + * forces the world bounding volume to be recomputed. + * + * @param modelBound The model bound to set + */ + @Override + public void setModelBound(BoundingVolume modelBound) { + this.worldBound = null; + mesh.setBound(modelBound); + setBoundRefresh(); + + // NOTE: Calling updateModelBound() would cause the mesh + // to recompute the bound based on the geometry thus making + // this call useless! + //updateModelBound(); + } + + public int collideWith(Collidable other, CollisionResults results) { + // Force bound to update + checkDoBoundUpdate(); + // Update transform, and compute cached world matrix + computeWorldMatrix(); + + assert (refreshFlags & (RF_BOUND | RF_TRANSFORM)) == 0; + + if (mesh != null) { + // NOTE: BIHTree in mesh already checks collision with the + // mesh's bound + int prevSize = results.size(); + int added = mesh.collideWith(other, cachedWorldMat, worldBound, results); + int newSize = results.size(); + for (int i = prevSize; i < newSize; i++) { + results.getCollisionDirect(i).setGeometry(this); + } + return added; + } + return 0; + } + + @Override + public void depthFirstTraversal(SceneGraphVisitor visitor) { + visitor.visit(this); + } + + @Override + protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue queue) { + } + + public boolean isBatched() { + return batchNode != null; + } + + /** + * This version of clone is a shallow clone, in other words, the + * same mesh is referenced as the original geometry. + * Exception: if the mesh is marked as being a software + * animated mesh, (bind pose is set) then the positions + * and normals are deep copied. + */ + @Override + public Geometry clone(boolean cloneMaterial) { + Geometry geomClone = (Geometry) super.clone(cloneMaterial); + //this geometry is batched but the clonned one should not be + if (isBatched()) { + geomClone.batchNode = null; + geomClone.unBatch(); + } + geomClone.cachedWorldMat = cachedWorldMat.clone(); + if (material != null) { + if (cloneMaterial) { + geomClone.material = material.clone(); + } else { + geomClone.material = material; + } + } + + if (mesh != null && mesh.getBuffer(Type.BindPosePosition) != null) { + geomClone.mesh = mesh.cloneForAnim(); + } + + return geomClone; + } + + /** + * This version of clone is a shallow clone, in other words, the + * same mesh is referenced as the original geometry. + * Exception: if the mesh is marked as being a software + * animated mesh, (bind pose is set) then the positions + * and normals are deep copied. + */ + @Override + public Geometry clone() { + return clone(true); + } + + /** + * Creates a deep clone of the geometry, + * this creates an identical copy of the mesh + * with the vertexbuffer data duplicated. + */ + @Override + public Spatial deepClone() { + Geometry geomClone = clone(true); + geomClone.mesh = mesh.deepClone(); + return geomClone; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(mesh, "mesh", null); + if (material != null) { + oc.write(material.getAssetName(), "materialName", null); + } + oc.write(material, "material", null); + oc.write(ignoreTransform, "ignoreTransform", false); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + mesh = (Mesh) ic.readSavable("mesh", null); + + material = null; + String matName = ic.readString("materialName", null); + if (matName != null) { + // Material name is set, + // Attempt to load material via J3M + try { + material = im.getAssetManager().loadMaterial(matName); + } catch (AssetNotFoundException ex) { + // Cannot find J3M file. + logger.log(Level.FINE, "Cannot locate {0} for geometry {1}", new Object[]{matName, key}); + } + } + // If material is NULL, try to load it from the geometry + if (material == null) { + material = (Material) ic.readSavable("material", null); + } + ignoreTransform = ic.readBoolean("ignoreTransform", false); + + if (ic.getSavableVersion(Geometry.class) == 0) { + // Fix shared mesh (if set) + Mesh sharedMesh = getUserData(UserData.JME_SHAREDMESH); + if (sharedMesh != null) { + getMesh().extractVertexData(sharedMesh); + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/LightNode.java b/jme3-core/src/main/java/com/jme3/scene/LightNode.java new file mode 100644 index 000000000..fea63bf57 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/LightNode.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.light.Light; +import com.jme3.scene.control.LightControl; +import com.jme3.scene.control.LightControl.ControlDirection; +import java.io.IOException; + +/** + * LightNode is used to link together a {@link Light} object + * with a {@link Node} object. + * + * @author Tim8Dev + */ +public class LightNode extends Node { + + private LightControl lightControl; + + /** + * Serialization only. Do not use. + */ + public LightNode() { + } + + public LightNode(String name, Light light) { + this(name, new LightControl(light)); + } + + public LightNode(String name, LightControl control) { + super(name); + addControl(control); + lightControl = control; + } + + /** + * Enable or disable the LightNode functionality. + * + * @param enabled If false, the functionality of LightNode will + * be disabled. + */ + public void setEnabled(boolean enabled) { + lightControl.setEnabled(enabled); + } + + public boolean isEnabled() { + return lightControl.isEnabled(); + } + + public void setControlDir(ControlDirection controlDir) { + lightControl.setControlDir(controlDir); + } + + public void setLight(Light light) { + lightControl.setLight(light); + } + + public ControlDirection getControlDir() { + return lightControl.getControlDir(); + } + + public Light getLight() { + return lightControl.getLight(); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + lightControl = (LightControl)im.getCapsule(this).readSavable("lightControl", null); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + ex.getCapsule(this).write(lightControl, "lightControl", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/Mesh.java b/jme3-core/src/main/java/com/jme3/scene/Mesh.java new file mode 100644 index 000000000..b894450dc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java @@ -0,0 +1,1405 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; +import com.jme3.collision.bih.BIHTree; +import com.jme3.export.*; +import com.jme3.material.RenderState; +import com.jme3.math.Matrix4f; +import com.jme3.math.Triangle; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.mesh.*; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import com.jme3.util.SafeArrayList; +import java.io.IOException; +import java.nio.*; +import java.util.ArrayList; + +/** + * Mesh is used to store rendering data. + *

      + * All visible elements in a scene are represented by meshes. + * Meshes may contain three types of geometric primitives: + *

        + *
      • Points - Every vertex represents a single point in space, + * the size of each point is specified via {@link Mesh#setPointSize(float) }. + * Points can also be used for {@link RenderState#setPointSprite(boolean) point + * sprite} mode.
      • + *
      • Lines - 2 vertices represent a line segment, with the width specified + * via {@link Mesh#setLineWidth(float) }.
      • + *
      • Triangles - 3 vertices represent a solid triangle primitive.
      • + *
      + * + * @author Kirill Vainer + */ +public class Mesh implements Savable, Cloneable { + + /** + * The mode of the Mesh specifies both the type of primitive represented + * by the mesh and how the data should be interpreted. + */ + public enum Mode { + /** + * A primitive is a single point in space. The size of the points + * can be specified with {@link Mesh#setPointSize(float) }. + */ + Points(true), + + /** + * A primitive is a line segment. Every two vertices specify + * a single line. {@link Mesh#setLineWidth(float) } can be used + * to set the width of the lines. + */ + Lines(true), + + /** + * A primitive is a line segment. The first two vertices specify + * a single line, while subsequent vertices are combined with the + * previous vertex to make a line. {@link Mesh#setLineWidth(float) } can + * be used to set the width of the lines. + */ + LineStrip(false), + + /** + * Identical to {@link #LineStrip} except that at the end + * the last vertex is connected with the first to form a line. + * {@link Mesh#setLineWidth(float) } can be used + * to set the width of the lines. + */ + LineLoop(false), + + /** + * A primitive is a triangle. Each 3 vertices specify a single + * triangle. + */ + Triangles(true), + + /** + * Similar to {@link #Triangles}, the first 3 vertices + * specify a triangle, while subsequent vertices are combined with + * the previous two to form a triangle. + */ + TriangleStrip(false), + + /** + * Similar to {@link #Triangles}, the first 3 vertices + * specify a triangle, each 2 subsequent vertices are combined + * with the very first vertex to make a triangle. + */ + TriangleFan(false), + + /** + * A combination of various triangle modes. It is best to avoid + * using this mode as it may not be supported by all renderers. + * The {@link Mesh#setModeStart(int[]) mode start points} and + * {@link Mesh#setElementLengths(int[]) element lengths} must + * be specified for this mode. + */ + Hybrid(false); + + private boolean listMode = false; + + private Mode(boolean listMode){ + this.listMode = listMode; + } + + /** + * Returns true if the specified mode is a list mode (meaning + * ,it specifies the indices as a linear list and not some special + * format). + * Will return true for the types {@link #Points}, {@link #Lines} and + * {@link #Triangles}. + * + * @return true if the mode is a list type mode + */ + public boolean isListMode(){ + return listMode; + } + } + + /** + * The bounding volume that contains the mesh entirely. + * By default a BoundingBox (AABB). + */ + private BoundingVolume meshBound = new BoundingBox(); + + private CollisionData collisionTree = null; + + private SafeArrayList buffersList = new SafeArrayList(VertexBuffer.class); + 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; + + /** + * Creates a new mesh with no {@link VertexBuffer vertex buffers}. + */ + public Mesh(){ + } + + /** + * Create a shallow clone of this Mesh. The {@link VertexBuffer vertex + * buffers} are shared between this and the clone mesh, the rest + * of the data is cloned. + * + * @return A shallow clone of the mesh + */ + @Override + public Mesh clone() { + try { + Mesh clone = (Mesh) super.clone(); + clone.meshBound = meshBound.clone(); + clone.collisionTree = collisionTree != null ? collisionTree : null; + clone.buffers = buffers.clone(); + clone.buffersList = new SafeArrayList(VertexBuffer.class,buffersList); + clone.vertexArrayID = -1; + if (elementLengths != null) { + clone.elementLengths = elementLengths.clone(); + } + if (modeStart != null) { + clone.modeStart = modeStart.clone(); + } + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * Creates a deep clone of this mesh. + * The {@link VertexBuffer vertex buffers} and the data inside them + * is cloned. + * + * @return a deep clone of this mesh. + */ + public Mesh deepClone(){ + try{ + Mesh clone = (Mesh) super.clone(); + clone.meshBound = meshBound != null ? meshBound.clone() : null; + + // TODO: Collision tree cloning + //clone.collisionTree = collisionTree != null ? collisionTree : null; + clone.collisionTree = null; // it will get re-generated in any case + + clone.buffers = new IntMap(); + clone.buffersList = new SafeArrayList(VertexBuffer.class); + for (VertexBuffer vb : buffersList.getArray()){ + VertexBuffer bufClone = vb.clone(); + clone.buffers.put(vb.getBufferType().ordinal(), bufClone); + clone.buffersList.add(bufClone); + } + + clone.vertexArrayID = -1; + clone.vertCount = -1; + clone.elementCount = -1; + + // although this could change + // if the bone weight/index buffers are modified + clone.maxNumWeights = maxNumWeights; + + clone.elementLengths = elementLengths != null ? elementLengths.clone() : null; + clone.modeStart = modeStart != null ? modeStart.clone() : null; + return clone; + }catch (CloneNotSupportedException ex){ + throw new AssertionError(); + } + } + + /** + * Clone the mesh for animation use. + * This creates a shallow clone of the mesh, sharing most + * of the {@link VertexBuffer vertex buffer} data, however the + * {@link Type#Position}, {@link Type#Normal}, and {@link Type#Tangent} buffers + * are deeply cloned. + * + * @return A clone of the mesh for animation use. + */ + public Mesh cloneForAnim(){ + Mesh clone = clone(); + if (getBuffer(Type.BindPosePosition) != null){ + VertexBuffer oldPos = getBuffer(Type.Position); + + // NOTE: creates deep clone + VertexBuffer newPos = oldPos.clone(); + clone.clearBuffer(Type.Position); + clone.setBuffer(newPos); + + if (getBuffer(Type.BindPoseNormal) != null){ + VertexBuffer oldNorm = getBuffer(Type.Normal); + VertexBuffer newNorm = oldNorm.clone(); + clone.clearBuffer(Type.Normal); + clone.setBuffer(newNorm); + + if (getBuffer(Type.BindPoseTangent) != null){ + VertexBuffer oldTang = getBuffer(Type.Tangent); + VertexBuffer newTang = oldTang.clone(); + clone.clearBuffer(Type.Tangent); + clone.setBuffer(newTang); + } + } + } + return clone; + } + + /** + * Generates the {@link Type#BindPosePosition}, {@link Type#BindPoseNormal}, + * and {@link Type#BindPoseTangent} + * buffers for this mesh by duplicating them based on the position and normal + * buffers already set on the mesh. + * This method does nothing if the mesh has no bone weight or index + * buffers. + * + * @param forSoftwareAnim Should be true if the bind pose is to be generated. + */ + public void generateBindPose(boolean forSoftwareAnim){ + if (forSoftwareAnim){ + VertexBuffer pos = getBuffer(Type.Position); + if (pos == null || getBuffer(Type.BoneIndex) == null) { + // ignore, this mesh doesn't have positional data + // or it doesn't have bone-vertex assignments, so its not animated + return; + } + + VertexBuffer bindPos = new VertexBuffer(Type.BindPosePosition); + bindPos.setupData(Usage.CpuOnly, + pos.getNumComponents(), + pos.getFormat(), + BufferUtils.clone(pos.getData())); + setBuffer(bindPos); + + // XXX: note that this method also sets stream mode + // so that animation is faster. this is not needed for hardware skinning + pos.setUsage(Usage.Stream); + + VertexBuffer norm = getBuffer(Type.Normal); + if (norm != null) { + VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal); + bindNorm.setupData(Usage.CpuOnly, + norm.getNumComponents(), + norm.getFormat(), + BufferUtils.clone(norm.getData())); + setBuffer(bindNorm); + norm.setUsage(Usage.Stream); + } + + VertexBuffer tangents = getBuffer(Type.Tangent); + if (tangents != null) { + VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent); + bindTangents.setupData(Usage.CpuOnly, + tangents.getNumComponents(), + tangents.getFormat(), + BufferUtils.clone(tangents.getData())); + setBuffer(bindTangents); + tangents.setUsage(Usage.Stream); + }// else hardware setup does nothing, mesh already in bind pose + } + } + + /** + * Prepares the mesh for software skinning by converting the bone index + * and weight buffers to heap buffers. + * + * @param forSoftwareAnim Should be true to enable the conversion. + */ + public void prepareForAnim(boolean forSoftwareAnim){ + if (forSoftwareAnim) { + // convert indices to ubytes on the heap + VertexBuffer indices = getBuffer(Type.BoneIndex); + if (!indices.getData().hasArray()) { + ByteBuffer originalIndex = (ByteBuffer) indices.getData(); + ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity()); + originalIndex.clear(); + arrayIndex.put(originalIndex); + indices.updateData(arrayIndex); + } + indices.setUsage(Usage.CpuOnly); + + // convert weights on the heap + VertexBuffer weights = getBuffer(Type.BoneWeight); + if (!weights.getData().hasArray()) { + FloatBuffer originalWeight = (FloatBuffer) weights.getData(); + FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity()); + originalWeight.clear(); + arrayWeight.put(originalWeight); + weights.updateData(arrayWeight); + } + weights.setUsage(Usage.CpuOnly); + // position, normal, and tanget buffers to be in "Stream" mode + VertexBuffer positions = getBuffer(Type.Position); + VertexBuffer normals = getBuffer(Type.Normal); + VertexBuffer tangents = getBuffer(Type.Tangent); + positions.setUsage(Usage.Stream); + if (normals != null) { + normals.setUsage(Usage.Stream); + } + if (tangents != null) { + tangents.setUsage(Usage.Stream); + } + } else { + //if HWBoneIndex and HWBoneWieght are empty, we setup them as direct + //buffers with software anim buffers data + VertexBuffer indicesHW = getBuffer(Type.HWBoneIndex); + if(indicesHW.getData() == null){ + VertexBuffer indices = getBuffer(Type.BoneIndex); + ByteBuffer originalIndex = (ByteBuffer) indices.getData(); + ByteBuffer directIndex = BufferUtils.createByteBuffer(originalIndex.capacity()); + originalIndex.clear(); + directIndex.put(originalIndex); + indicesHW.setupData(Usage.Static, indices.getNumComponents(), indices.getFormat(), directIndex); + } + + VertexBuffer weightsHW = getBuffer(Type.HWBoneWeight); + if(weightsHW.getData() == null){ + VertexBuffer weights = getBuffer(Type.BoneWeight); + FloatBuffer originalWeight = (FloatBuffer) weights.getData(); + FloatBuffer directWeight = BufferUtils.createFloatBuffer(originalWeight.capacity()); + originalWeight.clear(); + directWeight.put(originalWeight); + weightsHW.setupData(Usage.Static, weights.getNumComponents(), weights.getFormat(), directWeight); + } + + // position, normal, and tanget buffers to be in "Static" mode + VertexBuffer positions = getBuffer(Type.Position); + VertexBuffer normals = getBuffer(Type.Normal); + VertexBuffer tangents = getBuffer(Type.Tangent); + positions.setUsage(Usage.Static); + if (normals != null) { + normals.setUsage(Usage.Static); + } + if (tangents != null) { + tangents.setUsage(Usage.Static); + } + } + } + + /** + * Set the LOD (level of detail) index buffers on this mesh. + * + * @param lodLevels The LOD levels to set + */ + public void setLodLevels(VertexBuffer[] lodLevels){ + this.lodLevels = lodLevels; + } + + /** + * @return The number of LOD levels set on this mesh, including the main + * index buffer, returns zero if there are no lod levels. + */ + public int getNumLodLevels(){ + return lodLevels != null ? lodLevels.length : 0; + } + + /** + * Returns the lod level at the given index. + * + * @param lod The lod level index, this does not include + * the main index buffer. + * @return The LOD index buffer at the index + * + * @throws IndexOutOfBoundsException If the index is outside of the + * range [0, {@link #getNumLodLevels()}]. + * + * @see #setLodLevels(com.jme3.scene.VertexBuffer[]) + */ + public VertexBuffer getLodLevel(int lod){ + return lodLevels[lod]; + } + + /** + * Get the element lengths for {@link Mode#Hybrid} mesh mode. + * + * @return element lengths + */ + public int[] getElementLengths() { + return elementLengths; + } + + /** + * Set the element lengths for {@link Mode#Hybrid} mesh mode. + * + * @param elementLengths The element lengths to set + */ + public void setElementLengths(int[] elementLengths) { + this.elementLengths = elementLengths; + } + + /** + * Set the mode start indices for {@link Mode#Hybrid} mesh mode. + * + * @return mode start indices + */ + public int[] getModeStart() { + return modeStart; + } + + /** + * Get the mode start indices for {@link Mode#Hybrid} mesh mode. + */ + public void setModeStart(int[] modeStart) { + this.modeStart = modeStart; + } + + /** + * Returns the mesh mode + * + * @return the mesh mode + * + * @see #setMode(com.jme3.scene.Mesh.Mode) + */ + public Mode getMode() { + return mode; + } + + /** + * Change the Mesh's mode. By default the mode is {@link Mode#Triangles}. + * + * @param mode The new mode to set + * + * @see Mode + */ + public void setMode(Mode mode) { + this.mode = mode; + updateCounts(); + } + + /** + * Returns the maximum number of weights per vertex on this mesh. + * + * @return maximum number of weights per vertex + * + * @see #setMaxNumWeights(int) + */ + public int getMaxNumWeights() { + return maxNumWeights; + } + + /** + * Set the maximum number of weights per vertex on this mesh. + * Only relevant if this mesh has bone index/weight buffers. + * This value should be between 0 and 4. + * + * @param maxNumWeights + */ + public void setMaxNumWeights(int maxNumWeights) { + this.maxNumWeights = maxNumWeights; + } + + /** + * Returns the size of points for point meshes + * + * @return the size of points + * + * @see #setPointSize(float) + */ + public float getPointSize() { + return pointSize; + } + + /** + * Set the size of points for meshes of mode {@link Mode#Points}. + * The point size is specified as on-screen pixels, the default + * value is 1.0. The point size + * does nothing if {@link RenderState#setPointSprite(boolean) point sprite} + * render state is enabled, in that case, the vertex shader must specify the + * point size by writing to gl_PointSize. + * + * @param pointSize The size of points + */ + public void setPointSize(float pointSize) { + this.pointSize = pointSize; + } + + /** + * Returns the line width for line meshes. + * + * @return the line width + */ + public float getLineWidth() { + return lineWidth; + } + + /** + * Specify the line width for meshes of the line modes, such + * as {@link Mode#Lines}. The line width is specified as on-screen pixels, + * the default value is 1.0. + * + * @param lineWidth The line width + */ + public void setLineWidth(float lineWidth) { + this.lineWidth = lineWidth; + } + + /** + * Indicates to the GPU that this mesh will not be modified (a hint). + * Sets the usage mode to {@link Usage#Static} + * for all {@link VertexBuffer vertex buffers} on this Mesh. + */ + public void setStatic() { + for (VertexBuffer vb : buffersList.getArray()){ + vb.setUsage(Usage.Static); + } + } + + /** + * Indicates to the GPU that this mesh will be modified occasionally (a hint). + * Sets the usage mode to {@link Usage#Dynamic} + * for all {@link VertexBuffer vertex buffers} on this Mesh. + */ + public void setDynamic() { + for (VertexBuffer vb : buffersList.getArray()){ + vb.setUsage(Usage.Dynamic); + } + } + + /** + * Indicates to the GPU that this mesh will be modified every frame (a hint). + * Sets the usage mode to {@link Usage#Stream} + * for all {@link VertexBuffer vertex buffers} on this Mesh. + */ + public void setStreamed(){ + for (VertexBuffer vb : buffersList.getArray()){ + vb.setUsage(Usage.Stream); + } + } + + /** + * Interleaves the data in this mesh. This operation cannot be reversed. + * Some GPUs may prefer the data in this format, however it is a good idea + * to avoid using this method as it disables some engine features. + */ + @Deprecated + public void setInterleaved(){ + ArrayList vbs = new ArrayList(); + vbs.addAll(buffersList); + +// 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); + buffersList.add(allData); + + for (int vert = 0; vert < getVertexCount(); vert++){ + for (int i = 0; i < vbs.size(); i++){ + VertexBuffer vb = vbs.get(i); + switch (vb.getFormat()){ + case Float: + FloatBuffer fb = (FloatBuffer) vb.getData(); + for (int comp = 0; comp < vb.components; comp++){ + dataBuf.putFloat(fb.get()); + } + break; + case Byte: + case UnsignedByte: + ByteBuffer bb = (ByteBuffer) vb.getData(); + for (int comp = 0; comp < vb.components; comp++){ + dataBuf.put(bb.get()); + } + break; + case Half: + case Short: + case UnsignedShort: + ShortBuffer sb = (ShortBuffer) vb.getData(); + for (int comp = 0; comp < vb.components; comp++){ + dataBuf.putShort(sb.get()); + } + break; + case Int: + case UnsignedInt: + IntBuffer ib = (IntBuffer) vb.getData(); + for (int comp = 0; comp < vb.components; comp++){ + dataBuf.putInt(ib.get()); + } + break; + case Double: + DoubleBuffer db = (DoubleBuffer) vb.getData(); + for (int comp = 0; comp < vb.components; comp++){ + dataBuf.putDouble(db.get()); + } + break; + } + } + } + + int offset = 0; + for (VertexBuffer vb : vbs){ + vb.setOffset(offset); + vb.setStride(stride); + + vb.updateData(null); + //vb.setupData(vb.usage, vb.components, vb.format, null); + offset += vb.componentsLength; + } + } + + private int computeNumElements(int bufSize){ + switch (mode){ + case Triangles: + return bufSize / 3; + case TriangleFan: + case TriangleStrip: + return bufSize - 2; + case Points: + return bufSize; + case Lines: + return bufSize / 2; + case LineLoop: + return bufSize; + case LineStrip: + return bufSize - 1; + default: + throw new UnsupportedOperationException(); + } + } + + /** + * Update the {@link #getVertexCount() vertex} and + * {@link #getTriangleCount() triangle} counts for this mesh + * based on the current data. This method should be called + * after the {@link Buffer#capacity() capacities} of the mesh's + * {@link VertexBuffer vertex buffers} has been altered. + * + * @throws IllegalStateException If this mesh is in + * {@link #setInterleaved() interleaved} format. + */ + public void updateCounts(){ + if (getBuffer(Type.InterleavedData) != null) + throw new IllegalStateException("Should update counts before interleave"); + + VertexBuffer pb = getBuffer(Type.Position); + VertexBuffer ib = getBuffer(Type.Index); + if (pb != null){ + vertCount = pb.getData().limit() / pb.getNumComponents(); + } + if (ib != null){ + elementCount = computeNumElements(ib.getData().limit()); + }else{ + elementCount = computeNumElements(vertCount); + } + } + + /** + * Returns the triangle count for the given LOD level. + * + * @param lod The lod level to look up + * @return The triangle count for that LOD level + */ + public int getTriangleCount(int lod){ + if (lodLevels != null){ + if (lod < 0) + throw new IllegalArgumentException("LOD level cannot be < 0"); + + if (lod >= lodLevels.length) + throw new IllegalArgumentException("LOD level "+lod+" does not exist!"); + + return computeNumElements(lodLevels[lod].getData().limit()); + }else if (lod == 0){ + return elementCount; + }else{ + throw new IllegalArgumentException("There are no LOD levels on the mesh!"); + } + } + + /** + * Returns how many triangles or elements are on this Mesh. + * This value is only updated when {@link #updateCounts() } is called. + * If the mesh mode is not a triangle mode, then this returns the + * number of elements/primitives, e.g. how many lines or how many points, + * instead of how many triangles. + * + * @return how many triangles/elements are on this Mesh. + */ + public int getTriangleCount(){ + return elementCount; + } + + /** + * Returns the number of vertices on this mesh. + * The value is computed based on the position buffer, which + * must be set on all meshes. + * + * @return Number of vertices on the mesh + */ + public int getVertexCount(){ + return vertCount; + } + + /** + * Gets the triangle vertex positions at the given triangle index + * and stores them into the v1, v2, v3 arguments. + * + * @param index The index of the triangle. + * Should be between 0 and {@link #getTriangleCount()}. + * + * @param v1 Vector to contain first vertex position + * @param v2 Vector to contain second vertex position + * @param v3 Vector to contain third vertex position + */ + public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3){ + VertexBuffer pb = getBuffer(Type.Position); + IndexBuffer ib = getIndicesAsList(); + if (pb != null && pb.getFormat() == Format.Float && pb.getNumComponents() == 3){ + FloatBuffer fpb = (FloatBuffer) pb.getData(); + + // aquire triangle's vertex indices + int vertIndex = index * 3; + int vert1 = ib.get(vertIndex); + int vert2 = ib.get(vertIndex+1); + int vert3 = ib.get(vertIndex+2); + + BufferUtils.populateFromBuffer(v1, fpb, vert1); + BufferUtils.populateFromBuffer(v2, fpb, vert2); + BufferUtils.populateFromBuffer(v3, fpb, vert3); + }else{ + throw new UnsupportedOperationException("Position buffer not set or " + + " has incompatible format"); + } + } + + /** + * Gets the triangle vertex positions at the given triangle index + * and stores them into the {@link Triangle} argument. + * Also sets the triangle index to the index argument. + * + * @param index The index of the triangle. + * Should be between 0 and {@link #getTriangleCount()}. + * + * @param tri The triangle to store the positions in + */ + public void getTriangle(int index, Triangle tri){ + getTriangle(index, tri.get1(), tri.get2(), tri.get3()); + tri.setIndex(index); + tri.setNormal(null); + } + + /** + * Gets the triangle vertex indices at the given triangle index + * and stores them into the given int array. + * + * @param index The index of the triangle. + * Should be between 0 and {@link #getTriangleCount()}. + * + * @param indices Indices of the triangle's vertices + */ + public void getTriangle(int index, int[] indices){ + IndexBuffer ib = getIndicesAsList(); + + // acquire triangle's vertex indices + int vertIndex = index * 3; + indices[0] = ib.get(vertIndex); + indices[1] = ib.get(vertIndex+1); + indices[2] = ib.get(vertIndex+2); + } + + /** + * Returns the mesh's VAO ID. Internal use only. + */ + public int getId(){ + return vertexArrayID; + } + + /** + * Sets the mesh's VAO ID. Internal use only. + */ + public void setId(int id){ + if (vertexArrayID != -1) + throw new IllegalStateException("ID has already been set."); + + vertexArrayID = id; + } + + /** + * Generates a collision tree for the mesh. + * Called automatically by {@link #collideWith(com.jme3.collision.Collidable, + * com.jme3.math.Matrix4f, + * com.jme3.bounding.BoundingVolume, + * com.jme3.collision.CollisionResults) }. + */ + public void createCollisionData(){ + BIHTree tree = new BIHTree(this); + tree.construct(); + collisionTree = tree; + } + + /** + * Clears any previously generated collision data. Use this if + * the mesh has changed in some way that invalidates any previously + * generated BIHTree. + */ + public void clearCollisionData() { + collisionTree = null; + } + + /** + * Handles collision detection, internal use only. + * User code should only use collideWith() on scene + * graph elements such as {@link Spatial}s. + */ + public int collideWith(Collidable other, + Matrix4f worldMatrix, + BoundingVolume worldBound, + CollisionResults results){ + + if (getVertexCount() == 0) { + return 0; + } + + if (collisionTree == null){ + createCollisionData(); + } + + return collisionTree.collideWith(other, worldMatrix, worldBound, results); + } + + /** + * Sets the {@link VertexBuffer} on the mesh. + * This will update the vertex/triangle counts if needed. + * + * @param vb The buffer to set + * @throws IllegalArgumentException If the buffer type is already set + */ + public void setBuffer(VertexBuffer vb){ + if (buffers.containsKey(vb.getBufferType().ordinal())) + throw new IllegalArgumentException("Buffer type already set: "+vb.getBufferType()); + + buffers.put(vb.getBufferType().ordinal(), vb); + buffersList.add(vb); + updateCounts(); + } + + /** + * Unsets the {@link VertexBuffer} set on this mesh + * with the given type. Does nothing if the vertex buffer type is not set + * initially. + * + * @param type The buffer type to remove + */ + public void clearBuffer(VertexBuffer.Type type){ + VertexBuffer vb = buffers.remove(type.ordinal()); + if (vb != null){ + buffersList.remove(vb); + updateCounts(); + } + } + + /** + * Creates a {@link VertexBuffer} for the mesh or modifies + * the existing one per the parameters given. + * + * @param type The type of the buffer + * @param components Number of components + * @param format Data format + * @param buf The buffer data + * + * @throws UnsupportedOperationException If the buffer already set is + * incompatible with the parameters given. + */ + public void setBuffer(Type type, int components, Format format, Buffer buf){ + VertexBuffer vb = buffers.get(type.ordinal()); + if (vb == null){ + vb = new VertexBuffer(type); + vb.setupData(Usage.Dynamic, components, format, buf); + setBuffer(vb); + }else{ + if (vb.getNumComponents() != components || vb.getFormat() != format){ + throw new UnsupportedOperationException("The buffer already set " + + "is incompatible with the given parameters"); + } + vb.updateData(buf); + updateCounts(); + } + } + + /** + * Set a floating point {@link VertexBuffer} on the mesh. + * + * @param type The type of {@link VertexBuffer}, + * e.g. {@link Type#Position}, {@link Type#Normal}, etc. + * + * @param components Number of components on the vertex buffer, should + * be between 1 and 4. + * + * @param buf The floating point data to contain + */ + public void setBuffer(Type type, int components, FloatBuffer buf) { + setBuffer(type, components, Format.Float, buf); + } + + public void setBuffer(Type type, int components, float[] buf){ + setBuffer(type, components, BufferUtils.createFloatBuffer(buf)); + } + + public void setBuffer(Type type, int components, IntBuffer buf) { + setBuffer(type, components, Format.UnsignedInt, buf); + } + + public void setBuffer(Type type, int components, int[] buf){ + setBuffer(type, components, BufferUtils.createIntBuffer(buf)); + } + + public void setBuffer(Type type, int components, ShortBuffer buf) { + setBuffer(type, components, Format.UnsignedShort, buf); + } + + public void setBuffer(Type type, int components, byte[] buf){ + setBuffer(type, components, BufferUtils.createByteBuffer(buf)); + } + + public void setBuffer(Type type, int components, ByteBuffer buf) { + setBuffer(type, components, Format.UnsignedByte, buf); + } + + public void setBuffer(Type type, int components, short[] buf){ + setBuffer(type, components, BufferUtils.createShortBuffer(buf)); + } + + /** + * Get the {@link VertexBuffer} stored on this mesh with the given + * type. + * + * @param type The type of VertexBuffer + * @return the VertexBuffer data, or null if not set + */ + public VertexBuffer getBuffer(Type type){ + return buffers.get(type.ordinal()); + } + + /** + * Get the {@link VertexBuffer} data stored on this mesh in float + * format. + * + * @param type The type of VertexBuffer + * @return the VertexBuffer data, or null if not set + */ + public FloatBuffer getFloatBuffer(Type type) { + VertexBuffer vb = getBuffer(type); + if (vb == null) + return null; + + return (FloatBuffer) vb.getData(); + } + + /** + * Get the {@link VertexBuffer} data stored on this mesh in short + * format. + * + * @param type The type of VertexBuffer + * @return the VertexBuffer data, or null if not set + */ + public ShortBuffer getShortBuffer(Type type) { + VertexBuffer vb = getBuffer(type); + if (vb == null) + return null; + + return (ShortBuffer) vb.getData(); + } + + /** + * Acquires an index buffer that will read the vertices on the mesh + * as a list. + * + * @return A virtual or wrapped index buffer to read the data as a list + */ + public IndexBuffer getIndicesAsList(){ + if (mode == Mode.Hybrid) + throw new UnsupportedOperationException("Hybrid mode not supported"); + + IndexBuffer ib = getIndexBuffer(); + if (ib != null){ + if (mode.isListMode()){ + // already in list mode + return ib; + }else{ + // not in list mode but it does have an index buffer + // wrap it so the data is converted to list format + return new WrappedIndexBuffer(this); + } + }else{ + // return a virtual index buffer that will supply + // "fake" indices in list format + return new VirtualIndexBuffer(vertCount, mode); + } + } + + /** + * Get the index buffer for this mesh. + * Will return null if no index buffer is set. + * + * @return The index buffer of this mesh. + * + * @see Type#Index + */ + public IndexBuffer getIndexBuffer() { + VertexBuffer vb = getBuffer(Type.Index); + if (vb == null) + return null; + + return IndexBuffer.wrapIndexBuffer(vb.getData()); + } + + /** + * Extracts the vertex attributes from the given mesh into + * this mesh, by using this mesh's {@link #getIndexBuffer() index buffer} + * to index into the attributes of the other mesh. + * Note that this will also change this mesh's index buffer so that + * the references to the vertex data match the new indices. + * + * @param other The mesh to extract the vertex data from + */ + public void extractVertexData(Mesh other) { + // Determine the number of unique vertices need to + // be created. Also determine the mappings + // between old indices to new indices (since we avoid duplicating + // vertices, this is a map and not an array). + VertexBuffer oldIdxBuf = getBuffer(Type.Index); + IndexBuffer indexBuf = getIndexBuffer(); + int numIndices = indexBuf.size(); + + IntMap oldIndicesToNewIndices = new IntMap(numIndices); + ArrayList newIndicesToOldIndices = new ArrayList(); + int newIndex = 0; + + for (int i = 0; i < numIndices; i++) { + int oldIndex = indexBuf.get(i); + + if (!oldIndicesToNewIndices.containsKey(oldIndex)) { + // this vertex has not been added, so allocate a + // new index for it and add it to the map + oldIndicesToNewIndices.put(oldIndex, newIndex); + newIndicesToOldIndices.add(oldIndex); + + // increment to have the next index + newIndex++; + } + } + + // Number of unique verts to be created now available + int newNumVerts = newIndicesToOldIndices.size(); + + if (newIndex != newNumVerts) { + throw new AssertionError(); + } + + // Create the new index buffer. + // Do not overwrite the old one because we might be able to + // convert from int index buffer to short index buffer + IndexBuffer newIndexBuf; + if (newNumVerts >= 65536) { + newIndexBuf = new IndexIntBuffer(BufferUtils.createIntBuffer(numIndices)); + } else { + newIndexBuf = new IndexShortBuffer(BufferUtils.createShortBuffer(numIndices)); + } + + for (int i = 0; i < numIndices; i++) { + // Map the old indices to the new indices + int oldIndex = indexBuf.get(i); + newIndex = oldIndicesToNewIndices.get(oldIndex); + + newIndexBuf.put(i, newIndex); + } + + VertexBuffer newIdxBuf = new VertexBuffer(Type.Index); + newIdxBuf.setupData(oldIdxBuf.getUsage(), + oldIdxBuf.getNumComponents(), + newIndexBuf instanceof IndexIntBuffer ? Format.UnsignedInt : Format.UnsignedShort, + newIndexBuf.getBuffer()); + clearBuffer(Type.Index); + setBuffer(newIdxBuf); + + // Now, create the vertex buffers + SafeArrayList oldVertexData = other.getBufferList(); + for (VertexBuffer oldVb : oldVertexData) { + if (oldVb.getBufferType() == VertexBuffer.Type.Index) { + // ignore the index buffer + continue; + } + + VertexBuffer newVb = new VertexBuffer(oldVb.getBufferType()); + newVb.setNormalized(oldVb.isNormalized()); + //check for data before copying, some buffers are just empty shells + //for caching purpose (HW skinning buffers), and will be filled when + //needed + if(oldVb.getData()!=null){ + // Create a new vertex buffer with similar configuration, but + // with the capacity of number of unique vertices + Buffer buffer = VertexBuffer.createBuffer(oldVb.getFormat(), oldVb.getNumComponents(), newNumVerts); + newVb.setupData(oldVb.getUsage(), oldVb.getNumComponents(), oldVb.getFormat(), buffer); + + // Copy the vertex data from the old buffer into the new buffer + for (int i = 0; i < newNumVerts; i++) { + int oldIndex = newIndicesToOldIndices.get(i); + + // Copy the vertex attribute from the old index + // to the new index + oldVb.copyElement(oldIndex, newVb, i); + } + } + + // Set the buffer on the mesh + clearBuffer(newVb.getBufferType()); + setBuffer(newVb); + } + + // Copy max weights per vertex as well + setMaxNumWeights(other.getMaxNumWeights()); + + // The data has been copied over, update informations + updateCounts(); + updateBound(); + } + + /** + * Scales the texture coordinate buffer on this mesh by the given + * scale factor. + *

      + * Note that values above 1 will cause the + * texture to tile, while values below 1 will cause the texture + * to stretch. + *

      + * + * @param scaleFactor The scale factor to scale by. Every texture + * coordinate is multiplied by this vector to get the result. + * + * @throws IllegalStateException If there's no texture coordinate + * buffer on the mesh + * @throws UnsupportedOperationException If the texture coordinate + * buffer is not in 2D float format. + */ + public void scaleTextureCoordinates(Vector2f scaleFactor){ + VertexBuffer tc = getBuffer(Type.TexCoord); + if (tc == null) + throw new IllegalStateException("The mesh has no texture coordinates"); + + if (tc.getFormat() != VertexBuffer.Format.Float) + throw new UnsupportedOperationException("Only float texture coord format is supported"); + + if (tc.getNumComponents() != 2) + throw new UnsupportedOperationException("Only 2D texture coords are supported"); + + FloatBuffer fb = (FloatBuffer) tc.getData(); + fb.clear(); + for (int i = 0; i < fb.limit() / 2; i++){ + float x = fb.get(); + float y = fb.get(); + fb.position(fb.position()-2); + x *= scaleFactor.getX(); + y *= scaleFactor.getY(); + fb.put(x).put(y); + } + fb.clear(); + tc.updateData(fb); + } + + /** + * Updates the bounding volume of this mesh. + * The method does nothing if the mesh has no {@link Type#Position} buffer. + * It is expected that the position buffer is a float buffer with 3 components. + */ + public void updateBound(){ + VertexBuffer posBuf = getBuffer(VertexBuffer.Type.Position); + if (meshBound != null && posBuf != null){ + meshBound.computeFromPoints((FloatBuffer)posBuf.getData()); + } + } + + /** + * Returns the {@link BoundingVolume} of this Mesh. + * By default the bounding volume is a {@link BoundingBox}. + * + * @return the bounding volume of this mesh + */ + public BoundingVolume getBound() { + return meshBound; + } + + /** + * Sets the {@link BoundingVolume} for this Mesh. + * The bounding volume is recomputed by calling {@link #updateBound() }. + * + * @param modelBound The model bound to set + */ + public void setBound(BoundingVolume modelBound) { + meshBound = modelBound; + } + + /** + * Returns a map of all {@link VertexBuffer vertex buffers} on this Mesh. + * The integer key for the map is the {@link Enum#ordinal() ordinal} + * of the vertex buffer's {@link Type}. + * Note that the returned map is a reference to the map used internally, + * modifying it will cause undefined results. + * + * @return map of vertex buffers on this mesh. + */ + public IntMap getBuffers(){ + return buffers; + } + + /** + * Returns a list of all {@link VertexBuffer vertex buffers} on this Mesh. + * Using a list instead an IntMap via the {@link #getBuffers() } method is + * better for iteration as there's no need to create an iterator instance. + * Note that the returned list is a reference to the list used internally, + * modifying it will cause undefined results. + * + * @return list of vertex buffers on this mesh. + */ + public SafeArrayList getBufferList(){ + return buffersList; + } + + public boolean isAnimated() { + //TODO this won't work once we have pose animations, we should find a better way to check for animation + return getBuffer(Type.BindPosePosition) != null && getBuffer(Type.BoneIndex) != null && getBuffer(Type.BoneWeight) != null; + } + + + 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); + + //Removing HW skinning buffers to not save them + VertexBuffer hwBoneIndex = null; + VertexBuffer hwBoneWeight = null; + hwBoneIndex = getBuffer(Type.HWBoneIndex); + if (hwBoneIndex != null) { + buffers.remove(Type.HWBoneIndex.ordinal()); + } + hwBoneWeight = getBuffer(Type.HWBoneWeight); + if (hwBoneWeight != null) { + buffers.remove(Type.HWBoneWeight.ordinal()); + } + + out.writeIntSavableMap(buffers, "buffers", null); + + //restoring Hw skinning buffers. + if (hwBoneIndex != null) { + buffers.put(hwBoneIndex.getBufferType().ordinal(), hwBoneIndex); + } + if (hwBoneWeight != null) { + buffers.put(hwBoneWeight.getBufferType().ordinal(), hwBoneWeight); + } + + 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); + for (Entry entry : buffers){ + buffersList.add(entry.getValue()); + } + + //creating hw animation buffers empty so that they are put in the cache + if(isAnimated()){ + VertexBuffer hwBoneIndex = new VertexBuffer(Type.HWBoneIndex); + hwBoneIndex.setUsage(Usage.CpuOnly); + setBuffer(hwBoneIndex); + VertexBuffer hwBoneWeight = new VertexBuffer(Type.HWBoneWeight); + hwBoneWeight.setUsage(Usage.CpuOnly); + setBuffer(hwBoneWeight); + } + + Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null); + if (lodLevelsSavable != null) { + lodLevels = new VertexBuffer[lodLevelsSavable.length]; + System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/Node.java b/jme3-core/src/main/java/com/jme3/scene/Node.java new file mode 100644 index 000000000..b5bd45b10 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/Node.java @@ -0,0 +1,643 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.Savable; +import com.jme3.material.Material; +import com.jme3.util.SafeArrayList; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * 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 SafeArrayList children = new SafeArrayList(Spatial.class); + + /** + * Serialization only. Do not use. + */ + 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.getArray()){ + if ((child.refreshFlags & RF_TRANSFORM) != 0) + continue; + + child.setTransformRefresh(); + } + } + + @Override + protected void setLightListRefresh(){ + super.setLightListRefresh(); + for (Spatial child : children.getArray()){ + if ((child.refreshFlags & RF_LIGHTLIST) != 0) + continue; + + child.setLightListRefresh(); + } + } + + @Override + protected void updateWorldBound(){ + super.updateWorldBound(); + + // for a node, the world bound is a combination of all it's children + // bounds + BoundingVolume resultBound = null; + for (Spatial child : children.getArray()) { + // child bound is assumed to be updated + assert (child.refreshFlags & RF_BOUND) == 0; + if (resultBound != null) { + // merge current world bound with child world bound + resultBound.mergeLocal(child.getWorldBound()); + } else { + // set world bound to first non-null child world bound + if (child.getWorldBound() != null) { + resultBound = child.getWorldBound().clone(this.worldBound); + } + } + } + this.worldBound = resultBound; + } + + @Override + public void updateLogicalState(float tpf){ + super.updateLogicalState(tpf); + + if (children.isEmpty()) { + return; + } + + for (Spatial child : children.getArray()) { + child.updateLogicalState(tpf); + } + } + + @Override + public void updateGeometricState(){ + if ((refreshFlags & RF_LIGHTLIST) != 0){ + updateWorldLightList(); + } + + if ((refreshFlags & RF_TRANSFORM) != 0){ + // combine with parent transforms- same for all spatial + // subclasses. + updateWorldTransforms(); + } + + if (!children.isEmpty()) { + // the important part- make sure child geometric state is refreshed + // first before updating own world bound. This saves + // a round-trip later on. + // NOTE 9/19/09 + // Although it does save a round trip, + for (Spatial child : children.getArray()) { + child.updateGeometricState(); + } + } + + if ((refreshFlags & RF_BOUND) != 0){ + updateWorldBound(); + } + + assert refreshFlags == 0; + } + + /** + * 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 IllegalArgumentException if child is null. + */ + public int attachChild(Spatial child) { + if (child == null) + throw new IllegalArgumentException("child cannot be null"); + + 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.FINE)) { + logger.log(Level.FINE,"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.FINE)) { + logger.log(Level.FINE,"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 longer be maintained. + * + * @param child + * the child to remove. + * @return the index the child was at. -1 if the child was not in the list. + */ + public int detachChild(Spatial child) { + if (child == null) + throw new NullPointerException(); + + if (child.getParent() == this) { + int index = children.indexOf(child); + if (index != -1) { + detachChildAt(index); + } + return index; + } + + return -1; + } + + /** + * 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.log(Level.FINE, "{0}: Child removed.", this.toString()); + + // since a child with a bound was detached; + // our own bound will probably change. + setBoundRefresh(); + + // our world transform no longer influences the child. + // XXX: Not neccessary? Since child will have transform updated + // when attached anyway. + child.setTransformRefresh(); + // lights are also inherited from parent + child.setLightListRefresh(); + } + return child; + } + + /** + * + * detachAllChildren removes all children attached to this + * node. + */ + public void detachAllChildren() { + for ( int i = children.size() - 1; i >= 0; i-- ) { + detachChildAt(i); + } + logger.log(Level.FINE, "{0}: All children removed.", this.toString()); + } + + /** + * getChildIndex returns the index of the given spatial + * in this node's list of children. + * @param sp + * The spatial to look up + * @return + * The index of the spatial in the node's children, or -1 + * if the spatial is not attached to this node + */ + public int getChildIndex(Spatial sp) { + return children.indexOf(sp); + } + + /** + * More efficient than e.g detaching and attaching as no updates are needed. + * + * @param index1 The index of the first child to swap + * @param index2 The index of the second child to swap + */ + public void swapChildren(int index1, int index2) { + Spatial c2 = children.get(index2); + Spatial c1 = children.remove(index1); + children.add(index1, c2); + children.remove(index2); + children.add(index2, c1); + } + + /** + * + * 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.) This method does a depth first recursive + * search of all descendants of this node, it will return the first spatial + * found with a matching name. + * + * @param name + * the name of the child to retrieve. If null, we'll return null. + * @return the child if found, or null. + */ + public Spatial getChild(String name) { + if (name == null) + return null; + + for (Spatial child : children.getArray()) { + if (name.equals(child.getName())) { + return child; + } else if(child instanceof Node) { + Spatial out = ((Node)child).getChild(name); + if(out != null) { + return out; + } + } + } + return null; + } + + /** + * determines if the provided Spatial is contained in the children list of + * this node. + * + * @param spat + * the child object to look for. + * @return true if the object is contained, false otherwise. + */ + public boolean hasChild(Spatial spat) { + if (children.contains(spat)) + return true; + + for (Spatial child : children.getArray()) { + if (child instanceof Node && ((Node) child).hasChild(spat)) + return true; + } + + return false; + } + + /** + * Returns all children to this node. Note that modifying that given + * list is not allowed. + * + * @return a list containing all children to this node + */ + public List getChildren() { + return children; + } + + @Override + public void setMaterial(Material mat){ + for (int i = 0; i < children.size(); i++){ + children.get(i).setMaterial(mat); + } + } + + @Override + public void setLodLevel(int lod){ + super.setLodLevel(lod); + for (Spatial child : children.getArray()) { + child.setLodLevel(lod); + } + } + + public int collideWith(Collidable other, CollisionResults results){ + int total = 0; + for (Spatial child : children.getArray()){ + total += child.collideWith(other, results); + } + return total; + } + + + /** + * Returns flat list of Spatials implementing the specified class AND + * with name matching the specified pattern. + *

      + * 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(java.lang.Class, java.lang.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(java.lang.Class, java.lang.String) + */ + public List descendantMatches( + Class spatialSubclass) { + return descendantMatches(spatialSubclass, null); + } + + /** + * Convenience wrapper. + * + * @see #descendantMatches(java.lang.Class, java.lang.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 SafeArrayList(Spatial.class); + for (Spatial child : children){ + Spatial childClone = child.deepClone(); + childClone.parent = nodeClone; + nodeClone.children.add(childClone); + } + return nodeClone; + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + e.getCapsule(this).writeSavableArrayList(new ArrayList(children), "children", null); + } + + @Override + public void read(JmeImporter e) throws IOException { + // XXX: Load children before loading itself!! + // This prevents empty children list if controls query + // it in Control.setSpatial(). + + children = new SafeArrayList( Spatial.class, + e.getCapsule(this).readSavableArrayList("children", null) ); + + // go through children and set parent to this node + if (children != null) { + for (Spatial child : children.getArray()) { + child.parent = this; + } + } + + super.read(e); + } + + @Override + public void setModelBound(BoundingVolume modelBound) { + if(children != null) { + for (Spatial child : children.getArray()) { + child.setModelBound(modelBound != null ? modelBound.clone(null) : null); + } + } + } + + @Override + public void updateModelBound() { + if(children != null) { + for (Spatial child : children.getArray()) { + child.updateModelBound(); + } + } + } + + @Override + public void depthFirstTraversal(SceneGraphVisitor visitor) { + for (Spatial child : children.getArray()) { + child.depthFirstTraversal(visitor); + } + visitor.visit(this); + } + + @Override + protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue queue) { + queue.addAll(children); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/SceneGraphVisitor.java b/jme3-core/src/main/java/com/jme3/scene/SceneGraphVisitor.java new file mode 100644 index 000000000..f19002796 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/SceneGraphVisitor.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +/** + * SceneGraphVisitorAdapter is used to traverse the scene + * graph tree. + * Use by calling {@link Spatial#depthFirstTraversal(com.jme3.scene.SceneGraphVisitor) } + * or {@link Spatial#breadthFirstTraversal(com.jme3.scene.SceneGraphVisitor)}. + */ +public interface SceneGraphVisitor { + /** + * Called when a spatial is visited in the scene graph. + * + * @param spatial The visited spatial + */ + public void visit(Spatial spatial); +} diff --git a/jme3-core/src/main/java/com/jme3/scene/SceneGraphVisitorAdapter.java b/jme3-core/src/main/java/com/jme3/scene/SceneGraphVisitorAdapter.java new file mode 100644 index 000000000..8fa419e75 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/SceneGraphVisitorAdapter.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +/** + * SceneGraphVisitorAdapter is used to traverse the scene + * graph tree. The adapter version of the interface simply separates + * between the {@link Geometry geometries} and the {@link Node nodes} by + * supplying visit methods that take them. + * Use by calling {@link Spatial#depthFirstTraversal(com.jme3.scene.SceneGraphVisitor) } + * or {@link Spatial#breadthFirstTraversal(com.jme3.scene.SceneGraphVisitor)}. + */ +public class SceneGraphVisitorAdapter implements SceneGraphVisitor { + + /** + * Called when a {@link Geometry} is visited. + * + * @param geom The visited geometry + */ + public void visit(Geometry geom) {} + + /** + * Called when a {@link Node} is visited. + * + * @param geom The visited node + */ + public void visit(Node geom) {} + + @Override + public final void visit(Spatial spatial) { + if (spatial instanceof Geometry) { + visit((Geometry)spatial); + } else { + visit((Node)spatial); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java b/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java new file mode 100644 index 000000000..2c5a1773f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import com.jme3.math.Matrix4f; +import com.jme3.math.Transform; +import com.jme3.util.TempVars; + +/** + * + * SimpleBatchNode comes with some restrictions, but can yield better performances. + * Geometries to be batched has to be attached directly to the BatchNode + * You can't attach a Node to a SimpleBatchNode + * SimpleBatchNode is recommended when you have a large number of geometries using the same material that does not require a complex scene graph structure. + * @see BatchNode + * @author Nehon + */ +public class SimpleBatchNode extends BatchNode { + + public SimpleBatchNode() { + super(); + } + + public SimpleBatchNode(String name) { + super(name); + } + + @Override + public int attachChild(Spatial child) { + + if (!(child instanceof Geometry)) { + throw new UnsupportedOperationException("BatchNode is BatchMode.Simple only support child of type Geometry, use BatchMode.Complex to use a complex structure"); + } + + return super.attachChild(child); + } + + @Override + protected void setTransformRefresh() { + refreshFlags |= RF_TRANSFORM; + setBoundRefresh(); + for (Batch batch : batches.getArray()) { + batch.geometry.setTransformRefresh(); + } + } + private Matrix4f cachedLocalMat = new Matrix4f(); + + @Override + protected Matrix4f getTransformMatrix(Geometry g){ + // Compute the Local matrix for the geometry + cachedLocalMat.loadIdentity(); + cachedLocalMat.setRotationQuaternion(g.localTransform.getRotation()); + cachedLocalMat.setTranslation(g.localTransform.getTranslation()); + + TempVars vars = TempVars.get(); + Matrix4f scaleMat = vars.tempMat4; + scaleMat.loadIdentity(); + scaleMat.scale(g.localTransform.getScale()); + cachedLocalMat.multLocal(scaleMat); + vars.release(); + return cachedLocalMat; + } + + + @Override + public void batch() { + doBatch(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java new file mode 100644 index 000000000..7562a9140 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -0,0 +1,1515 @@ +/* + * Copyright (c) 2009-2013 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR 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.AssetKey; +import com.jme3.asset.CloneableSmartAsset; +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.Collidable; +import com.jme3.export.*; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.control.Control; +import com.jme3.util.SafeArrayList; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.*; +import java.util.logging.Logger; + +/** + * 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 scene graph elements, such as {@link Node} and + * {@link Geometry} are subclasses of Spatial. + * + * @author Mark Powell + * @author Joshua Slack + * @version $Revision: 4075 $, $Data$ + */ +public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset { + + private static final Logger logger = Logger.getLogger(Spatial.class.getName()); + + /** + * Specifies how frustum culling should be handled by + * this spatial. + */ + public enum CullHint { + + /** + * Do whatever our parent does. If no parent, default to {@link #Dynamic}. + */ + Inherit, + /** + * Do not draw if we are not at least partially within the view frustum + * of the camera. This is determined via the defined + * Camera planes whether or not this Spatial should be culled. + */ + Dynamic, + /** + * Always cull this from the view, throwing away this object + * and any children from rendering commands. + */ + Always, + /** + * Never cull this from view, always draw it. + * Note that we will still get culled if our parent is culled. + */ + Never; + } + + /** + * Specifies if this spatial should be batched + */ + public enum BatchHint { + + /** + * Do whatever our parent does. If no parent, default to {@link #Always}. + */ + Inherit, + /** + * This spatial will always be batched when attached to a BatchNode. + */ + Always, + /** + * This spatial will never be batched when attached to a BatchNode. + */ + Never; + } + /** + * Refresh flag types + */ + protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms + RF_BOUND = 0x02, + RF_LIGHTLIST = 0x04; // changes in light lists + + protected CullHint cullHint = CullHint.Inherit; + protected BatchHint batchHint = BatchHint.Inherit; + /** + * Spatial's bounding volume relative to the world. + */ + protected BoundingVolume worldBound; + /** + * LightList + */ + protected LightList localLights; + protected transient LightList worldLights; + /** + * This spatial's name. + */ + protected String name; + // scale values + protected transient Camera.FrustumIntersect frustrumIntersects = Camera.FrustumIntersect.Intersects; + protected RenderQueue.Bucket queueBucket = RenderQueue.Bucket.Inherit; + protected ShadowMode shadowMode = RenderQueue.ShadowMode.Inherit; + public transient float queueDistance = Float.NEGATIVE_INFINITY; + protected Transform localTransform; + protected Transform worldTransform; + protected SafeArrayList controls = new SafeArrayList(Control.class); + protected HashMap userData = null; + /** + * Used for smart asset caching + * + * @see AssetKey#useSmartCache() + */ + protected AssetKey key; + /** + * Spatial's parent, or null if it has none. + */ + protected transient Node parent; + /** + * Refresh flags. Indicate what data of the spatial need to be + * updated to reflect the correct state. + */ + protected transient int refreshFlags = 0; + + /** + * Serialization only. Do not use. + */ + public Spatial() { + localTransform = new Transform(); + worldTransform = new Transform(); + + localLights = new LightList(this); + worldLights = new LightList(this); + + refreshFlags |= RF_BOUND; + } + + /** + * Constructor instantiates a new 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 comparison purposes. + */ + public Spatial(String name) { + this(); + this.name = name; + } + + public void setKey(AssetKey key) { + this.key = key; + } + + public AssetKey getKey() { + return key; + } + + /** + * Indicate that the transform of this spatial has changed and that + * a refresh is required. + */ + protected void setTransformRefresh() { + refreshFlags |= RF_TRANSFORM; + setBoundRefresh(); + } + + protected void setLightListRefresh() { + refreshFlags |= RF_LIGHTLIST; + } + + /** + * Indicate that the bounding of this spatial has changed and that + * a refresh is required. + */ + protected void setBoundRefresh() { + refreshFlags |= RF_BOUND; + + Spatial p = parent; + while (p != null) { + if ((p.refreshFlags & RF_BOUND) != 0) { + return; + } + + p.refreshFlags |= RF_BOUND; + p = p.parent; + } + } + + /** + * (Internal use only) Forces a refresh of the given types of data. + * + * @param transforms Refresh world transform based on parents' + * @param bounds Refresh bounding volume data based on child nodes + * @param lights Refresh light list based on parents' + */ + public void forceRefresh(boolean transforms, boolean bounds, boolean lights) { + if (transforms) { + setTransformRefresh(); + } + if (bounds) { + setBoundRefresh(); + } + if (lights) { + setLightListRefresh(); + } + } + + /** + * 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" + + "State was changed after rootNode.updateGeometricState() call. \n" + + "Make sure you do not modify the scene from another thread!\n" + + "Problem spatial name: " + getName()); + } + + CullHint cm = getCullHint(); + assert cm != CullHint.Inherit; + if (cm == Spatial.CullHint.Always) { + setLastFrustumIntersection(Camera.FrustumIntersect.Outside); + return false; + } else if (cm == Spatial.CullHint.Never) { + setLastFrustumIntersection(Camera.FrustumIntersect.Intersects); + return true; + } + + // check to see if we can cull this node + frustrumIntersects = (parent != null ? parent.frustrumIntersects + : Camera.FrustumIntersect.Intersects); + + if (frustrumIntersects == Camera.FrustumIntersect.Intersects) { + if (getQueueBucket() == Bucket.Gui) { + return cam.containsGui(getWorldBound()); + } else { + frustrumIntersects = cam.contains(getWorldBound()); + } + } + + return frustrumIntersects != Camera.FrustumIntersect.Outside; + } + + /** + * Sets the name of this spatial. + * + * @param name + * The spatial's new name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the name of this spatial. + * + * @return This spatial's name. + */ + public String getName() { + return name; + } + + /** + * Returns the local {@link LightList}, which are the lights + * that were directly attached to this Spatial through the + * {@link #addLight(com.jme3.light.Light) } and + * {@link #removeLight(com.jme3.light.Light) } methods. + * + * @return The local light list + */ + public LightList getLocalLightList() { + return localLights; + } + + /** + * Returns the world {@link LightList}, containing the lights + * combined from all this Spatial's parents up to and including + * this Spatial's lights. + * + * @return The combined world light list + */ + public LightList getWorldLightList() { + return worldLights; + } + + /** + * getWorldRotation retrieves the absolute rotation of the + * Spatial. + * + * @return the Spatial's world rotation quaternion. + */ + public Quaternion getWorldRotation() { + checkDoTransformUpdate(); + return worldTransform.getRotation(); + } + + /** + * getWorldTranslation retrieves the absolute translation of + * the spatial. + * + * @return the Spatial's world tranlsation vector. + */ + public Vector3f getWorldTranslation() { + checkDoTransformUpdate(); + return worldTransform.getTranslation(); + } + + /** + * getWorldScale retrieves the absolute scale factor of the + * spatial. + * + * @return the Spatial's world 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 utility function that alters the + * local rotation to point the Y axis in the direction given by newUp. + * + * @param newUp + * the up vector to use - assumed to be a unit vector. + */ + public void rotateUpTo(Vector3f newUp) { + TempVars vars = TempVars.get(); + + Vector3f compVecA = vars.vect1; + Quaternion q = vars.quat1; + + // First figure out the current up vector. + Vector3f upY = compVecA.set(Vector3f.UNIT_Y); + Quaternion rot = localTransform.getRotation(); + rot.multLocal(upY); + + // get angle between vectors + float angle = upY.angleBetween(newUp); + + // figure out rotation axis by taking cross product + Vector3f rotAxis = upY.crossLocal(newUp).normalizeLocal(); + + // Build a rotation quat and apply current local rotation. + q.fromAngleNormalAxis(angle, rotAxis); + q.mult(rot, rot); + + vars.release(); + + setTransformRefresh(); + } + + /** + * lookAt is a convenience method for auto-setting the local + * rotation based on a position in world space and an up vector. It computes the rotation + * to transform the z-axis to point onto 'position' and the y-axis to 'up'. + * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) } + * this method takes a world position to look at and not a relative direction. + * + * Note : 28/01/2013 this method has been fixed as it was not taking into account the parent rotation. + * This was resulting in improper rotation when the spatial had rotated parent nodes. + * This method is intended to work in world space, so no matter what parent graph the + * spatial has, it will look at the given position in world space. + * + * @param position + * where to look at in terms of world coordinates + * @param upVector + * a vector indicating the (local) up direction. (typically {0, + * 1, 0} in jME.) + */ + public void lookAt(Vector3f position, Vector3f upVector) { + Vector3f worldTranslation = getWorldTranslation(); + + TempVars vars = TempVars.get(); + + Vector3f compVecA = vars.vect4; + + compVecA.set(position).subtractLocal(worldTranslation); + getLocalRotation().lookAt(compVecA, upVector); + + if ( getParent() != null ) { + Quaternion rot=vars.quat1; + rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation()); + rot.normalizeLocal(); + setLocalRotation(rot); + } + vars.release(); + setTransformRefresh(); + } + + /** + * Should be overridden by Node and Geometry. + */ + protected void updateWorldBound() { + // the world bound of a leaf is the same as it's model bound + // for a node, the world bound is a combination of all it's children + // bounds + // -> handled by subclass + refreshFlags &= ~RF_BOUND; + } + + protected void updateWorldLightList() { + if (parent == null) { + worldLights.update(localLights, null); + refreshFlags &= ~RF_LIGHTLIST; + } else { + if ((parent.refreshFlags & RF_LIGHTLIST) == 0) { + worldLights.update(localLights, parent.worldLights); + refreshFlags &= ~RF_LIGHTLIST; + } else { + assert false; + } + } + } + + /** + * Should only be called from updateGeometricState(). + * In most cases should not be subclassed. + */ + protected void updateWorldTransforms() { + if (parent == null) { + worldTransform.set(localTransform); + refreshFlags &= ~RF_TRANSFORM; + } else { + // check if transform for parent is updated + assert ((parent.refreshFlags & RF_TRANSFORM) == 0); + worldTransform.set(localTransform); + worldTransform.combineWithParent(parent.worldTransform); + refreshFlags &= ~RF_TRANSFORM; + } + } + + /** + * Computes the world transform of this Spatial in the most + * efficient manner possible. + */ + void checkDoTransformUpdate() { + if ((refreshFlags & RF_TRANSFORM) == 0) { + return; + } + + if (parent == null) { + worldTransform.set(localTransform); + refreshFlags &= ~RF_TRANSFORM; + } else { + TempVars vars = TempVars.get(); + + Spatial[] stack = vars.spatialStack; + Spatial rootNode = this; + int i = 0; + while (true) { + Spatial hisParent = rootNode.parent; + if (hisParent == null) { + rootNode.worldTransform.set(rootNode.localTransform); + rootNode.refreshFlags &= ~RF_TRANSFORM; + i--; + break; + } + + stack[i] = rootNode; + + if ((hisParent.refreshFlags & RF_TRANSFORM) == 0) { + break; + } + + rootNode = hisParent; + i++; + } + + vars.release(); + + for (int j = i; j >= 0; j--) { + rootNode = stack[j]; + //rootNode.worldTransform.set(rootNode.localTransform); + //rootNode.worldTransform.combineWithParent(rootNode.parent.worldTransform); + //rootNode.refreshFlags &= ~RF_TRANSFORM; + rootNode.updateWorldTransforms(); + } + } + } + + /** + * Computes this Spatial's world bounding volume in the most efficient + * manner possible. + */ + void checkDoBoundUpdate() { + if ((refreshFlags & RF_BOUND) == 0) { + return; + } + + checkDoTransformUpdate(); + + // Go to children recursively and update their bound + if (this instanceof Node) { + Node node = (Node) this; + int len = node.getQuantity(); + for (int i = 0; i < len; i++) { + Spatial child = node.getChild(i); + child.checkDoBoundUpdate(); + } + } + + // All children's bounds have been updated. Update my own now. + updateWorldBound(); + } + + private void runControlUpdate(float tpf) { + if (controls.isEmpty()) { + return; + } + + for (Control c : controls.getArray()) { + c.update(tpf); + } + } + + /** + * Called when the Spatial is about to be rendered, to notify + * controls attached to this Spatial using the Control.render() method. + * + * @param rm The RenderManager rendering the Spatial. + * @param vp The ViewPort to which the Spatial is being rendered to. + * + * @see Spatial#addControl(com.jme3.scene.control.Control) + * @see Spatial#getControl(java.lang.Class) + */ + public void runControlRender(RenderManager rm, ViewPort vp) { + if (controls.isEmpty()) { + return; + } + + for (Control c : controls.getArray()) { + c.render(rm, vp); + } + } + + /** + * Add a control to the list of controls. + * @param control The control to add. + * + * @see Spatial#removeControl(java.lang.Class) + */ + public void addControl(Control control) { + controls.add(control); + control.setSpatial(this); + } + + /** + * Removes the first control that is an instance of the given class. + * + * @see Spatial#addControl(com.jme3.scene.control.Control) + */ + public void removeControl(Class 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 (Control c : controls.getArray()) { + if (controlType.isAssignableFrom(c.getClass())) { + return (T) c; + } + } + return null; + } + + /** + * Returns the control at the given index in the list. + * + * @param index The index of the control in the list to find. + * @return The control at the given index. + * + * @throws IndexOutOfBoundsException + * If the index is outside the range [0, getNumControls()-1] + * + * @see Spatial#addControl(com.jme3.scene.control.Control) + */ + public Control getControl(int index) { + return controls.get(index); + } + + /** + * @return The number of controls attached to this Spatial. + * @see Spatial#addControl(com.jme3.scene.control.Control) + * @see Spatial#removeControl(java.lang.Class) + */ + public int getNumControls() { + return controls.size(); + } + + /** + * 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) { + checkDoTransformUpdate(); + return worldTransform.transformVector(in, store); + } + + /** + * Convert a vector (in) from world coordinate space to this spatials' local + * coordinate space. + * + * @param in + * vector to read from + * @param store + * where to write the result + * @return the result (store) + */ + public Vector3f worldToLocal(final Vector3f in, final Vector3f store) { + checkDoTransformUpdate(); + return worldTransform.transformInverseVector(in, store); + } + + /** + * getParent retrieves this node's parent. If the parent is + * null this is the root node. + * + * @return the parent of this node. + */ + public Node getParent() { + return parent; + } + + /** + * Called by {@link Node#attachChild(Spatial)} and + * {@link Node#detachChild(Spatial)} - don't call directly. + * 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 + * by using a {@link Matrix3f}. + * + * @param rotation + * the new local rotation. + */ + public void setLocalRotation(Matrix3f rotation) { + localTransform.getRotation().fromRotationMatrix(rotation); + setTransformRefresh(); + } + + /** + * setLocalRotation sets the local rotation of this node. + * + * @param quaternion + * the new local rotation. + */ + public void setLocalRotation(Quaternion quaternion) { + localTransform.setRotation(quaternion); + 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); + setTransformRefresh(); + } + + /** + * setLocalScale sets the local scale of this node. + */ + public void setLocalScale(float x, float y, float z) { + localTransform.setScale(x, y, z); + setTransformRefresh(); + } + + /** + * setLocalScale sets the local scale of this node. + * + * @param localScale + * the new local scale. + */ + public void setLocalScale(Vector3f localScale) { + localTransform.setScale(localScale); + 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); + setTransformRefresh(); + } + + /** + * setLocalTranslation sets the local translation of this + * spatial. + */ + public void setLocalTranslation(float x, float y, float z) { + this.localTransform.setTranslation(x, y, z); + 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); + 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); + 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); + 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); + setTransformRefresh(); + + return this; + } + + /** + * Rotates the spatial by the xAngle, yAngle and zAngle angles (in radians), + * (aka pitch, yaw, roll) in the local coordinate space. + * + * @return The spatial on which this method is called, e.g this. + */ + public Spatial rotate(float xAngle, float yAngle, float zAngle) { + TempVars vars = TempVars.get(); + Quaternion q = vars.quat1; + q.fromAngles(xAngle, yAngle, zAngle); + rotate(q); + vars.release(); + + return this; + } + + /** + * Centers the spatial in the origin of the world bound. + * @return The spatial on which this method is called, e.g 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; + } + } + + public BatchHint getBatchHint() { + if (batchHint != BatchHint.Inherit) { + return batchHint; + } else if (parent != null) { + return parent.getBatchHint(); + } else { + return BatchHint.Always; + } + } + + /** + * Returns this spatial's renderqueue bucket. If the mode is set to inherit, + * then the spatial gets its renderqueue bucket from its parent. + * + * @return The spatial's current renderqueue mode. + */ + public RenderQueue.Bucket getQueueBucket() { + if (queueBucket != RenderQueue.Bucket.Inherit) { + return queueBucket; + } else if (parent != null) { + return parent.getQueueBucket(); + } else { + return RenderQueue.Bucket.Opaque; + } + } + + /** + * @return The shadow mode of this spatial, if the local shadow + * mode is set to inherit, then the parent's shadow mode is returned. + * + * @see Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) + * @see ShadowMode + */ + public RenderQueue.ShadowMode getShadowMode() { + if (shadowMode != RenderQueue.ShadowMode.Inherit) { + return shadowMode; + } else if (parent != null) { + return parent.getShadowMode(); + } else { + return ShadowMode.Off; + } + } + + /** + * Sets the level of detail to use when rendering this Spatial, + * this call propagates to all geometries under this Spatial. + * + * @param lod The lod level to set. + */ + public void setLodLevel(int lod) { + } + + /** + * 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); + + // No need to force cloned to update. + // This node already has the refresh flags + // set below so it will have to update anyway. + clone.worldTransform = worldTransform.clone(); + clone.localTransform = localTransform.clone(); + + if (clone instanceof Node) { + Node node = (Node) this; + Node nodeClone = (Node) clone; + nodeClone.children = new SafeArrayList(Spatial.class); + for (Spatial child : node.children) { + Spatial childClone = child.clone(cloneMaterial); + childClone.parent = nodeClone; + nodeClone.children.add(childClone); + } + } + + clone.parent = null; + clone.setBoundRefresh(); + clone.setTransformRefresh(); + clone.setLightListRefresh(); + + clone.controls = new SafeArrayList(Control.class); + for (int i = 0; i < controls.size(); i++) { + Control newControl = controls.get(i).cloneForSpatial(clone); + newControl.setSpatial(clone); + clone.controls.add(newControl); + } + + 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 == null){ + userData.remove(key); + }else if (data instanceof Savable) { + userData.put(key, (Savable) data); + } else { + userData.put(key, new UserData(UserData.getObjectType(data), data)); + } + } + + @SuppressWarnings("unchecked") + public T getUserData(String key) { + if (userData == null) { + return null; + } + + Savable s = userData.get(key); + if (s instanceof UserData) { + return (T) ((UserData) s).getValue(); + } else { + return (T) s; + } + } + + public Collection 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(batchHint, "batch_hint", BatchHint.Inherit); + capsule.write(queueBucket, "queue", RenderQueue.Bucket.Inherit); + capsule.write(shadowMode, "shadow_mode", ShadowMode.Inherit); + capsule.write(localTransform, "transform", Transform.IDENTITY); + capsule.write(localLights, "lights", null); + + // Shallow clone the controls array to convert its type. + capsule.writeSavableArrayList(new ArrayList(controls), "controlsList", null); + capsule.writeStringSavableMap(userData, "user_data", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + + name = ic.readString("name", null); + worldBound = (BoundingVolume) ic.readSavable("world_bound", null); + cullHint = ic.readEnum("cull_mode", CullHint.class, CullHint.Inherit); + batchHint = ic.readEnum("batch_hint", BatchHint.class, BatchHint.Inherit); + queueBucket = ic.readEnum("queue", RenderQueue.Bucket.class, + RenderQueue.Bucket.Inherit); + shadowMode = ic.readEnum("shadow_mode", ShadowMode.class, + ShadowMode.Inherit); + + localTransform = (Transform) ic.readSavable("transform", Transform.IDENTITY); + + localLights = (LightList) ic.readSavable("lights", null); + localLights.setOwner(this); + + //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split + //the AnimControl creates the SkeletonControl for old files and add it to the spatial. + //The SkeletonControl must be the last in the stack so we add the list of all other control before it. + //When backward compatibility won't be needed anymore this can be replaced by : + //controls = ic.readSavableArrayList("controlsList", null)); + controls.addAll(0, ic.readSavableArrayList("controlsList", null)); + + userData = (HashMap) 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 alters how view frustum culling will treat this + * spatial. + * + * @param hint one of: CullHint.Dynamic, + * CullHint.Always, CullHint.Inherit, or + * CullHint.Never + *

      + * The effect of the default value (CullHint.Inherit) may change if the + * spatial gets re-parented. + */ + public void setCullHint(CullHint hint) { + cullHint = hint; + } + + /** + * setBatchHint alters how batching will treat this spatial. + * + * @param hint one of: BatchHint.Never, + * BatchHint.Always, or BatchHint.Inherit + *

      + * The effect of the default value (BatchHint.Inherit) may change if the + * spatial gets re-parented. + */ + public void setBatchHint(BatchHint hint) { + batchHint = hint; + } + + /** + * @return the cullmode set on this Spatial + */ + public CullHint getLocalCullHint() { + return cullHint; + } + + /** + * @return the batchHint set on this Spatial + */ + public BatchHint getLocalBatchHint() { + return batchHint; + } + + /** + * setQueueBucket determines at what phase of the + * rendering process this Spatial will rendered. See the + * {@link Bucket} enum for an explanation of the various + * render queue buckets. + * + * @param queueBucket + * The bucket to use for this Spatial. + */ + public void setQueueBucket(RenderQueue.Bucket queueBucket) { + this.queueBucket = queueBucket; + } + + /** + * Sets the shadow mode of the spatial + * The shadow mode determines how the spatial should be shadowed, + * when a shadowing technique is used. See the + * documentation for the class {@link ShadowMode} for more information. + * + * @see ShadowMode + * + * @param shadowMode The local shadow mode to set. + */ + public void setShadowMode(RenderQueue.ShadowMode shadowMode) { + this.shadowMode = shadowMode; + } + + /** + * @return The locally set queue bucket mode + * + * @see Spatial#setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket) + */ + public RenderQueue.Bucket getLocalQueueBucket() { + return queueBucket; + } + + /** + * @return The locally set shadow mode + * + * @see Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) + */ + public RenderQueue.ShadowMode getLocalShadowMode() { + return shadowMode; + } + + /** + * Returns this spatial's last frustum intersection result. This int is set + * when a check is made to determine if the bounds of the object fall inside + * a camera's frustum. If a parent is found to fall outside the frustum, the + * value for this spatial will not be updated. + * + * @return The spatial's last frustum intersection result. + */ + public Camera.FrustumIntersect getLastFrustumIntersection() { + return frustrumIntersects; + } + + /** + * Overrides the last intersection result. This is useful for operations + * that want to start rendering at the middle of a scene tree and don't want + * the parent of that node to influence culling. + * + * @param intersects + * the new value + */ + public void setLastFrustumIntersection(Camera.FrustumIntersect intersects) { + frustrumIntersects = intersects; + } + + /** + * Returns the Spatial's name followed by the class of the spatial
      + * 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/jme3-core/src/main/java/com/jme3/scene/UserData.java b/jme3-core/src/main/java/com/jme3/scene/UserData.java new file mode 100644 index 000000000..4b5d70404 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/UserData.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import com.jme3.export.*; +import java.io.IOException; + +/** + * UserData is used to contain user data objects + * set on spatials (primarily primitives) that do not implement + * the {@link Savable} interface. Note that attempting + * to export any models which have non-savable objects + * attached to them will fail. + */ +public final class UserData implements Savable { + + /** + * Boolean type on Geometries to indicate that physics collision + * shape generation should ignore them. + */ + public static final String JME_PHYSICSIGNORE = "JmePhysicsIgnore"; + + /** + * For geometries using shared mesh, this will specify the shared + * mesh reference. + */ + public static final String JME_SHAREDMESH = "JmeSharedMesh"; + + protected byte type; + protected Object value; + + public UserData() { + } + + /** + * Creates a new UserData with the given + * type and value. + * + * @param type Type of data, should be between 0 and 4. + * @param value Value of the data + */ + public UserData(byte type, Object value) { + assert type >= 0 && type <= 4; + this.type = type; + this.value = value; + } + + public Object getValue() { + return value; + } + + @Override + public String toString() { + return value.toString(); + } + + public static byte getObjectType(Object type) { + if (type instanceof Integer) { + return 0; + } else if (type instanceof Float) { + return 1; + } else if (type instanceof Boolean) { + return 2; + } else if (type instanceof String) { + return 3; + } else if (type instanceof Long) { + return 4; + } else { + throw new IllegalArgumentException("Unsupported type: " + type.getClass().getName()); + } + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(type, "type", (byte)0); + + switch (type) { + case 0: + int i = (Integer) value; + oc.write(i, "intVal", 0); + break; + case 1: + float f = (Float) value; + oc.write(f, "floatVal", 0f); + break; + case 2: + boolean b = (Boolean) value; + oc.write(b, "boolVal", false); + break; + case 3: + String s = (String) value; + oc.write(s, "strVal", null); + break; + case 4: + Long l = (Long) value; + oc.write(l, "longVal", 0l); + break; + default: + throw new UnsupportedOperationException(); + } + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + type = ic.readByte("type", (byte) 0); + + switch (type) { + case 0: + value = ic.readInt("intVal", 0); + break; + case 1: + value = ic.readFloat("floatVal", 0f); + break; + case 2: + value = ic.readBoolean("boolVal", false); + break; + case 3: + value = ic.readString("strVal", null); + break; + case 4: + value = ic.readLong("longVal", 0l); + break; + default: + throw new UnsupportedOperationException(); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java new file mode 100644 index 000000000..59bb66411 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java @@ -0,0 +1,1100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import com.jme3.export.*; +import com.jme3.math.FastMath; +import com.jme3.renderer.Renderer; +import com.jme3.util.BufferUtils; +import com.jme3.util.NativeObject; +import java.io.IOException; +import java.nio.*; + +/** + * A 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. + *

      + * Several terms are used throughout the javadoc for this class, explanation: + *

        + *
      • Element - A single element is the largest individual object + * inside a VertexBuffer. E.g. if the VertexBuffer is used to store 3D position + * data, then an element will be a single 3D vector.
      • + *
      • Component - A component represents the parts inside an element. + * For a 3D vector, a single component is one of the dimensions, X, Y or Z.
      • + *
      + */ +public class VertexBuffer extends NativeObject implements Savable, Cloneable { + + /** + * Type of buffer. Specifies the actual attribute it defines. + */ + public static enum Type { + /** + * Position of the vertex (3 floats) + */ + Position, + + /** + * The size of the point when using point buffers (float). + */ + Size, + + /** + * Normal vector, normalized (3 floats). + */ + Normal, + + /** + * Texture coordinate (2 float) + */ + TexCoord, + + /** + * Color and Alpha (4 floats) + */ + Color, + + /** + * Tangent vector, normalized (4 floats) (x,y,z,w) + * the w component is called the binormal parity, is not normalized and is either 1f or -1f + * It's used to compuste the direction on the binormal verctor on the GPU at render time. + */ + Tangent, + + /** + * Binormal vector, normalized (3 floats, optional) + */ + Binormal, + + /** + * Specifies the source data for various vertex buffers + * when interleaving is used. By default the format is + * byte. + */ + InterleavedData, + + /** + * Do not use. + */ + @Deprecated + MiscAttrib, + + /** + * Specifies the index buffer, must contain integer data + * (ubyte, ushort, or uint). + */ + Index, + + /** + * Initial vertex position, used with animation. + * Should have the same format and size as {@link Type#Position}. + * If used with software skinning, the usage should be + * {@link Usage#CpuOnly}, and the buffer should be allocated + * on the heap. + */ + BindPosePosition, + + /** + * Initial vertex normals, used with animation. + * Should have the same format and size as {@link Type#Normal}. + * If used with software skinning, the usage should be + * {@link Usage#CpuOnly}, and the buffer should be allocated + * on the heap. + */ + BindPoseNormal, + + /** + * Bone weights, used with animation (4 floats). + * Only used for software skinning, the usage should be + * {@link Usage#CpuOnly}, and the buffer should be allocated + * on the heap. + */ + BoneWeight, + + /** + * Bone indices, used with animation (4 ubytes). + * Only used for software skinning, the usage should be + * {@link Usage#CpuOnly}, and the buffer should be allocated + * on the heap as a ubytes buffer. + */ + BoneIndex, + + /** + * Texture coordinate #2 + */ + TexCoord2, + + /** + * Texture coordinate #3 + */ + TexCoord3, + + /** + * Texture coordinate #4 + */ + TexCoord4, + + /** + * Texture coordinate #5 + */ + TexCoord5, + + /** + * Texture coordinate #6 + */ + TexCoord6, + + /** + * Texture coordinate #7 + */ + TexCoord7, + + /** + * Texture coordinate #8 + */ + TexCoord8, + + /** + * Initial vertex tangents, used with animation. + * Should have the same format and size as {@link Type#Tangent}. + * If used with software skinning, the usage should be + * {@link Usage#CpuOnly}, and the buffer should be allocated + * on the heap. + */ + BindPoseTangent, + + /** + * Bone weights, used with animation (4 floats). + * for Hardware Skinning only + */ + HWBoneWeight, + + /** + * Bone indices, used with animation (4 ubytes). + * for Hardware Skinning only + * either an int or float buffer due to shader attribute types restrictions. + */ + HWBoneIndex, + } + + /** + * 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; + } + + /** + * Specifies format of the data stored in the buffer. + * This should directly correspond to the buffer's class, for example, + * an {@link Format#UnsignedShort} formatted buffer should use the + * class {@link ShortBuffer} (e.g. the closest resembling type). + * For the {@link Format#Half} type, {@link ByteBuffer}s should + * be used. + */ + public static enum Format { + /** + * Half precision floating point. + * 2 bytes, signed. + */ + Half(2), + + /** + * Single precision floating point. + * 4 bytes, signed + */ + Float(4), + + /** + * Double precision floating point. + * 8 bytes, signed. May not + * be supported by all GPUs. + */ + Double(8), + + /** + * 1 byte integer, signed. + */ + Byte(1), + + /** + * 1 byte integer, unsigned. + */ + UnsignedByte(1), + + /** + * 2 byte integer, signed. + */ + Short(2), + + /** + * 2 byte integer, unsigned. + */ + UnsignedShort(2), + + /** + * 4 byte integer, signed. + */ + Int(4), + + /** + * 4 byte integer, unsigned. + */ + UnsignedInt(4); + + private int componentSize = 0; + + Format(int componentSize){ + this.componentSize = componentSize; + } + + /** + * Returns the size in bytes of this data type. + * + * @return Size in bytes of this data type. + */ + public int getComponentSize(){ + return componentSize; + } + } + + protected int offset = 0; + protected int lastLimit = 0; + protected int stride = 0; + protected int components = 0; + + /** + * derived from components * format.getComponentSize() + */ + protected transient int componentsLength = 0; + protected Buffer data = null; + protected Usage usage; + protected Type bufType; + protected Format format; + protected boolean normalized = false; + protected transient boolean dataSizeChanged = false; + + /** + * Creates an empty, uninitialized buffer. + * Must call setupData() to initialize. + */ + public VertexBuffer(Type type){ + super(); + this.bufType = type; + } + + /** + * Serialization only. Do not use. + */ + public VertexBuffer(){ + super(); + } + + protected VertexBuffer(int id){ + super(id); + } + + public boolean invariant() { + // Does the VB hold any data? + if (data == null) { + throw new AssertionError(); + } + // Position must be 0. + if (data.position() != 0) { + throw new AssertionError(); + } + // Is the size of the VB == 0? + if (data.limit() == 0) { + throw new AssertionError(); + } + // Does offset exceed buffer limit or negative? + if (offset > data.limit() || offset < 0) { + throw new AssertionError(); + } + // Are components between 1 and 4? + if (components < 1 || components > 4) { + throw new AssertionError(); + } + + // Does usage comply with buffer directness? + //if (usage == Usage.CpuOnly && data.isDirect()) { + // throw new AssertionError(); + /*} else*/ if (usage != Usage.CpuOnly && !data.isDirect()) { + throw new AssertionError(); + } + + // Double/Char/Long buffers are not supported for VertexBuffers. + // For the rest, ensure they comply with the "Format" value. + if (data instanceof DoubleBuffer) { + throw new AssertionError(); + } else if (data instanceof CharBuffer) { + throw new AssertionError(); + } else if (data instanceof LongBuffer) { + throw new AssertionError(); + } else if (data instanceof FloatBuffer && format != Format.Float) { + throw new AssertionError(); + } else if (data instanceof IntBuffer && format != Format.Int && format != Format.UnsignedInt) { + throw new AssertionError(); + } else if (data instanceof ShortBuffer && format != Format.Short && format != Format.UnsignedShort) { + throw new AssertionError(); + } else if (data instanceof ByteBuffer && format != Format.Byte && format != Format.UnsignedByte) { + throw new AssertionError(); + } + return true; + } + + /** + * @return The offset after which the data is sent to the GPU. + * + * @see #setOffset(int) + */ + public int getOffset() { + return offset; + } + + /** + * @param offset Specify the offset (in bytes) from the start of the buffer + * after which the data is sent to the GPU. + */ + public void setOffset(int offset) { + this.offset = offset; + } + + /** + * @return The stride (in bytes) for the data. + * + * @see #setStride(int) + */ + public int getStride() { + return stride; + } + + /** + * Set the stride (in bytes) for the data. + *

      + * If the data is packed in the buffer, then stride is 0, if there's other + * data that is between the current component and the next component in the + * buffer, then this specifies the size in bytes of that additional data. + * + * @param stride the stride (in bytes) for the data + */ + public void setStride(int stride) { + this.stride = stride; + } + + /** + * Returns the raw internal data buffer used by this VertexBuffer. + * This buffer is not safe to call from multiple threads since buffers + * have their own internal position state that cannot be shared. + * Call getData().duplicate(), getData().asReadOnlyBuffer(), or + * the more convenient getDataReadOnly() if the buffer may be accessed + * from multiple threads. + * + * @return A native buffer, in the specified {@link Format format}. + */ + public Buffer getData(){ + return data; + } + + /** + * Returns a safe read-only version of this VertexBuffer's data. The + * contents of the buffer will reflect whatever changes are made on + * other threads (eventually) but these should not be used in that way. + * This method provides a read-only buffer that is safe to _read_ from + * a separate thread since it has its own book-keeping state (position, limit, etc.) + * + * @return A rewound native buffer in the specified {@link Format format} + * that is safe to read from a separate thread from other readers. + */ + public Buffer getDataReadOnly() { + + if (data == null) { + return null; + } + + // Create a read-only duplicate(). Note: this does not copy + // the underlying memory, it just creates a new read-only wrapper + // with its own buffer position state. + + // Unfortunately, this is not 100% straight forward since Buffer + // does not have an asReadOnlyBuffer() method. + Buffer result; + if( data instanceof ByteBuffer ) { + result = ((ByteBuffer)data).asReadOnlyBuffer(); + } else if( data instanceof FloatBuffer ) { + result = ((FloatBuffer)data).asReadOnlyBuffer(); + } else if( data instanceof ShortBuffer ) { + result = ((ShortBuffer)data).asReadOnlyBuffer(); + } else if( data instanceof IntBuffer ) { + result = ((IntBuffer)data).asReadOnlyBuffer(); + } else { + throw new UnsupportedOperationException( "Cannot get read-only view of buffer type:" + data ); + } + + // Make sure the caller gets a consistent view since we may + // have grabbed this buffer while another thread was reading + // the raw data. + result.rewind(); + + return result; + } + + /** + * @return The usage of this buffer. See {@link Usage} for more + * information. + */ + public Usage getUsage(){ + return usage; + } + + /** + * @param usage The usage of this buffer. See {@link Usage} for more + * information. + */ + public void setUsage(Usage usage){ +// if (id != -1) +// throw new UnsupportedOperationException("Data has already been sent. Cannot set usage."); + + this.usage = usage; + } + + /** + * @param normalized Set to true if integer components should be converted + * from their maximal range into the range 0.0 - 1.0 when converted to + * a floating-point value for the shader. + * E.g. if the {@link Format} is {@link Format#UnsignedInt}, then + * the components will be converted to the range 0.0 - 1.0 by dividing + * every integer by 2^32. + */ + public void setNormalized(boolean normalized){ + this.normalized = normalized; + } + + /** + * @return True if integer components should be converted to the range 0-1. + * @see VertexBuffer#setNormalized(boolean) + */ + public boolean isNormalized(){ + return normalized; + } + + /** + * @return The type of information that this buffer has. + */ + public Type getBufferType(){ + return bufType; + } + + /** + * @return The {@link Format format}, or data type of the data. + */ + public Format getFormat(){ + return format; + } + + /** + * @return The number of components of the given {@link Format format} per + * element. + */ + public int getNumComponents(){ + return components; + } + + /** + * @return The total number of data elements in the data buffer. + */ + public int getNumElements(){ + int elements = data.limit() / 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."); + + if (usage == null || format == null || data == null) + throw new IllegalArgumentException("None of the arguments can be null"); + + if (data.isReadOnly()) + throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." ); + + if (components < 1 || components > 4) + throw new IllegalArgumentException("components must be between 1 and 4"); + + this.data = data; + this.components = components; + this.usage = usage; + this.format = format; + this.componentsLength = components * format.getComponentSize(); + this.lastLimit = data.limit(); + setUpdateNeeded(); + } + + /** + * Called to update the data in the buffer with new data. Can only + * be called after {@link VertexBuffer#setupData(com.jme3.scene.VertexBuffer.Usage, int, com.jme3.scene.VertexBuffer.Format, java.nio.Buffer) } + * has been called. Note that it is fine to call this method on the + * data already set, e.g. vb.updateData(vb.getData()), this will just + * set the proper update flag indicating the data should be sent to the GPU + * again. + *

      + * It is allowed to specify a buffer with different capacity than the + * originally set buffer, HOWEVER, if you do so, you must + * call Mesh.updateCounts() otherwise bizarre errors can occur. + * + * @param data The data buffer to set + */ + public void updateData(Buffer data){ + if (id != -1){ + // request to update data is okay + } + + // Check if the data buffer is read-only which is a sign + // of a bug on the part of the caller + if (data != null && data.isReadOnly()) { + throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." ); + } + + // will force renderer to call glBufferData again + if (data != null && (this.data.getClass() != data.getClass() || data.limit() != lastLimit)){ + dataSizeChanged = true; + lastLimit = data.limit(); + } + + this.data = data; + setUpdateNeeded(); + } + + /** + * Returns true if the data size of the VertexBuffer has changed. + * Internal use only. + * @return true if the data size has changed + */ + public boolean hasDataSizeChanged() { + return dataSizeChanged; + } + + @Override + public void clearUpdateNeeded(){ + super.clearUpdateNeeded(); + dataSizeChanged = false; + } + + /** + * Converts single floating-point data to {@link Format#Half half} floating-point data. + */ + public void convertToHalf(){ + if (id != -1) + throw new UnsupportedOperationException("Data has already been sent."); + + if (format != Format.Float) + throw new IllegalStateException("Format must be float!"); + + int numElements = data.limit() / 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.limit(); i++){ + float f = floatData.get(i); + short half = FastMath.convertFloatToHalf(f); + halfData.putShort(half); + } + this.data = halfData; + setUpdateNeeded(); + dataSizeChanged = true; + } + + /** + * Reduces the capacity of the buffer to the given amount + * of elements, any elements at the end of the buffer are truncated + * as necessary. + * + * @param numElements The number of elements to reduce to. + */ + public void compact(int numElements){ + int total = components * numElements; + data.clear(); + switch (format){ + case Byte: + case UnsignedByte: + case Half: + ByteBuffer bbuf = (ByteBuffer) data; + bbuf.limit(total); + ByteBuffer bnewBuf = BufferUtils.createByteBuffer(total); + bnewBuf.put(bbuf); + data = bnewBuf; + break; + case Short: + case UnsignedShort: + ShortBuffer sbuf = (ShortBuffer) data; + sbuf.limit(total); + ShortBuffer snewBuf = BufferUtils.createShortBuffer(total); + snewBuf.put(sbuf); + data = snewBuf; + break; + case Int: + case UnsignedInt: + IntBuffer ibuf = (IntBuffer) data; + ibuf.limit(total); + IntBuffer inewBuf = BufferUtils.createIntBuffer(total); + inewBuf.put(ibuf); + data = inewBuf; + break; + case Float: + FloatBuffer fbuf = (FloatBuffer) data; + fbuf.limit(total); + FloatBuffer fnewBuf = BufferUtils.createFloatBuffer(total); + fnewBuf.put(fbuf); + data = fnewBuf; + break; + default: + throw new UnsupportedOperationException("Unrecognized buffer format: "+format); + } + data.clear(); + setUpdateNeeded(); + dataSizeChanged = true; + } + + /** + * Modify a component inside an element. + * The val parameter must be in the buffer's format: + * {@link Format}. + * + * @param elementIndex The element index to modify + * @param componentIndex The component index to modify + * @param val The value to set, either byte, short, int or float depending + * on the {@link Format}. + */ + public void setElementComponent(int elementIndex, int componentIndex, Object val){ + int inPos = elementIndex * components; + int elementPos = componentIndex; + + if (format == Format.Half){ + inPos *= 2; + elementPos *= 2; + } + + data.clear(); + + switch (format){ + case Byte: + case UnsignedByte: + case Half: + ByteBuffer bin = (ByteBuffer) data; + bin.put(inPos + elementPos, (Byte)val); + break; + case Short: + case UnsignedShort: + ShortBuffer sin = (ShortBuffer) data; + sin.put(inPos + elementPos, (Short)val); + break; + case Int: + case UnsignedInt: + IntBuffer iin = (IntBuffer) data; + iin.put(inPos + elementPos, (Integer)val); + break; + case Float: + FloatBuffer fin = (FloatBuffer) data; + fin.put(inPos + elementPos, (Float)val); + break; + default: + throw new UnsupportedOperationException("Unrecognized buffer format: "+format); + } + } + + /** + * Get the component inside an element. + * + * @param elementIndex The element index + * @param componentIndex The component index + * @return The component, as one of the primitive types, byte, short, + * int or float. + */ + public Object getElementComponent(int elementIndex, int componentIndex){ + int inPos = elementIndex * components; + int elementPos = componentIndex; + + if (format == Format.Half){ + inPos *= 2; + elementPos *= 2; + } + + Buffer srcData = getDataReadOnly(); + + switch (format){ + case Byte: + case UnsignedByte: + case Half: + ByteBuffer bin = (ByteBuffer) srcData; + return bin.get(inPos + elementPos); + case Short: + case UnsignedShort: + ShortBuffer sin = (ShortBuffer) srcData; + return sin.get(inPos + elementPos); + case Int: + case UnsignedInt: + IntBuffer iin = (IntBuffer) srcData; + return iin.get(inPos + elementPos); + case Float: + FloatBuffer fin = (FloatBuffer) srcData; + return fin.get(inPos + elementPos); + default: + throw new UnsupportedOperationException("Unrecognized buffer format: "+format); + } + } + + /** + * Copies a single element of data from this VertexBuffer + * to the given output VertexBuffer. + * + * @param inIndex The input element index + * @param outVb The buffer to copy to + * @param outIndex The output element index + * + * @throws IllegalArgumentException If the formats of the buffers do not + * match. + */ + public void copyElement(int inIndex, VertexBuffer outVb, int outIndex){ + copyElements(inIndex, outVb, outIndex, 1); + } + + /** + * Copies a sequence of elements of data from this VertexBuffer + * to the given output VertexBuffer. + * + * @param inIndex The input element index + * @param outVb The buffer to copy to + * @param outIndex The output element index + * @param len The number of elements to copy + * + * @throws IllegalArgumentException If the formats of the buffers do not + * match. + */ + public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){ + if (outVb.format != format || outVb.components != components) + throw new IllegalArgumentException("Buffer format mismatch. Cannot copy"); + + int inPos = inIndex * components; + int outPos = outIndex * components; + int elementSz = components; + if (format == Format.Half){ + // because half is stored as bytebuf but its 2 bytes long + inPos *= 2; + outPos *= 2; + elementSz *= 2; + } + + // Make sure to grab a read-only copy in case some other + // thread is also accessing the buffer and messing with its + // position() + Buffer srcData = getDataReadOnly(); + outVb.data.clear(); + + switch (format){ + case Byte: + case UnsignedByte: + case Half: + ByteBuffer bin = (ByteBuffer) srcData; + ByteBuffer bout = (ByteBuffer) outVb.data; + bin.position(inPos).limit(inPos + elementSz * len); + bout.position(outPos).limit(outPos + elementSz * len); + bout.put(bin); + break; + case Short: + case UnsignedShort: + ShortBuffer sin = (ShortBuffer) srcData; + ShortBuffer sout = (ShortBuffer) outVb.data; + sin.position(inPos).limit(inPos + elementSz * len); + sout.position(outPos).limit(outPos + elementSz * len); + sout.put(sin); + break; + case Int: + case UnsignedInt: + IntBuffer iin = (IntBuffer) srcData; + IntBuffer iout = (IntBuffer) outVb.data; + iin.position(inPos).limit(inPos + elementSz * len); + iout.position(outPos).limit(outPos + elementSz * len); + iout.put(iin); + break; + case Float: + FloatBuffer fin = (FloatBuffer) srcData; + FloatBuffer fout = (FloatBuffer) outVb.data; + fin.position(inPos).limit(inPos + elementSz * len); + fout.position(outPos).limit(outPos + elementSz * len); + fout.put(fin); + break; + default: + throw new UnsupportedOperationException("Unrecognized buffer format: "+format); + } + + // Clear the output buffer to rewind it and reset its + // limit from where we shortened it above. + outVb.data.clear(); + } + + /** + * Creates a {@link Buffer} that satisfies the given type and size requirements + * of the parameters. The buffer will be of the type specified by + * {@link Format format} and would be able to contain the given number + * of elements with the given number of components in each element. + */ + public static Buffer createBuffer(Format format, int components, int numElements){ + if (components < 1 || components > 4) + throw new IllegalArgumentException("Num components must be between 1 and 4"); + + int total = numElements * components; + + switch (format){ + case Byte: + case UnsignedByte: + return BufferUtils.createByteBuffer(total); + case Half: + return BufferUtils.createByteBuffer(total * 2); + case Short: + case UnsignedShort: + return BufferUtils.createShortBuffer(total); + case Int: + case UnsignedInt: + return BufferUtils.createIntBuffer(total); + case Float: + return BufferUtils.createFloatBuffer(total); + case Double: + return BufferUtils.createDoubleBuffer(total); + default: + throw new UnsupportedOperationException("Unrecoginized buffer format: "+format); + } + } + + /** + * Creates a deep clone of the {@link VertexBuffer}. + * + * @return Deep clone of this buffer + */ + @Override + public VertexBuffer clone(){ + // NOTE: Superclass GLObject automatically creates shallow clone + // e.g re-use ID. + VertexBuffer vb = (VertexBuffer) super.clone(); + vb.handleRef = new Object(); + vb.id = -1; + if (data != null) { + // Make sure to pass a read-only buffer to clone so that + // the position information doesn't get clobbered by another + // reading thread during cloning (and vice versa) since this is + // a purely read-only operation. + vb.updateData(BufferUtils.clone(getDataReadOnly())); + } + + return vb; + } + + /** + * Creates a deep clone of this VertexBuffer but overrides the + * {@link Type}. + * + * @param overrideType The type of the cloned VertexBuffer + * @return A deep clone of the buffer + */ + public VertexBuffer clone(Type overrideType){ + VertexBuffer vb = new VertexBuffer(overrideType); + vb.components = components; + vb.componentsLength = componentsLength; + + // Make sure to pass a read-only buffer to clone so that + // the position information doesn't get clobbered by another + // reading thread during cloning (and vice versa) since this is + // a purely read-only operation. + vb.data = BufferUtils.clone(getDataReadOnly()); + vb.format = format; + vb.handleRef = new Object(); + vb.id = -1; + vb.normalized = normalized; + vb.offset = offset; + vb.stride = stride; + vb.updateNeeded = true; + vb.usage = usage; + return vb; + } + + @Override + public String toString(){ + String dataTxt = null; + if (data != null){ + dataTxt = ", elements="+data.limit(); + } + return getClass().getSimpleName() + "[fmt="+format.name() + +", type="+bufType.name() + +", usage="+usage.name() + +dataTxt+"]"; + } + + @Override + public void resetObject() { +// assert this.id != -1; + this.id = -1; + setUpdateNeeded(); + } + + @Override + public void deleteObject(Object rendererObject) { + ((Renderer)rendererObject).deleteBuffer(this); + } + + @Override + protected void deleteNativeBuffers() { + if (data != null) { + BufferUtils.destroyDirectBuffer(data); + } + } + + @Override + public NativeObject createDestructableClone(){ + return new VertexBuffer(id); + } + + @Override + public long getUniqueId() { + return ((long)OBJTYPE_VERTEXBUFFER << 32) | ((long)id); + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(components, "components", 0); + oc.write(usage, "usage", Usage.Dynamic); + oc.write(bufType, "buffer_type", null); + oc.write(format, "format", Format.Float); + oc.write(normalized, "normalized", false); + oc.write(offset, "offset", 0); + oc.write(stride, "stride", 0); + + String dataName = "data" + format.name(); + Buffer roData = getDataReadOnly(); + switch (format){ + case Float: + oc.write((FloatBuffer) roData, dataName, null); + break; + case Short: + case UnsignedShort: + oc.write((ShortBuffer) roData, dataName, null); + break; + case UnsignedByte: + case Byte: + case Half: + oc.write((ByteBuffer) roData, dataName, null); + break; + case Int: + case UnsignedInt: + oc.write((IntBuffer) roData, dataName, null); + break; + default: + throw new IOException("Unsupported export buffer format: "+format); + } + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + components = ic.readInt("components", 0); + usage = ic.readEnum("usage", Usage.class, Usage.Dynamic); + bufType = ic.readEnum("buffer_type", Type.class, null); + format = ic.readEnum("format", Format.class, Format.Float); + normalized = ic.readBoolean("normalized", false); + offset = ic.readInt("offset", 0); + stride = ic.readInt("stride", 0); + componentsLength = components * format.getComponentSize(); + + String dataName = "data" + format.name(); + switch (format){ + case Float: + data = ic.readFloatBuffer(dataName, null); + break; + case Short: + case UnsignedShort: + data = ic.readShortBuffer(dataName, null); + break; + case UnsignedByte: + case Byte: + case Half: + data = ic.readByteBuffer(dataName, null); + break; + case Int: + case UnsignedInt: + data = ic.readIntBuffer(dataName, null); + break; + default: + throw new IOException("Unsupported import buffer format: "+format); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java b/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java new file mode 100644 index 000000000..43cae6db5 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.control; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Spatial; +import java.io.IOException; + +/** + * An abstract implementation of the Control interface. + * + * @author Kirill Vainer + */ +public abstract class AbstractControl implements Control { + + protected boolean enabled = true; + protected Spatial spatial; + + public AbstractControl(){ + } + + public void setSpatial(Spatial spatial) { + if (this.spatial != null && spatial != null && spatial != this.spatial) { + throw new IllegalStateException("This control has already been added to a Spatial"); + } + this.spatial = spatial; + } + + public Spatial getSpatial(){ + return spatial; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + /** + * To be implemented in subclass. + */ + protected abstract void controlUpdate(float tpf); + + /** + * To be implemented in subclass. + */ + protected abstract void controlRender(RenderManager rm, ViewPort vp); + + /** + * Default implementation of cloneForSpatial() that + * simply clones the control and sets the spatial. + *

      +     *  AbstractControl c = clone();
      +     *  c.spatial = null;
      +     *  c.setSpatial(spatial);
      +     *  
      + * + * Controls that wish to be persisted must be Cloneable. + */ + @Override + public Control cloneForSpatial(Spatial spatial) { + try { + AbstractControl c = (AbstractControl)clone(); + c.spatial = null; // to keep setSpatial() from throwing an exception + c.setSpatial(spatial); + return c; + } catch(CloneNotSupportedException e) { + throw new RuntimeException( "Can't clone control for spatial", e ); + } + } + + 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/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java b/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java new file mode 100644 index 000000000..7def0e252 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.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/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java b/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java new file mode 100644 index 000000000..7f54f901b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.control; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import java.io.IOException; + +public class BillboardControl extends AbstractControl { + + private Matrix3f orient; + private Vector3f look; + private Vector3f left; + private Alignment alignment; + + /** + * Determines how the billboard is aligned to the screen/camera. + */ + public enum Alignment { + /** + * Aligns this Billboard to the screen. + */ + Screen, + + /** + * Aligns this Billboard to the camera position. + */ + Camera, + + /** + * Aligns this Billboard to the screen, but keeps the Y axis fixed. + */ + AxialY, + + /** + * Aligns this Billboard to the screen, but keeps the Z axis fixed. + */ + AxialZ; + } + + public BillboardControl() { + super(); + orient = new Matrix3f(); + look = new Vector3f(); + left = new Vector3f(); + alignment = Alignment.Screen; + } + + public Control cloneForSpatial(Spatial spatial) { + BillboardControl control = new BillboardControl(); + control.alignment = this.alignment; + control.setSpatial(spatial); + return control; + } + + @Override + protected void controlUpdate(float tpf) { + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + Camera cam = vp.getCamera(); + rotateBillboard(cam); + } + + private void fixRefreshFlags(){ + // force transforms to update below this node + spatial.updateGeometricState(); + + // force world bound to update + Spatial rootNode = spatial; + while (rootNode.getParent() != null){ + rootNode = rootNode.getParent(); + } + rootNode.getWorldBound(); + } + + /** + * rotate the billboard based on the type set + * + * @param cam + * Camera + */ + private void rotateBillboard(Camera cam) { + switch (alignment) { + case AxialY: + rotateAxial(cam, Vector3f.UNIT_Y); + break; + case AxialZ: + rotateAxial(cam, Vector3f.UNIT_Z); + break; + case Screen: + rotateScreenAligned(cam); + break; + case Camera: + rotateCameraAligned(cam); + break; + } + } + + /** + * Aligns this Billboard so that it points to the camera position. + * + * @param camera + * Camera + */ + private void rotateCameraAligned(Camera camera) { + look.set(camera.getLocation()).subtractLocal( + spatial.getWorldTranslation()); + // coopt left for our own purposes. + Vector3f xzp = left; + // The xzp vector is the projection of the look vector on the xz plane + xzp.set(look.x, 0, look.z); + + // check for undefined rotation... + if (xzp.equals(Vector3f.ZERO)) { + return; + } + + look.normalizeLocal(); + xzp.normalizeLocal(); + float cosp = look.dot(xzp); + + // compute the local orientation matrix for the billboard + orient.set(0, 0, xzp.z); + orient.set(0, 1, xzp.x * -look.y); + orient.set(0, 2, xzp.x * cosp); + orient.set(1, 0, 0); + orient.set(1, 1, cosp); + orient.set(1, 2, look.y); + orient.set(2, 0, -xzp.x); + orient.set(2, 1, xzp.z * -look.y); + orient.set(2, 2, xzp.z * cosp); + + // The billboard must be oriented to face the camera before it is + // transformed into the world. + spatial.setLocalRotation(orient); + fixRefreshFlags(); + } + + /** + * Rotate the billboard so it points directly opposite the direction the + * camera's facing + * + * @param camera + * Camera + */ + private void rotateScreenAligned(Camera camera) { + // coopt diff for our in direction: + look.set(camera.getDirection()).negateLocal(); + // coopt loc for our left direction: + left.set(camera.getLeft()).negateLocal(); + orient.fromAxes(left, camera.getUp(), look); + Node parent = spatial.getParent(); + Quaternion rot=new Quaternion().fromRotationMatrix(orient); + if ( parent != null ) { + rot = parent.getWorldRotation().inverse().multLocal(rot); + rot.normalizeLocal(); + } + spatial.setLocalRotation(rot); + fixRefreshFlags(); + } + + /** + * Rotate the billboard towards the camera, but keeping a given axis fixed. + * + * @param camera + * Camera + */ + private void rotateAxial(Camera camera, Vector3f axis) { + // Compute the additional rotation required for the billboard to face + // the camera. To do this, the camera must be inverse-transformed into + // the model space of the billboard. + look.set(camera.getLocation()).subtractLocal( + spatial.getWorldTranslation()); + spatial.getParent().getWorldRotation().mult(look, left); // coopt left for our own + // purposes. + left.x *= 1.0f / spatial.getWorldScale().x; + left.y *= 1.0f / spatial.getWorldScale().y; + left.z *= 1.0f / spatial.getWorldScale().z; + + // squared length of the camera projection in the xz-plane + float lengthSquared = left.x * left.x + left.z * left.z; + if (lengthSquared < FastMath.FLT_EPSILON) { + // camera on the billboard axis, rotation not defined + return; + } + + // unitize the projection + float invLength = FastMath.invSqrt(lengthSquared); + if (axis.y == 1) { + left.x *= invLength; + left.y = 0.0f; + left.z *= invLength; + + // compute the local orientation matrix for the billboard + orient.set(0, 0, left.z); + orient.set(0, 1, 0); + orient.set(0, 2, left.x); + orient.set(1, 0, 0); + orient.set(1, 1, 1); + orient.set(1, 2, 0); + orient.set(2, 0, -left.x); + orient.set(2, 1, 0); + orient.set(2, 2, left.z); + } else if (axis.z == 1) { + left.x *= invLength; + left.y *= invLength; + left.z = 0.0f; + + // compute the local orientation matrix for the billboard + orient.set(0, 0, left.y); + orient.set(0, 1, left.x); + orient.set(0, 2, 0); + orient.set(1, 0, -left.y); + orient.set(1, 1, left.x); + orient.set(1, 2, 0); + orient.set(2, 0, 0); + orient.set(2, 1, 0); + orient.set(2, 2, 1); + } + + // The billboard must be oriented to face the camera before it is + // transformed into the world. + spatial.setLocalRotation(orient); + fixRefreshFlags(); + } + + /** + * Returns the alignment this Billboard is set too. + * + * @return The alignment of rotation, AxialY, AxialZ, Camera or Screen. + */ + public Alignment getAlignment() { + return alignment; + } + + /** + * Sets the type of rotation this Billboard will have. The alignment can + * be Camera, Screen, AxialY, or AxialZ. Invalid alignments will + * assume no billboard rotation. + */ + public void setAlignment(Alignment alignment) { + this.alignment = alignment; + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(orient, "orient", null); + capsule.write(look, "look", null); + capsule.write(left, "left", null); + capsule.write(alignment, "alignment", Alignment.Screen); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + orient = (Matrix3f) capsule.readSavable("orient", null); + look = (Vector3f) capsule.readSavable("look", null); + left = (Vector3f) capsule.readSavable("left", null); + alignment = capsule.readEnum("alignment", Alignment.class, Alignment.Screen); + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java b/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java new file mode 100644 index 000000000..a154cbc24 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.control; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Spatial; +import com.jme3.util.TempVars; +import java.io.IOException; + +/** + * This Control maintains a reference to a Camera, + * which will be synched with the position (worldTranslation) + * of the current spatial. + * @author tim + */ +public class CameraControl extends AbstractControl { + + public static enum ControlDirection { + + /** + * Means, that the Camera's transform is "copied" + * to the Transform of the Spatial. + */ + CameraToSpatial, + /** + * Means, that the Spatial's transform is "copied" + * to the Transform of the Camera. + */ + SpatialToCamera; + } + private Camera camera; + private ControlDirection controlDir = ControlDirection.SpatialToCamera; + + /** + * Constructor used for Serialization. + */ + public CameraControl() { + } + + /** + * @param camera The Camera to be synced. + */ + public CameraControl(Camera camera) { + this.camera = camera; + } + + /** + * @param camera The Camera to be synced. + */ + public CameraControl(Camera camera, ControlDirection controlDir) { + this.camera = camera; + this.controlDir = controlDir; + } + + public Camera getCamera() { + return camera; + } + + public void setCamera(Camera camera) { + this.camera = camera; + } + + public ControlDirection getControlDir() { + return controlDir; + } + + public void setControlDir(ControlDirection controlDir) { + this.controlDir = controlDir; + } + + // fields used, when inversing ControlDirection: + @Override + protected void controlUpdate(float tpf) { + if (spatial != null && camera != null) { + switch (controlDir) { + case SpatialToCamera: + camera.setLocation(spatial.getWorldTranslation()); + camera.setRotation(spatial.getWorldRotation()); + break; + case CameraToSpatial: + // set the localtransform, so that the worldtransform would be equal to the camera's transform. + // Location: + TempVars vars = TempVars.get(); + + Vector3f vecDiff = vars.vect1.set(camera.getLocation()).subtractLocal(spatial.getWorldTranslation()); + spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation())); + + // Rotation: + Quaternion worldDiff = vars.quat1.set(camera.getRotation()).subtractLocal(spatial.getWorldRotation()); + spatial.setLocalRotation(worldDiff.addLocal(spatial.getLocalRotation())); + vars.release(); + break; + } + } + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + // nothing to do + } + + @Override + public Control cloneForSpatial(Spatial newSpatial) { + CameraControl control = new CameraControl(camera, controlDir); + control.setSpatial(newSpatial); + control.setEnabled(isEnabled()); + return control; + } + private static final String CONTROL_DIR_NAME = "controlDir"; + private static final String CAMERA_NAME = "camera"; + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + controlDir = ic.readEnum(CONTROL_DIR_NAME, ControlDirection.class, ControlDirection.SpatialToCamera); + camera = (Camera)ic.readSavable(CAMERA_NAME, null); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(controlDir, CONTROL_DIR_NAME, ControlDirection.SpatialToCamera); + oc.write(camera, CAMERA_NAME, null); + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/scene/control/Control.java b/jme3-core/src/main/java/com/jme3/scene/control/Control.java new file mode 100644 index 000000000..63d7b43c8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/control/Control.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.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. + *

      + * Controls are used to specify certain update and render logic + * for a {@link Spatial}. + * + * @author Kirill Vainer + */ +public interface Control extends Savable { + + /** + * Creates a clone of the Control, the given Spatial is the cloned + * version of the spatial to which this control is attached to. + * @param spatial + * @return A clone of this control for the spatial + */ + public Control cloneForSpatial(Spatial spatial); + + /** + * @param spatial the spatial to be controlled. This should not be called + * from user code. + */ + public void setSpatial(Spatial spatial); + + /** + * 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/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java b/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java new file mode 100644 index 000000000..029cc1b9a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.control; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Spatial; +import com.jme3.util.TempVars; +import java.io.IOException; + +/** + * This Control maintains a reference to a Camera, + * which will be synched with the position (worldTranslation) + * of the current spatial. + * @author tim + */ +public class LightControl extends AbstractControl { + + public static enum ControlDirection { + + /** + * Means, that the Light's transform is "copied" + * to the Transform of the Spatial. + */ + LightToSpatial, + /** + * Means, that the Spatial's transform is "copied" + * to the Transform of the light. + */ + SpatialToLight; + } + private Light light; + private ControlDirection controlDir = ControlDirection.SpatialToLight; + + /** + * Constructor used for Serialization. + */ + public LightControl() { + } + + /** + * @param light The light to be synced. + */ + public LightControl(Light light) { + this.light = light; + } + + /** + * @param light The light to be synced. + */ + public LightControl(Light light, ControlDirection controlDir) { + this.light = light; + this.controlDir = controlDir; + } + + public Light getLight() { + return light; + } + + public void setLight(Light light) { + this.light = light; + } + + public ControlDirection getControlDir() { + return controlDir; + } + + public void setControlDir(ControlDirection controlDir) { + this.controlDir = controlDir; + } + + // fields used, when inversing ControlDirection: + @Override + protected void controlUpdate(float tpf) { + if (spatial != null && light != null) { + switch (controlDir) { + case SpatialToLight: + spatialTolight(light); + break; + case LightToSpatial: + lightToSpatial(light); + break; + } + } + } + + private void spatialTolight(Light light) { + if (light instanceof PointLight) { + ((PointLight) light).setPosition(spatial.getWorldTranslation()); + } + TempVars vars = TempVars.get(); + + if (light instanceof DirectionalLight) { + ((DirectionalLight) light).setDirection(vars.vect1.set(spatial.getWorldTranslation()).multLocal(-1.0f)); + } + + if (light instanceof SpotLight) { + ((SpotLight) light).setPosition(spatial.getWorldTranslation()); + ((SpotLight) light).setDirection(spatial.getWorldRotation().multLocal(vars.vect1.set(Vector3f.UNIT_Y).multLocal(-1))); + } + vars.release(); + + } + + private void lightToSpatial(Light light) { + TempVars vars = TempVars.get(); + if (light instanceof PointLight) { + + PointLight pLight = (PointLight) light; + + Vector3f vecDiff = vars.vect1.set(pLight.getPosition()).subtractLocal(spatial.getWorldTranslation()); + spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation())); + } + + if (light instanceof DirectionalLight) { + DirectionalLight dLight = (DirectionalLight) light; + vars.vect1.set(dLight.getDirection()).multLocal(-1.0f); + Vector3f vecDiff = vars.vect1.subtractLocal(spatial.getWorldTranslation()); + spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation())); + } + vars.release(); + //TODO add code for Spot light here when it's done + + + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + // nothing to do + } + + @Override + public Control cloneForSpatial(Spatial newSpatial) { + LightControl control = new LightControl(light, controlDir); + control.setSpatial(newSpatial); + control.setEnabled(isEnabled()); + return control; + } + private static final String CONTROL_DIR_NAME = "controlDir"; + private static final String LIGHT_NAME = "light"; + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + controlDir = ic.readEnum(CONTROL_DIR_NAME, ControlDirection.class, ControlDirection.SpatialToLight); + light = (Light)ic.readSavable(LIGHT_NAME, null); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(controlDir, CONTROL_DIR_NAME, ControlDirection.SpatialToLight); + oc.write(light, LIGHT_NAME, null); + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java b/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java new file mode 100644 index 000000000..030ccbb3a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.control; + +import com.jme3.bounding.BoundingVolume; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import java.io.IOException; + +/** + * Determines what Level of Detail a spatial should be, based on how many pixels + * on the screen the spatial is taking up. The more pixels covered, the more + * detailed the spatial should be. It calculates the area of the screen that the + * spatial covers by using its bounding box. When initializing, it will ask the + * spatial for how many triangles it has for each LOD. It then uses that, along + * with the trisPerPixel value to determine what LOD it should be at. It + * requires the camera to do this. The controlRender method is called each frame + * and will update the spatial's LOD if the camera has moved by a specified + * amount. + */ +public class LodControl extends AbstractControl implements Cloneable { + + private float trisPerPixel = 1f; + private float distTolerance = 1f; + private float lastDistance = 0f; + private int lastLevel = 0; + private int numLevels; + private int[] numTris; + + /** + * Creates a new + * LodControl. + */ + public LodControl() { + } + + /** + * Returns the distance tolerance for changing LOD. + * + * @return the distance tolerance for changing LOD. + * + * @see #setDistTolerance(float) + */ + public float getDistTolerance() { + return distTolerance; + } + + /** + * Specifies the distance tolerance for changing the LOD level on the + * geometry. The LOD level will only get changed if the geometry has moved + * this distance beyond the current LOD level. + * + * @param distTolerance distance tolerance for changing LOD + */ + public void setDistTolerance(float distTolerance) { + this.distTolerance = distTolerance; + } + + /** + * Returns the triangles per pixel value. + * + * @return the triangles per pixel value. + * + * @see #setTrisPerPixel(float) + */ + public float getTrisPerPixel() { + return trisPerPixel; + } + + /** + * Sets the triangles per pixel value. The + * LodControl will use this value as an error metric to + * determine which LOD level to use based on the geometry's area on the + * screen. + * + * @param trisPerPixel triangles per pixel + */ + public void setTrisPerPixel(float trisPerPixel) { + this.trisPerPixel = trisPerPixel; + } + + @Override + public void setSpatial(Spatial spatial) { + if (!(spatial instanceof Geometry)) { + throw new IllegalArgumentException("LodControl can only be attached to Geometry!"); + } + + super.setSpatial(spatial); + Geometry geom = (Geometry) spatial; + Mesh mesh = geom.getMesh(); + numLevels = mesh.getNumLodLevels(); + numTris = new int[numLevels]; + for (int i = numLevels - 1; i >= 0; i--) { + numTris[i] = mesh.getTriangleCount(i); + } + } + + @Override + public Control cloneForSpatial(Spatial spatial) { + LodControl clone = (LodControl) super.cloneForSpatial(spatial); + clone.lastDistance = 0; + clone.lastLevel = 0; + clone.numTris = numTris != null ? numTris.clone() : null; + return clone; + } + + @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/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java b/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java new file mode 100644 index 000000000..a52bfb6ee --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.control; + +import com.jme3.app.AppTask; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Spatial; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Future; + +/** + * Allows for enqueueing tasks onto the update loop / rendering thread. + * + * Usage: + * mySpatial.addControl(new UpdateControl()); // add it once + * mySpatial.getControl(UpdateControl.class).enqueue(new Callable() { + * public Object call() throws Exception { + * // do stuff here + * return null; + * } + * }); + * + * @author Brent Owens + */ +public class UpdateControl extends AbstractControl { + + private final ConcurrentLinkedQueue> taskQueue = new ConcurrentLinkedQueue>(); + + /** + * Enqueues a task/callable object to execute in the jME3 + * rendering thread. + */ + public Future enqueue(Callable callable) { + AppTask task = new AppTask(callable); + taskQueue.add(task); + return task; + } + + @Override + protected void controlUpdate(float tpf) { + AppTask task = taskQueue.poll(); + toploop: do { + if (task == null) break; + while (task.isCancelled()) { + task = taskQueue.poll(); + if (task == null) break toploop; + } + task.invoke(); + } while (((task = taskQueue.poll()) != null)); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + + } + + public Control cloneForSpatial(Spatial newSpatial) { + UpdateControl control = new UpdateControl(); + control.setSpatial(newSpatial); + control.setEnabled(isEnabled()); + control.taskQueue.addAll(taskQueue); + return control; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/control/package.html b/jme3-core/src/main/java/com/jme3/scene/control/package.html new file mode 100644 index 000000000..a387840d8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/control/package.html @@ -0,0 +1,17 @@ + + + + + + + + + +The com.jme3.control package provides +{@link com.jme3.scene.control.Control controls}. +Controls represent the "logical" programming of scene graph elements, containing +callbacks for when a {@link com.jme3.scene.Spatial} is rendered or updated +by the engine. + + + diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java b/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java new file mode 100644 index 000000000..0b1855bc9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.debug; + +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import java.nio.FloatBuffer; + +/** + * The Arrow debug shape represents an arrow. + * An arrow is simply a line going from the original toward an extent + * and at the tip there will be triangle-like shape. + * + * @author Kirill Vainer + */ +public class Arrow extends Mesh { + + private Quaternion tempQuat = new Quaternion(); + private Vector3f tempVec = new Vector3f(); + + private static final float[] positions = new float[]{ + 0, 0, 0, + 0, 0, 1, // tip + 0.05f, 0, 0.9f, // tip right + -0.05f, 0, 0.9f, // tip left + 0, 0.05f, 0.9f, // tip top + 0, -0.05f, 0.9f, // tip buttom + }; + + /** + * Serialization only. Do not use. + */ + public Arrow() { + } + + /** + * Creates an arrow mesh with the given extent. + * The arrow will start at the origin (0,0,0) and finish + * at the given extent. + * + * @param extent Extent of the arrow from origin + */ + public Arrow(Vector3f extent) { + float len = extent.length(); + Vector3f dir = extent.normalize(); + + tempQuat.lookAt(dir, Vector3f.UNIT_Y); + tempQuat.normalizeLocal(); + + float[] newPositions = new float[positions.length]; + for (int i = 0; i < positions.length; i += 3) { + Vector3f vec = tempVec.set(positions[i], + positions[i + 1], + positions[i + 2]); + vec.multLocal(len); + tempQuat.mult(vec, vec); + + newPositions[i] = vec.getX(); + newPositions[i + 1] = vec.getY(); + newPositions[i + 2] = vec.getZ(); + } + + setBuffer(Type.Position, 3, newPositions); + setBuffer(Type.Index, 2, + new short[]{ + 0, 1, + 1, 2, + 1, 3, + 1, 4, + 1, 5,}); + setMode(Mode.Lines); + + updateBound(); + updateCounts(); + } + + /** + * Sets the arrow's extent. + * This will modify the buffers on the mesh. + * + * @param extent the arrow's extent. + */ + public void setArrowExtent(Vector3f extent) { + float len = extent.length(); +// Vector3f dir = extent.normalize(); + + tempQuat.lookAt(extent, Vector3f.UNIT_Y); + tempQuat.normalizeLocal(); + + VertexBuffer pvb = getBuffer(Type.Position); + FloatBuffer buffer = (FloatBuffer)pvb.getData(); + buffer.rewind(); + for (int i = 0; i < positions.length; i += 3) { + Vector3f vec = tempVec.set(positions[i], + positions[i + 1], + positions[i + 2]); + vec.multLocal(len); + tempQuat.mult(vec, vec); + + buffer.put(vec.x); + buffer.put(vec.y); + buffer.put(vec.z); + } + + pvb.updateData(buffer); + + updateBound(); + updateCounts(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/Grid.java b/jme3-core/src/main/java/com/jme3/scene/debug/Grid.java new file mode 100644 index 000000000..5b6d0661e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/Grid.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.debug; + +import com.jme3.scene.Mesh; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + * Simple grid shape. + * + * @author Kirill Vainer + */ +public class Grid extends Mesh { + + /** + * Creates a grid debug shape. + * @param xLines + * @param yLines + * @param lineDist + */ + public Grid(int xLines, int yLines, float lineDist){ + xLines -= 2; + yLines -= 2; + int lineCount = xLines + yLines + 4; + + FloatBuffer fpb = BufferUtils.createFloatBuffer(6 * lineCount); + ShortBuffer sib = BufferUtils.createShortBuffer(2 * lineCount); + + float xLineLen = (yLines + 1) * lineDist; + float yLineLen = (xLines + 1) * lineDist; + int curIndex = 0; + + // add lines along X + for (int i = 0; i < xLines + 2; i++){ + float y = (i) * lineDist; + + // positions + fpb.put(0) .put(0).put(y); + fpb.put(xLineLen).put(0).put(y); + + // indices + sib.put( (short) (curIndex++) ); + sib.put( (short) (curIndex++) ); + } + + // add lines along Y + for (int i = 0; i < yLines + 2; i++){ + float x = (i) * lineDist; + + // positions + fpb.put(x).put(0).put(0); + fpb.put(x).put(0).put(yLineLen); + + // indices + sib.put( (short) (curIndex++) ); + sib.put( (short) (curIndex++) ); + } + + fpb.flip(); + sib.flip(); + + setBuffer(Type.Position, 3, fpb); + setBuffer(Type.Index, 2, sib); + + setMode(Mode.Lines); + + updateBound(); + updateCounts(); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java new file mode 100644 index 000000000..b633ab5b8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.debug; + +import java.util.Map; + +import com.jme3.animation.Skeleton; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; + +/** + * The class that creates a mesh to display how bones behave. + * If it is supplied with the bones' lengths it will show exactly how the bones look like on the scene. + * If not then only connections between each bone heads will be shown. + */ +public class SkeletonDebugger extends Node { + /** The lines of the bones or the wires between their heads. */ + private SkeletonWire wires; + /** The heads and tails points of the bones or only heads if no length data is available. */ + private SkeletonPoints points; + /** The dotted lines between a bone's tail and the had of its children. Not available if the length data was not provided. */ + private SkeletonInterBoneWire interBoneWires; + + public SkeletonDebugger() { + } + + /** + * Creates a debugger with no length data. The wires will be a connection between the bones' heads only. + * The points will show the bones' heads only and no dotted line of inter bones connection will be visible. + * @param name + * the name of the debugger's node + * @param skeleton + * the skeleton that will be shown + */ + public SkeletonDebugger(String name, Skeleton skeleton) { + this(name, skeleton, null); + } + + /** + * Creates a debugger with bone lengths data. If the data is supplied then the wires will show each full bone (from head to tail), + * the points will display both heads and tails of the bones and dotted lines between bones will be seen. + * @param name + * the name of the debugger's node + * @param skeleton + * the skeleton that will be shown + * @param boneLengths + * a map between the bone's index and the bone's length + */ + public SkeletonDebugger(String name, Skeleton skeleton, Map boneLengths) { + super(name); + + wires = new SkeletonWire(skeleton, boneLengths); + points = new SkeletonPoints(skeleton, boneLengths); + + this.attachChild(new Geometry(name + "_wires", wires)); + this.attachChild(new Geometry(name + "_points", points)); + if (boneLengths != null) { + interBoneWires = new SkeletonInterBoneWire(skeleton, boneLengths); + this.attachChild(new Geometry(name + "_interwires", interBoneWires)); + } + + this.setQueueBucket(Bucket.Transparent); + } + + @Override + public void updateLogicalState(float tpf) { + super.updateLogicalState(tpf); + wires.updateGeometry(); + points.updateGeometry(); + if(interBoneWires != null) { + interBoneWires.updateGeometry(); + } + } + + /** + * @return the skeleton points + */ + public SkeletonPoints getPoints() { + return points; + } + + /** + * @return the skeleton wires + */ + public SkeletonWire getWires() { + return wires; + } + + /** + * @return the dotted line between bones (can be null) + */ + public SkeletonInterBoneWire getInterBoneWires() { + return interBoneWires; + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonInterBoneWire.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonInterBoneWire.java new file mode 100644 index 000000000..ccf97aa2e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonInterBoneWire.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.debug; + +import java.nio.FloatBuffer; +import java.util.Map; + +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; + +/** + * A class that displays a dotted line between a bone tail and its childrens' heads. + * + * @author Marcin Roguski (Kaelthas) + */ +public class SkeletonInterBoneWire extends Mesh { + private static final int POINT_AMOUNT = 10; + /** The amount of connections between bones. */ + private int connectionsAmount; + /** The skeleton that will be showed. */ + private Skeleton skeleton; + /** The map between the bone index and its length. */ + private Map boneLengths; + + /** + * Creates buffers for points. Each line has POINT_AMOUNT of points. + * @param skeleton + * the skeleton that will be showed + * @param boneLengths + * the lengths of the bones + */ + public SkeletonInterBoneWire(Skeleton skeleton, Map boneLengths) { + this.skeleton = skeleton; + + for (Bone bone : skeleton.getRoots()) { + this.countConnections(bone); + } + + this.setMode(Mode.Points); + this.setPointSize(1); + this.boneLengths = boneLengths; + + VertexBuffer pb = new VertexBuffer(Type.Position); + FloatBuffer fpb = BufferUtils.createFloatBuffer(POINT_AMOUNT * connectionsAmount * 3); + pb.setupData(Usage.Stream, 3, Format.Float, fpb); + this.setBuffer(pb); + + this.updateCounts(); + } + + /** + * The method updates the geometry according to the poitions of the bones. + */ + public void updateGeometry() { + VertexBuffer vb = this.getBuffer(Type.Position); + FloatBuffer posBuf = this.getFloatBuffer(Type.Position); + posBuf.clear(); + for (int i = 0; i < skeleton.getBoneCount(); ++i) { + Bone bone = skeleton.getBone(i); + Vector3f parentTail = bone.getModelSpacePosition().add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneLengths.get(i)))); + + for (Bone child : bone.getChildren()) { + Vector3f childHead = child.getModelSpacePosition(); + Vector3f v = childHead.subtract(parentTail); + float pointDelta = v.length() / POINT_AMOUNT; + v.normalizeLocal().multLocal(pointDelta); + Vector3f pointPosition = parentTail.clone(); + for (int j = 0; j < POINT_AMOUNT; ++j) { + posBuf.put(pointPosition.getX()).put(pointPosition.getY()).put(pointPosition.getZ()); + pointPosition.addLocal(v); + } + } + } + posBuf.flip(); + vb.updateData(posBuf); + + this.updateBound(); + } + + /** + * Th method couns the connections between bones. + * @param bone + * the bone where counting starts + */ + private void countConnections(Bone bone) { + for (Bone child : bone.getChildren()) { + ++connectionsAmount; + this.countConnections(child); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonPoints.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonPoints.java new file mode 100644 index 000000000..83b08402b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonPoints.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.debug; + +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.util.BufferUtils; + +import java.nio.FloatBuffer; +import java.util.Map; + +/** + * The class that displays either heads of the bones if no length data is supplied or both heads and tails otherwise. + */ +public class SkeletonPoints extends Mesh { + /** The skeleton to be displayed. */ + private Skeleton skeleton; + /** The map between the bone index and its length. */ + private Map boneLengths; + + /** + * Creates a points with no length data. The points will only show the bone's heads. + * @param skeleton + * the skeleton that will be shown + */ + public SkeletonPoints(Skeleton skeleton) { + this(skeleton, null); + } + + /** + * Creates a points with bone lengths data. If the data is supplied then the points will show both head and tail of each bone. + * @param skeleton + * the skeleton that will be shown + * @param boneLengths + * a map between the bone's index and the bone's length + */ + public SkeletonPoints(Skeleton skeleton, Map boneLengths) { + this.skeleton = skeleton; + this.setMode(Mode.Points); + int pointsCount = skeleton.getBoneCount(); + + if (boneLengths != null) { + this.boneLengths = boneLengths; + pointsCount *= 2; + } + + VertexBuffer pb = new VertexBuffer(Type.Position); + FloatBuffer fpb = BufferUtils.createFloatBuffer(pointsCount * 3); + pb.setupData(Usage.Stream, 3, Format.Float, fpb); + this.setBuffer(pb); + + this.setPointSize(7); + this.updateCounts(); + + } + + /** + * The method updates the geometry according to the poitions of the bones. + */ + public void updateGeometry() { + VertexBuffer vb = this.getBuffer(Type.Position); + FloatBuffer posBuf = this.getFloatBuffer(Type.Position); + posBuf.clear(); + for (int i = 0; i < skeleton.getBoneCount(); ++i) { + Bone bone = skeleton.getBone(i); + Vector3f head = bone.getModelSpacePosition(); + + posBuf.put(head.getX()).put(head.getY()).put(head.getZ()); + if (boneLengths != null) { + Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneLengths.get(i)))); + posBuf.put(tail.getX()).put(tail.getY()).put(tail.getZ()); + } + } + posBuf.flip(); + vb.updateData(posBuf); + + this.updateBound(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonWire.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonWire.java new file mode 100644 index 000000000..b907a4c14 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonWire.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.debug; + +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; +import java.util.Map; + +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; + +/** + * The class that displays either wires between the bones' heads if no length data is supplied and + * full bones' shapes otherwise. + */ +public class SkeletonWire extends Mesh { + /** The number of bones' connections. Used in non-length mode. */ + private int numConnections; + /** The skeleton to be displayed. */ + private Skeleton skeleton; + /** The map between the bone index and its length. */ + private Map boneLengths; + + /** + * Creates a wire with no length data. The wires will be a connection between the bones' heads only. + * @param skeleton + * the skeleton that will be shown + */ + public SkeletonWire(Skeleton skeleton) { + this(skeleton, null); + } + + /** + * Creates a wire with bone lengths data. If the data is supplied then the wires will show each full bone (from head to tail). + * @param skeleton + * the skeleton that will be shown + * @param boneLengths + * a map between the bone's index and the bone's length + */ + public SkeletonWire(Skeleton skeleton, Map boneLengths) { + this.skeleton = skeleton; + + for (Bone bone : skeleton.getRoots()) { + this.countConnections(bone); + } + + this.setMode(Mode.Lines); + int lineVerticesCount = skeleton.getBoneCount(); + if (boneLengths != null) { + this.boneLengths = boneLengths; + lineVerticesCount *= 2; + } + + VertexBuffer pb = new VertexBuffer(Type.Position); + FloatBuffer fpb = BufferUtils.createFloatBuffer(lineVerticesCount * 3); + pb.setupData(Usage.Stream, 3, Format.Float, fpb); + this.setBuffer(pb); + + VertexBuffer ib = new VertexBuffer(Type.Index); + ShortBuffer sib = BufferUtils.createShortBuffer(boneLengths != null ? lineVerticesCount : numConnections * 2); + ib.setupData(Usage.Static, 2, Format.UnsignedShort, sib); + this.setBuffer(ib); + + if (boneLengths != null) { + for (int i = 0; i < lineVerticesCount; ++i) { + sib.put((short) i); + } + } else { + for (Bone bone : skeleton.getRoots()) { + this.writeConnections(sib, bone); + } + } + sib.flip(); + + this.updateCounts(); + } + + /** + * The method updates the geometry according to the poitions of the bones. + */ + public void updateGeometry() { + VertexBuffer vb = this.getBuffer(Type.Position); + FloatBuffer posBuf = this.getFloatBuffer(Type.Position); + posBuf.clear(); + for (int i = 0; i < skeleton.getBoneCount(); ++i) { + Bone bone = skeleton.getBone(i); + Vector3f head = bone.getModelSpacePosition(); + + posBuf.put(head.getX()).put(head.getY()).put(head.getZ()); + if (boneLengths != null) { + Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneLengths.get(i)))); + posBuf.put(tail.getX()).put(tail.getY()).put(tail.getZ()); + } + } + posBuf.flip(); + vb.updateData(posBuf); + + this.updateBound(); + } + + /** + * Th method couns the connections between bones. + * @param bone + * the bone where counting starts + */ + private void countConnections(Bone bone) { + for (Bone child : bone.getChildren()) { + numConnections++; + this.countConnections(child); + } + } + + /** + * The method writes the indexes for the connection vertices. Used in non-length mode. + * @param indexBuf + * the index buffer + * @param bone + * the bone + */ + 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)); + + this.writeConnections(indexBuf, child); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/WireBox.java b/jme3-core/src/main/java/com/jme3/scene/debug/WireBox.java new file mode 100644 index 000000000..7e0daa15c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/WireBox.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.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/jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java b/jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java new file mode 100644 index 000000000..c050d1edb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.debug; + +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; + +public class WireFrustum extends Mesh { + + public WireFrustum(Vector3f[] points){ + if (points != null) + setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points)); + + setBuffer(Type.Index, 2, + new short[]{ + 0, 1, + 1, 2, + 2, 3, + 3, 0, + + 4, 5, + 5, 6, + 6, 7, + 7, 4, + + 0, 4, + 1, 5, + 2, 6, + 3, 7, + } + ); + setMode(Mode.Lines); + } + + public void update(Vector3f[] points){ + VertexBuffer vb = getBuffer(Type.Position); + if (vb == null){ + setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points)); + return; + } + + FloatBuffer b = BufferUtils.createFloatBuffer(points); + FloatBuffer a = (FloatBuffer) vb.getData(); + b.rewind(); + a.rewind(); + a.put(b); + a.rewind(); + + vb.updateData(a); + + updateBound(); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/WireSphere.java b/jme3-core/src/main/java/com/jme3/scene/debug/WireSphere.java new file mode 100644 index 000000000..04338eac6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/WireSphere.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.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/jme3-core/src/main/java/com/jme3/scene/mesh/IndexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexBuffer.java new file mode 100644 index 000000000..a67bb6704 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexBuffer.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.mesh; + +import com.jme3.util.BufferUtils; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +/** + * 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 static IndexBuffer wrapIndexBuffer(Buffer buf) { + 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()); + } + } + + /** + * Creates an index buffer that can contain the given amount + * of vertices. + * Returns {@link IndexShortBuffer} + * + * @param vertexCount The amount of vertices to contain + * @param indexCount The amount of indices + * to contain. + * @return A new index buffer + */ + public static IndexBuffer createIndexBuffer(int vertexCount, int indexCount){ + if (vertexCount > 65535){ + return new IndexIntBuffer(BufferUtils.createIntBuffer(indexCount)); + }else{ + return new IndexShortBuffer(BufferUtils.createShortBuffer(indexCount)); + } + } + + /** + * Returns the vertex index for the given index in the index buffer. + * + * @param i The index inside the index buffer + * @return + */ + public abstract int get(int i); + + /** + * Puts the vertex index at the index buffer's index. + * Implementations may throw an {@link UnsupportedOperationException} + * if modifying the IndexBuffer is not supported (e.g. virtual index + * buffers). + */ + public abstract void put(int i, int value); + + /** + * Returns the size of the index buffer. + * + * @return the size of the index buffer. + */ + public abstract int size(); + + /** + * Returns the underlying data-type specific {@link Buffer}. + * Implementations may return null if there's no underlying + * buffer. + * + * @return the underlying {@link Buffer}. + */ + public abstract Buffer getBuffer(); +} diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java new file mode 100644 index 000000000..2c06c25a9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.mesh; + +import java.nio.Buffer; +import java.nio.ByteBuffer; + +/** + * IndexBuffer implementation for {@link ByteBuffer}s. + * + * @author lex + */ +public class IndexByteBuffer extends IndexBuffer { + + private ByteBuffer buf; + + public IndexByteBuffer(ByteBuffer buffer) { + buf = buffer; + buf.rewind(); + } + + @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/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java new file mode 100644 index 000000000..4412be260 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.mesh; + +import java.nio.Buffer; +import java.nio.IntBuffer; + +/** + * IndexBuffer implementation for {@link IntBuffer}s. + * + * @author lex + */ +public class IndexIntBuffer extends IndexBuffer { + + private IntBuffer buf; + + public IndexIntBuffer(IntBuffer buffer) { + buf = buffer; + buf.rewind(); + } + + @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/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java new file mode 100644 index 000000000..5f3c25ae9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.mesh; + +import java.nio.Buffer; +import java.nio.ShortBuffer; + +/** + * IndexBuffer implementation for {@link ShortBuffer}s. + * + * @author lex + */ +public class IndexShortBuffer extends IndexBuffer { + + private ShortBuffer buf; + + public IndexShortBuffer(ShortBuffer buffer) { + buf = buffer; + buf.rewind(); + } + + @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/jme3-core/src/main/java/com/jme3/scene/mesh/VirtualIndexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/VirtualIndexBuffer.java new file mode 100644 index 000000000..18fefa90a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/VirtualIndexBuffer.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.mesh; + +import com.jme3.scene.Mesh.Mode; +import java.nio.Buffer; + +/** + * IndexBuffer implementation that generates vertex indices sequentially + * based on a specific Mesh {@link Mode}. + * The generated indices are as if the mesh is in the given mode + * but contains no index buffer, thus this implementation will + * return the indices if the index buffer was there and contained sequential + * triangles. + * Example: + *

        + *
      • {@link Mode#Triangles}: 0, 1, 2 | 3, 4, 5 | 6, 7, 8 | ...
      • + *
      • {@link Mode#TriangleStrip}: 0, 1, 2 | 2, 1, 3 | 2, 3, 4 | ...
      • + *
      • {@link Mode#TriangleFan}: 0, 1, 2 | 0, 2, 3 | 0, 3, 4 | ...
      • + *
      + * + * @author Kirill Vainer + */ +public class VirtualIndexBuffer extends IndexBuffer { + + protected int numVerts = 0; + protected int numIndices = 0; + protected Mode meshMode; + + public VirtualIndexBuffer(int numVerts, Mode meshMode){ + this.numVerts = numVerts; + this.meshMode = meshMode; + switch (meshMode) { + case Points: + numIndices = numVerts; + return; + case LineLoop: + numIndices = (numVerts - 1) * 2 + 1; + return; + case LineStrip: + numIndices = (numVerts - 1) * 2; + return; + case Lines: + numIndices = numVerts; + return; + case TriangleFan: + numIndices = (numVerts - 2) * 3; + return; + case TriangleStrip: + numIndices = (numVerts - 2) * 3; + return; + case Triangles: + numIndices = numVerts; + return; + case Hybrid: + throw new UnsupportedOperationException(); + } + } + + @Override + public int get(int i) { + if (meshMode == Mode.Triangles || meshMode == Mode.Lines || meshMode == Mode.Points){ + return i; + }else if (meshMode == Mode.LineStrip){ + return (i + 1) / 2; + }else if (meshMode == Mode.LineLoop){ + return (i == (numVerts-1)) ? 0 : ((i + 1) / 2); + }else if (meshMode == Mode.TriangleStrip){ + int triIndex = i/3; + int vertIndex = i%3; + boolean isBack = (i/3)%2==1; + if (!isBack){ + return triIndex + vertIndex; + }else{ + switch (vertIndex){ + case 0: return triIndex + 1; + case 1: return triIndex; + case 2: return triIndex + 2; + default: throw new AssertionError(); + } + } + }else if (meshMode == Mode.TriangleFan){ + int vertIndex = i%3; + if (vertIndex == 0) + return 0; + else + return (i / 3) + vertIndex; + }else{ + throw new UnsupportedOperationException(); + } + } + + @Override + public void put(int i, int value) { + throw new UnsupportedOperationException("Does not represent index buffer"); + } + + @Override + public int size() { + return numIndices; + } + + @Override + public Buffer getBuffer() { + return null; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/WrappedIndexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/WrappedIndexBuffer.java new file mode 100644 index 000000000..a960d2e36 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/WrappedIndexBuffer.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.mesh; + +import com.jme3.scene.Mesh; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.VertexBuffer.Type; +import java.nio.Buffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +/** + * WrappedIndexBuffer converts vertex indices from a non list based + * mesh mode such as {@link Mode#TriangleStrip} or {@link Mode#LineLoop} + * into a list based mode such as {@link Mode#Triangles} or {@link Mode#Lines}. + * As it is often more convenient to read vertex data in list format + * than in a non-list format, using this class is recommended to avoid + * convoluting classes used to process mesh data from an external source. + * + * @author Kirill Vainer + */ +public class WrappedIndexBuffer extends VirtualIndexBuffer { + + private final IndexBuffer ib; + + public WrappedIndexBuffer(Mesh mesh){ + super(mesh.getVertexCount(), mesh.getMode()); + this.ib = mesh.getIndexBuffer(); + switch (meshMode){ + case Points: + numIndices = mesh.getTriangleCount(); + break; + case Lines: + case LineLoop: + case LineStrip: + numIndices = mesh.getTriangleCount() * 2; + break; + case Triangles: + case TriangleStrip: + case TriangleFan: + numIndices = mesh.getTriangleCount() * 3; + break; + default: + throw new UnsupportedOperationException(); + } + } + + @Override + public int get(int i) { + int superIdx = super.get(i); + return ib.get(superIdx); + } + + @Override + public Buffer getBuffer() { + return ib.getBuffer(); + } + + public static void convertToList(Mesh mesh){ + IndexBuffer inBuf = mesh.getIndicesAsList(); + IndexBuffer outBuf = IndexBuffer.createIndexBuffer(mesh.getVertexCount(), + inBuf.size()); + + for (int i = 0; i < inBuf.size(); i++){ + outBuf.put(i, inBuf.get(i)); + } + + mesh.clearBuffer(Type.Index); + switch (mesh.getMode()){ + case LineLoop: + case LineStrip: + mesh.setMode(Mode.Lines); + break; + case TriangleStrip: + case TriangleFan: + mesh.setMode(Mode.Triangles); + break; + default: + break; + } + if (outBuf instanceof IndexIntBuffer){ + mesh.setBuffer(Type.Index, 3, (IntBuffer)outBuf.getBuffer()); + }else{ + mesh.setBuffer(Type.Index, 3, (ShortBuffer)outBuf.getBuffer()); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/package.html b/jme3-core/src/main/java/com/jme3/scene/mesh/package.html new file mode 100644 index 000000000..5362c52fb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/package.html @@ -0,0 +1,25 @@ + + + + + + + + + +The com.jme3.scene.mesh package contains utilities +for reading from {@link com.jme3.scene.mesh.IndexBuffer index buffers}. +Several implementations are provided of the {@link com.jme3.scene.mesh.IndexBuffer} +class: +
        +
      • {@link com.jme3.scene.mesh.IndexByteBuffer} - For reading 8-bit index buffers
      • +
      • {@link com.jme3.scene.mesh.IndexShortBuffer} - For reading 16-bit index buffers
      • +
      • {@link com.jme3.scene.mesh.IndexIntBuffer} - For reading 32-bit index buffers
      • +
      • {@link com.jme3.scene.mesh.VirtualIndexBuffer} - For reading "virtual indices", for + those meshes that do not have an index buffer
      • +
      • {@link com.jme3.scene.mesh.WrappedIndexBuffer} - For converting from + non-list based mode indices to list based
      • +
      + + + diff --git a/jme3-core/src/main/java/com/jme3/scene/package.html b/jme3-core/src/main/java/com/jme3/scene/package.html new file mode 100644 index 000000000..c0d24be3d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/package.html @@ -0,0 +1,26 @@ + + + + + + + + + +The com.jme3.input package contains the scene graph implementation +in jMonkeyEngine. + +

      + The scene graph is the most important package in jME, as it is the API + used to manage scene elements so that they can be rendered. + The {@link com.jme3.scene.Spatial} class provides a common base class + for all scene graph elements. The {@link com.jme3.scene.Node} class provides + the "branches" in the graph, used to organize elements in a tree + hierarchy. The {@link com.jme3.scene.Geometry} is the leaf class that + will contain a {@link com.jme3.scene.Mesh} object (geometry data + such as vertex positions, normals, etc) and a {@link com.jme3.material.Material} + object containing information on how the geometry should be shaded. +

      + + + diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java b/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java new file mode 100644 index 000000000..35f078eeb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.shape; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import java.io.IOException; + +/** + * An eight sided box. + *

      + * 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} + * 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 geometry. + */ + protected abstract void duUpdateGeometryIndices(); + + /** + * Update the normals of each of the box's planes. + */ + protected abstract void duUpdateGeometryNormals(); + + /** + * Update the points that define the texture of the box. + *

      + * 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 center point of this box. + */ + public final Vector3f getCenter() { + return center; + } + + /** + * Get the x-axis size (extent) of this box. + */ + public final float getXExtent() { + return xExtent; + } + + /** + * Get the y-axis size (extent) of this box. + */ + public final float getYExtent() { + return yExtent; + } + + /** + * Get the z-axis size (extent) of this box. + */ + public final float getZExtent() { + return zExtent; + } + + /** + * Rebuilds the box after a property has been directly altered. + *

      + * 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); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + xExtent = capsule.readFloat("xExtent", 0); + yExtent = capsule.readFloat("yExtent", 0); + zExtent = capsule.readFloat("zExtent", 0); + center.set((Vector3f) capsule.readSavable("center", Vector3f.ZERO.clone())); + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(xExtent, "xExtent", 0); + capsule.write(yExtent, "yExtent", 0); + capsule.write(zExtent, "zExtent", 0); + capsule.write(center, "center", Vector3f.ZERO); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Box.java b/jme3-core/src/main/java/com/jme3/scene/shape/Box.java new file mode 100644 index 000000000..3b40439c8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Box.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// $Id: Box.java 4131 2009-03-19 20:15:28Z blaine.dev $ +package com.jme3.scene.shape; + +import com.jme3.math.Vector3f; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; + +/** + * A box with solid (filled) faces. + * + * @author Mark Powell + * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $ + */ +public class Box extends AbstractBox { + + private static final short[] GEOMETRY_INDICES_DATA = { + 2, 1, 0, 3, 2, 0, // back + 6, 5, 4, 7, 6, 4, // right + 10, 9, 8, 11, 10, 8, // front + 14, 13, 12, 15, 14, 12, // left + 18, 17, 16, 19, 18, 16, // top + 22, 21, 20, 23, 22, 20 // bottom + }; + + private static final float[] GEOMETRY_NORMALS_DATA = { + 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, // back + 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // right + 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // front + -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // left + 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // top + 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0 // bottom + }; + + private static final float[] GEOMETRY_TEXTURE_DATA = { + 1, 0, 0, 0, 0, 1, 1, 1, // back + 1, 0, 0, 0, 0, 1, 1, 1, // right + 1, 0, 0, 0, 0, 1, 1, 1, // front + 1, 0, 0, 0, 0, 1, 1, 1, // left + 1, 0, 0, 0, 0, 1, 1, 1, // top + 1, 0, 0, 0, 0, 1, 1, 1 // bottom + }; + + /** + * Creates a new box. + *

      + * 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 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. + * + * @Deprecated: Due to constant confusion of geometry centers and the center + * of the box mesh this method has been deprecated. + * + * @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. + */ + @Deprecated + 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 com.jme3.scene.Spatial#setLocalTranslation(com.jme3.math.Vector3f) } + * and {@link com.jme3.scene.Spatial#setLocalRotation(com.jme3.math.Quaternion) } + * methods to define those properties. + * + * @param min the minimum point that defines the box. + * @param max the maximum point that defines the box. + */ + public Box(Vector3f min, Vector3f max) { + super(); + updateGeometry(min, max); + } + + /** + * Empty constructor for serialization only. Do not use. + */ + public Box(){ + super(); + } + + /** + * Creates a clone of this box. + *

      + * 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/jme3-core/src/main/java/com/jme3/scene/shape/Curve.java b/jme3-core/src/main/java/com/jme3/scene/shape/Curve.java new file mode 100644 index 000000000..009b41c64 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Curve.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.shape; + +import com.jme3.math.Spline; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import java.util.Iterator; +import java.util.List; + +/** + * A + * Curve is a visual, line-based representation of a {@link Spline}. + * The underlying Spline will be sampled N times where N is the number of + * segments as specified in the constructor. Each segment will represent one + * line in the generated mesh. + * + * @author Nehon + */ +public class Curve extends Mesh { + + private Spline spline; + private Vector3f temp = new Vector3f(); + + /** + * Serialization only. Do not use. + */ + public Curve() { + } + + /** + * Create a curve mesh. Use a CatmullRom spline model that does not cycle. + * + * @param controlPoints the control points to use to create this curve + * @param nbSubSegments the number of subsegments between the control points + */ + public Curve(Vector3f[] controlPoints, int nbSubSegments) { + this(new Spline(Spline.SplineType.CatmullRom, controlPoints, 10, false), nbSubSegments); + } + + /** + * Create a curve mesh from a Spline + * + * @param spline the spline to use + * @param nbSubSegments the number of subsegments between the control points + */ + public Curve(Spline spline, int nbSubSegments) { + super(); + this.spline = spline; + switch (spline.getType()) { + case CatmullRom: + this.createCatmullRomMesh(nbSubSegments); + break; + case Bezier: + this.createBezierMesh(nbSubSegments); + break; + case Nurb: + this.createNurbMesh(nbSubSegments); + break; + case Linear: + default: + this.createLinearMesh(); + break; + } + } + + private void createCatmullRomMesh(int nbSubSegments) { + float[] array = new float[((spline.getControlPoints().size() - 1) * nbSubSegments + 1) * 3]; + short[] indices = new short[(spline.getControlPoints().size() - 1) * nbSubSegments * 2]; + int i = 0; + int cptCP = 0; + for (Iterator 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; + for (int j = 0; j < (spline.getControlPoints().size() - 1) * nbSubSegments; j++) { + k = j; + indices[i] = (short) k; + i++; + k++; + indices[i] = (short) k; + i++; + } + + this.setMode(Mesh.Mode.Lines); + this.setBuffer(VertexBuffer.Type.Position, 3, array); + this.setBuffer(VertexBuffer.Type.Index, 2, indices);//(spline.getControlPoints().size() - 1) * nbSubSegments * 2 + this.updateBound(); + this.updateCounts(); + } + + /** + * This method creates the Bezier path for this curve. + * + * @param nbSubSegments amount of subsegments between position control + * points + */ + private void createBezierMesh(int nbSubSegments) { + if (nbSubSegments == 0) { + nbSubSegments = 1; + } + int centerPointsAmount = (spline.getControlPoints().size() + 2) / 3; + + //calculating vertices + float[] array = new float[((centerPointsAmount - 1) * nbSubSegments + 1) * 3]; + int currentControlPoint = 0; + List 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; + short[] indices = new short[(centerPointsAmount - 1) * nbSubSegments << 1]; + for (int j = 0; j < (centerPointsAmount - 1) * nbSubSegments; ++j) { + k = j; + indices[i++] = (short) k; + ++k; + indices[i++] = (short) k; + } + + this.setMode(Mesh.Mode.Lines); + this.setBuffer(VertexBuffer.Type.Position, 3, array); + this.setBuffer(VertexBuffer.Type.Index, 2, indices); + this.updateBound(); + this.updateCounts(); + } + + /** + * This method creates the Nurb path for this curve. + * + * @param nbSubSegments amount of subsegments between position control + * points + */ + private void createNurbMesh(int nbSubSegments) { + float minKnot = spline.getMinNurbKnot(); + float maxKnot = spline.getMaxNurbKnot(); + float deltaU = (maxKnot - minKnot) / nbSubSegments; + + float[] array = new float[(nbSubSegments + 1) * 3]; + + float u = minKnot; + Vector3f interpolationResult = new Vector3f(); + for (int i = 0; i < array.length; i += 3) { + spline.interpolate(u, 0, interpolationResult); + array[i] = interpolationResult.x; + array[i + 1] = interpolationResult.y; + array[i + 2] = interpolationResult.z; + u += deltaU; + } + + //calculating indexes + int i = 0; + short[] indices = new short[nbSubSegments << 1]; + for (int j = 0; j < nbSubSegments; ++j) { + indices[i++] = (short) j; + indices[i++] = (short) (j + 1); + } + + this.setMode(Mesh.Mode.Lines); + this.setBuffer(VertexBuffer.Type.Position, 3, array); + this.setBuffer(VertexBuffer.Type.Index, 2, indices); + this.updateBound(); + this.updateCounts(); + } + + private void createLinearMesh() { + float[] array = new float[spline.getControlPoints().size() * 3]; + short[] indices = new short[(spline.getControlPoints().size() - 1) * 2]; + int i = 0; + int cpt = 0; + int k; + 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++; + } + } + + this.setMode(Mesh.Mode.Lines); + this.setBuffer(VertexBuffer.Type.Position, 3, array); + this.setBuffer(VertexBuffer.Type.Index, 2, indices); + this.updateBound(); + this.updateCounts(); + } + + /** + * This method returns the length of the curve. + * + * @return the length of the curve + */ + public float getLength() { + return spline.getTotalLength(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java b/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java new file mode 100644 index 000000000..f0966328f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// $Id: Cylinder.java 4131 2009-03-19 20:15:28Z blaine.dev $ +package com.jme3.scene.shape; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.BufferUtils; +import static com.jme3.util.BufferUtils.*; +import java.io.IOException; +import java.nio.FloatBuffer; + +/** + * A simple cylinder, defined by it's height and radius. + * (Ported to jME3) + * + * @author Mark Powell + * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $ + */ +public class Cylinder extends Mesh { + + private int axisSamples; + + private int radialSamples; + + private float radius; + private float radius2; + + private float height; + private boolean closed; + private boolean inverted; + + /** + * Default constructor for serialization only. Do not use. + */ + public Cylinder() { + } + + /** + * Creates a new Cylinder. By default its center is the origin. Usually, a + * higher sample number creates a better looking cylinder, but at the cost + * of more vertex information. + * + * @param axisSamples + * Number of triangle samples along the axis. + * @param radialSamples + * Number of triangle samples along the radial. + * @param radius + * The radius of the cylinder. + * @param height + * The cylinder's height. + */ + public Cylinder(int axisSamples, int radialSamples, + float radius, float height) { + this(axisSamples, radialSamples, radius, height, false); + } + + /** + * Creates a new Cylinder. By default its center is the origin. Usually, a + * higher sample number creates a better looking cylinder, but at the cost + * of more vertex information.
      + * 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; + } + + /** + * Rebuilds the cylinder based on a new set of parameters. + * + * @param axisSamples the number of samples along the axis. + * @param radialSamples the number of samples around the radial. + * @param radius the radius of the bottom of the cylinder. + * @param radius2 the radius of the top of the cylinder. + * @param height the cylinder's height. + * @param closed should the cylinder have top and bottom surfaces. + * @param inverted is the cylinder is meant to be viewed from the inside. + */ + public void updateGeometry(int axisSamples, int radialSamples, + float radius, float radius2, float height, boolean closed, boolean inverted) { + this.axisSamples = axisSamples + (closed ? 2 : 0); + this.radialSamples = radialSamples; + this.radius = radius; + this.radius2 = radius2; + this.height = height; + this.closed = closed; + this.inverted = inverted; + +// VertexBuffer pvb = getBuffer(Type.Position); +// VertexBuffer nvb = getBuffer(Type.Normal); +// VertexBuffer tvb = getBuffer(Type.TexCoord); + + // Vertices + int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0); + + setBuffer(Type.Position, 3, createVector3Buffer(getFloatBuffer(Type.Position), vertCount)); + + // Normals + setBuffer(Type.Normal, 3, createVector3Buffer(getFloatBuffer(Type.Normal), vertCount)); + + // Texture co-ordinates + setBuffer(Type.TexCoord, 2, createVector2Buffer(vertCount)); + + int triCount = ((closed ? 2 : 0) + 2 * (axisSamples - 1)) * radialSamples; + + setBuffer(Type.Index, 3, createShortBuffer(getShortBuffer(Type.Index), 3 * triCount)); + + // generate geometry + float inverseRadial = 1.0f / radialSamples; + float inverseAxisLess = 1.0f / (closed ? axisSamples - 3 : axisSamples - 1); + float inverseAxisLessTexture = 1.0f / (axisSamples - 1); + float halfHeight = 0.5f * height; + + // Generate points on the unit circle to be used in computing the mesh + // points on a cylinder slice. + float[] sin = new float[radialSamples + 1]; + float[] cos = new float[radialSamples + 1]; + + for (int radialCount = 0; radialCount < radialSamples; radialCount++) { + float angle = FastMath.TWO_PI * inverseRadial * radialCount; + cos[radialCount] = FastMath.cos(angle); + sin[radialCount] = FastMath.sin(angle); + } + sin[radialSamples] = sin[0]; + cos[radialSamples] = cos[0]; + + // calculate normals + Vector3f[] vNormals = null; + Vector3f vNormal = Vector3f.UNIT_Z; + + if ((height != 0.0f) && (radius != radius2)) { + vNormals = new Vector3f[radialSamples]; + Vector3f vHeight = Vector3f.UNIT_Z.mult(height); + Vector3f vRadial = new Vector3f(); + + for (int radialCount = 0; radialCount < radialSamples; radialCount++) { + vRadial.set(cos[radialCount], sin[radialCount], 0.0f); + Vector3f vRadius = vRadial.mult(radius); + Vector3f vRadius2 = vRadial.mult(radius2); + Vector3f vMantle = vHeight.subtract(vRadius2.subtract(vRadius)); + Vector3f vTangent = vRadial.cross(Vector3f.UNIT_Z); + vNormals[radialCount] = vMantle.cross(vTangent).normalize(); + } + } + + FloatBuffer nb = getFloatBuffer(Type.Normal); + FloatBuffer pb = getFloatBuffer(Type.Position); + FloatBuffer tb = getFloatBuffer(Type.TexCoord); + + // generate the cylinder itself + Vector3f tempNormal = new Vector3f(); + for (int axisCount = 0, i = 0; axisCount < axisSamples; axisCount++, i++) { + float axisFraction; + float axisFractionTexture; + int topBottom = 0; + if (!closed) { + axisFraction = axisCount * inverseAxisLess; // in [0,1] + axisFractionTexture = axisFraction; + } else { + if (axisCount == 0) { + topBottom = -1; // bottom + axisFraction = 0; + axisFractionTexture = inverseAxisLessTexture; + } else if (axisCount == axisSamples - 1) { + topBottom = 1; // top + axisFraction = 1; + axisFractionTexture = 1 - inverseAxisLessTexture; + } else { + axisFraction = (axisCount - 1) * inverseAxisLess; + axisFractionTexture = axisCount * inverseAxisLessTexture; + } + } + + // compute center of slice + float z = -halfHeight + height * axisFraction; + Vector3f sliceCenter = new Vector3f(0, 0, z); + + // compute slice vertices with duplication at end point + int save = i; + for (int radialCount = 0; radialCount < radialSamples; radialCount++, i++) { + float radialFraction = radialCount * inverseRadial; // in [0,1) + tempNormal.set(cos[radialCount], sin[radialCount], 0.0f); + + if (vNormals != null) { + vNormal = vNormals[radialCount]; + } else if (radius == radius2) { + vNormal = tempNormal; + } + + if (topBottom == 0) { + if (!inverted) + nb.put(vNormal.x).put(vNormal.y).put(vNormal.z); + else + nb.put(-vNormal.x).put(-vNormal.y).put(-vNormal.z); + } else { + nb.put(0).put(0).put(topBottom * (inverted ? -1 : 1)); + } + + tempNormal.multLocal((radius - radius2) * axisFraction + radius2) + .addLocal(sliceCenter); + pb.put(tempNormal.x).put(tempNormal.y).put(tempNormal.z); + + tb.put((inverted ? 1 - radialFraction : radialFraction)) + .put(axisFractionTexture); + } + + BufferUtils.copyInternalVector3(pb, save, i); + BufferUtils.copyInternalVector3(nb, save, i); + + tb.put((inverted ? 0.0f : 1.0f)) + .put(axisFractionTexture); + } + + if (closed) { + pb.put(0).put(0).put(-halfHeight); // bottom center + nb.put(0).put(0).put(-1 * (inverted ? -1 : 1)); + tb.put(0.5f).put(0); + pb.put(0).put(0).put(halfHeight); // top center + nb.put(0).put(0).put(1 * (inverted ? -1 : 1)); + tb.put(0.5f).put(1); + } + + IndexBuffer ib = getIndexBuffer(); + int index = 0; + // Connectivity + for (int axisCount = 0, axisStart = 0; axisCount < axisSamples - 1; axisCount++) { + int i0 = axisStart; + int i1 = i0 + 1; + axisStart += radialSamples + 1; + int i2 = axisStart; + int i3 = i2 + 1; + for (int i = 0; i < radialSamples; i++) { + if (closed && axisCount == 0) { + if (!inverted) { + ib.put(index++, i0++); + ib.put(index++, vertCount - 2); + ib.put(index++, i1++); + } else { + ib.put(index++, i0++); + ib.put(index++, i1++); + ib.put(index++, vertCount - 2); + } + } else if (closed && axisCount == axisSamples - 2) { + ib.put(index++, i2++); + ib.put(index++, inverted ? vertCount - 1 : i3++); + ib.put(index++, inverted ? i3++ : vertCount - 1); + } else { + ib.put(index++, i0++); + ib.put(index++, inverted ? i2 : i1); + ib.put(index++, inverted ? i1 : i2); + ib.put(index++, i1++); + ib.put(index++, inverted ? i2++ : i3++); + ib.put(index++, inverted ? i3++ : i2++); + } + } + } + + updateBound(); + } + + @Override + 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); + } + + @Override + 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/jme3-core/src/main/java/com/jme3/scene/shape/Dome.java b/jme3-core/src/main/java/com/jme3/scene/shape/Dome.java new file mode 100644 index 000000000..4494cce30 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Dome.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// $Id: Dome.java 4131 2009-03-19 20:15:28Z blaine.dev $ +package com.jme3.scene.shape; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + * A hemisphere. + * + * @author Peter Andersson + * @author Joshua Slack (Original sphere code that was adapted) + * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $ + */ +public class Dome extends Mesh { + + private int planes; + private int radialSamples; + /** The radius of the dome */ + private float radius; + /** The center of the dome */ + private Vector3f center; + private boolean insideView = true; + + /** + * Serialization only. Do not use. + */ + public Dome() { + } + + /** + * Constructs a dome for use as a SkyDome. The SkyDome is centered at the origin + * and only visible from the inside. + * @param planes + * The number of planes along the Z-axis. Must be >= 2. + * Influences how round the arch of the dome is. + * @param radialSamples + * The number of samples along the radial. + * Influences how round the base of the dome is. + * @param radius + * Radius of the dome. + * @see #Dome(com.jme.math.Vector3f, int, int, float) + */ + public Dome(int planes, int radialSamples, float radius) { + this(new Vector3f(0, 0, 0), planes, radialSamples, radius); + } + + /** + * Constructs a dome visible from the inside, e.g. for use as a SkyDome. + * All geometry data buffers are updated automatically.
      + * For a cone, set planes=2. For a pyramid, set radialSamples=4 and planes=2. + * Increasing planes and radialSamples increase the quality of the dome. + * + * @param center + * Center of the dome. + * @param planes + * The number of planes along the Z-axis. Must be >= 2. + * Influences how round the arch of the dome is. + * @param radialSamples + * The number of samples along the radial. + * Influences how round the base of the dome is. + * @param radius + * The radius of the dome. + */ + public Dome(Vector3f center, int planes, int radialSamples, + float radius) { + super(); + updateGeometry(center, planes, radialSamples, radius, true); + } + + /** + * Constructs a dome. Use this constructor for half-sphere, pyramids, or cones. + * All geometry data buffers are updated automatically.
      + * For a cone, set planes=2. For a pyramid, set radialSamples=4 and planes=2. + * Setting higher values for planes and radialSamples increases + * the quality of the half-sphere. + * + * @param center + * Center of the dome. + * @param planes + * The number of planes along the Z-axis. Must be >= 2. + * Influences how round the arch of the dome is. + * @param radialSamples + * The number of samples along the radial. + * Influences how round the base of the dome is. + * @param radius + * The radius of the dome. + * @param insideView + * If true, the dome is only visible from the inside, like a SkyDome. + * If false, the dome is only visible from the outside. + */ + public Dome(Vector3f center, int planes, int radialSamples, + float radius, boolean insideView) { + super(); + updateGeometry(center, planes, radialSamples, radius, insideView); + } + + public Vector3f getCenter() { + return center; + } + + /** + * Get the number of planar segments along the z-axis of the dome. + */ + public int getPlanes() { + return planes; + } + + /** + * Get the number of samples radially around the main axis of the dome. + */ + public int getRadialSamples() { + return radialSamples; + } + + /** + * Get the radius of the dome. + */ + public float getRadius() { + return radius; + } + + /** + * Are the triangles connected in such a way as to present a view out from the dome or not. + */ + public boolean isInsideView() { + return insideView; + } + + /** + * Rebuilds the dome with a new set of parameters. + * + * @param center the new center of the dome. + * @param planes the number of planes along the Z-axis. + * @param radialSamples the new number of radial samples of the dome. + * @param radius the new radius of the dome. + * @param insideView should the dome be set up to be viewed from the inside looking out. + */ + public void updateGeometry(Vector3f center, int planes, + int radialSamples, float radius, boolean insideView) { + this.insideView = insideView; + this.center = center != null ? center : new Vector3f(0, 0, 0); + this.planes = planes; + this.radialSamples = radialSamples; + this.radius = radius; + + int vertCount = ((planes - 1) * (radialSamples + 1)) + 1; + + // Allocate vertices, allocating one extra in each radial to get the + // correct texture coordinates +// setVertexCount(); +// setVertexBuffer(createVector3Buffer(getVertexCount())); + + // allocate normals +// setNormalBuffer(createVector3Buffer(getVertexCount())); + + // allocate texture coordinates +// getTextureCoords().set(0, new TexCoords(createVector2Buffer(getVertexCount()))); + + FloatBuffer vb = BufferUtils.createVector3Buffer(vertCount); + FloatBuffer nb = BufferUtils.createVector3Buffer(vertCount); + FloatBuffer tb = BufferUtils.createVector2Buffer(vertCount); + setBuffer(Type.Position, 3, vb); + setBuffer(Type.Normal, 3, nb); + setBuffer(Type.TexCoord, 2, tb); + + // generate geometry + float fInvRS = 1.0f / radialSamples; + float fYFactor = 1.0f / (planes - 1); + + // Generate points on the unit circle to be used in computing the mesh + // points on a dome slice. + float[] afSin = new float[(radialSamples)]; + float[] afCos = new float[(radialSamples)]; + for (int iR = 0; iR < radialSamples; iR++) { + float fAngle = FastMath.TWO_PI * fInvRS * iR; + afCos[iR] = FastMath.cos(fAngle); + afSin[iR] = FastMath.sin(fAngle); + } + + TempVars vars = TempVars.get(); + Vector3f tempVc = vars.vect3; + Vector3f tempVb = vars.vect2; + Vector3f tempVa = vars.vect1; + + // generate the dome itself + int i = 0; + for (int iY = 0; iY < (planes - 1); iY++, i++) { + float fYFraction = fYFactor * iY; // in (0,1) + float fY = radius * fYFraction; + // compute center of slice + Vector3f kSliceCenter = tempVb.set(center); + kSliceCenter.y += fY; + + // compute radius of slice + float fSliceRadius = FastMath.sqrt(FastMath.abs(radius * radius - fY * fY)); + + // compute slice vertices + Vector3f kNormal; + int iSave = i; + for (int iR = 0; iR < radialSamples; iR++, i++) { + float fRadialFraction = iR * fInvRS; // in [0,1) + Vector3f kRadial = tempVc.set(afCos[iR], 0, afSin[iR]); + kRadial.mult(fSliceRadius, tempVa); + vb.put(kSliceCenter.x + tempVa.x).put( + kSliceCenter.y + tempVa.y).put( + kSliceCenter.z + tempVa.z); + + BufferUtils.populateFromBuffer(tempVa, vb, i); + kNormal = tempVa.subtractLocal(center); + kNormal.normalizeLocal(); + if (insideView) { + nb.put(kNormal.x).put(kNormal.y).put(kNormal.z); + } else { + nb.put(-kNormal.x).put(-kNormal.y).put(-kNormal.z); + } + + tb.put(fRadialFraction).put(fYFraction); + } + BufferUtils.copyInternalVector3(vb, iSave, i); + BufferUtils.copyInternalVector3(nb, iSave, i); + tb.put(1.0f).put(fYFraction); + } + + vars.release(); + + // pole + vb.put(center.x).put(center.y + radius).put(center.z); + nb.put(0).put(insideView ? 1 : -1).put(0); + tb.put(0.5f).put(1.0f); + + // allocate connectivity + int triCount = (planes - 2) * radialSamples * 2 + radialSamples; + ShortBuffer ib = BufferUtils.createShortBuffer(3 * triCount); + setBuffer(Type.Index, 3, ib); + + // generate connectivity + int index = 0; + // Generate only for middle planes + for (int plane = 1; plane < (planes - 1); plane++) { + int bottomPlaneStart = ((plane - 1) * (radialSamples + 1)); + int topPlaneStart = (plane * (radialSamples + 1)); + for (int sample = 0; sample < radialSamples; sample++, index += 6) { + if (insideView){ + ib.put((short) (bottomPlaneStart + sample)); + ib.put((short) (bottomPlaneStart + sample + 1)); + ib.put((short) (topPlaneStart + sample)); + ib.put((short) (bottomPlaneStart + sample + 1)); + ib.put((short) (topPlaneStart + sample + 1)); + ib.put((short) (topPlaneStart + sample)); + }else{ + ib.put((short) (bottomPlaneStart + sample)); + ib.put((short) (topPlaneStart + sample)); + ib.put((short) (bottomPlaneStart + sample + 1)); + ib.put((short) (bottomPlaneStart + sample + 1)); + ib.put((short) (topPlaneStart + sample)); + ib.put((short) (topPlaneStart + sample + 1)); + } + } + } + + // pole triangles + int bottomPlaneStart = (planes - 2) * (radialSamples + 1); + for (int samples = 0; samples < radialSamples; samples++, index += 3) { + if (insideView){ + ib.put((short) (bottomPlaneStart + samples)); + ib.put((short) (bottomPlaneStart + samples + 1)); + ib.put((short) (vertCount - 1)); + }else{ + ib.put((short) (bottomPlaneStart + samples)); + ib.put((short) (vertCount - 1)); + ib.put((short) (bottomPlaneStart + samples + 1)); + } + } + + updateBound(); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + planes = capsule.readInt("planes", 0); + radialSamples = capsule.readInt("radialSamples", 0); + radius = capsule.readFloat("radius", 0); + center = (Vector3f) capsule.readSavable("center", Vector3f.ZERO.clone()); + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(planes, "planes", 0); + capsule.write(radialSamples, "radialSamples", 0); + capsule.write(radius, "radius", 0); + capsule.write(center, "center", Vector3f.ZERO); + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Line.java b/jme3-core/src/main/java/com/jme3/scene/shape/Line.java new file mode 100644 index 000000000..e2ac6d85c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Line.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.shape; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import java.io.IOException; +import java.nio.FloatBuffer; + +/** + * A simple line implementation with a start and an end. + * + * @author Brent Owens + */ +public class Line extends Mesh { + + private Vector3f start; + private Vector3f end; + + public Line() { + } + + public Line(Vector3f start, Vector3f end) { + setMode(Mode.Lines); + updateGeometry(start, end); + } + + protected void updateGeometry(Vector3f start, Vector3f end) { + this.start = start; + this.end = end; + setBuffer(Type.Position, 3, new float[]{start.x, start.y, start.z, + end.x, end.y, end.z,}); + + + setBuffer(Type.TexCoord, 2, new float[]{0, 0, + 1, 1}); + + setBuffer(Type.Normal, 3, new float[]{0, 0, 1, + 0, 0, 1}); + + setBuffer(Type.Index, 2, 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.rewind(); + 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/jme3-core/src/main/java/com/jme3/scene/shape/PQTorus.java b/jme3-core/src/main/java/com/jme3/scene/shape/PQTorus.java new file mode 100644 index 000000000..041051dcf --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/PQTorus.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// $Id: PQTorus.java 4131 2009-03-19 20:15:28Z blaine.dev $ +package com.jme3.scene.shape; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import static com.jme3.util.BufferUtils.*; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + * A parameterized torus, also known as a 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 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 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; + } + + /** + * 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, T, N, B; + Vector3f tempNorm = new Vector3f(); + float r, x, y, z, theta = 0.0f, beta; + 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 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); + } + + @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/jme3-core/src/main/java/com/jme3/scene/shape/Quad.java b/jme3-core/src/main/java/com/jme3/scene/shape/Quad.java new file mode 100644 index 000000000..194535851 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Quad.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.scene.shape; + +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; + +/** + * Quad represents a rectangular plane in space + * defined by 4 vertices. The quad's lower-left side is contained + * at the local space origin (0, 0, 0), while the upper-right + * side is located at the width/height coordinates (width, height, 0). + * + * @author Kirill Vainer + */ +public class Quad extends Mesh { + + private float width; + private float height; + + /** + * Serialization only. Do not use. + */ + public Quad(){ + } + + /** + * Create a quad with the given width and height. The quad + * is always created in the XY plane. + * + * @param width The X extent or width + * @param height The Y extent or width + */ + public Quad(float width, float height){ + updateGeometry(width, height); + } + + /** + * Create a quad with the given width and height. The quad + * is always created in the XY plane. + * + * @param width The X extent or width + * @param height The Y extent or width + * @param flipCoords If true, the texture coordinates will be flipped + * along the Y axis. + */ + public Quad(float width, float height, boolean flipCoords){ + updateGeometry(width, height, flipCoords); + } + + public float getHeight() { + return height; + } + + public float getWidth() { + return width; + } + + public void updateGeometry(float width, float height){ + updateGeometry(width, height, false); + } + + public void updateGeometry(float width, float height, boolean flipCoords) { + this.width = width; + this.height = height; + setBuffer(Type.Position, 3, new float[]{0, 0, 0, + width, 0, 0, + width, height, 0, + 0, height, 0 + }); + + + if (flipCoords){ + setBuffer(Type.TexCoord, 2, new float[]{0, 1, + 1, 1, + 1, 0, + 0, 0}); + }else{ + setBuffer(Type.TexCoord, 2, new float[]{0, 0, + 1, 0, + 1, 1, + 0, 1}); + } + setBuffer(Type.Normal, 3, new float[]{0, 0, 1, + 0, 0, 1, + 0, 0, 1, + 0, 0, 1}); + if (height < 0){ + setBuffer(Type.Index, 3, new short[]{0, 2, 1, + 0, 3, 2}); + }else{ + setBuffer(Type.Index, 3, new short[]{0, 1, 2, + 0, 2, 3}); + } + + updateBound(); + } + + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Sphere.java b/jme3-core/src/main/java/com/jme3/scene/shape/Sphere.java new file mode 100644 index 000000000..89db073a7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Sphere.java @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// $Id: Sphere.java 4163 2009-03-25 01:14:55Z matt.yellen $ +package com.jme3.scene.shape; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + * 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; + + /** + * Serialization only. Do not use. + */ + public Sphere() { + } + + /** + * Constructs a sphere. All geometry data buffers are updated automatically. + * Both zSamples and radialSamples increase the quality of the generated + * sphere. + * + * @param zSamples + * The number of samples along the Z. + * @param radialSamples + * The number of samples along the radial. + * @param radius + * The radius of the sphere. + */ + public Sphere(int zSamples, int radialSamples, float radius) { + this(zSamples, radialSamples, radius, false, false); + } + + /** + * Constructs a sphere. Additional arg to evenly space latitudinal slices + * + * @param zSamples + * The number of samples along the Z. + * @param radialSamples + * The number of samples along the radial. + * @param radius + * The radius of the sphere. + * @param useEvenSlices + * Slice sphere evenly along the Z axis + * @param interior + * Not yet documented + */ + public Sphere(int zSamples, int radialSamples, float radius, boolean useEvenSlices, boolean interior) { + updateGeometry(zSamples, radialSamples, radius, useEvenSlices, interior); + } + + public int getRadialSamples() { + return radialSamples; + } + + public float getRadius() { + return radius; + } + + /** + * @return Returns the textureMode. + */ + public TextureMode getTextureMode() { + return textureMode; + } + + public int getZSamples() { + return zSamples; + } + + /** + * builds the vertices based on the radius, radial and zSamples. + */ + private void setGeometryData() { + // allocate vertices + vertCount = (zSamples - 2) * (radialSamples + 1) + 2; + + FloatBuffer posBuf = BufferUtils.createVector3Buffer(vertCount); + + // allocate normals if requested + FloatBuffer normBuf = BufferUtils.createVector3Buffer(vertCount); + + // allocate texture coordinates + FloatBuffer texBuf = BufferUtils.createVector2Buffer(vertCount); + + setBuffer(Type.Position, 3, posBuf); + setBuffer(Type.Normal, 3, normBuf); + setBuffer(Type.TexCoord, 2, texBuf); + + // generate geometry + float fInvRS = 1.0f / radialSamples; + float fZFactor = 2.0f / (zSamples - 1); + + // Generate points on the unit circle to be used in computing the mesh + // points on a sphere slice. + float[] afSin = new float[(radialSamples + 1)]; + float[] afCos = new float[(radialSamples + 1)]; + for (int iR = 0; iR < radialSamples; iR++) { + float fAngle = FastMath.TWO_PI * fInvRS * iR; + afCos[iR] = FastMath.cos(fAngle); + afSin[iR] = FastMath.sin(fAngle); + } + afSin[radialSamples] = afSin[0]; + afCos[radialSamples] = afCos[0]; + + TempVars vars = TempVars.get(); + Vector3f tempVa = vars.vect1; + Vector3f tempVb = vars.vect2; + Vector3f tempVc = vars.vect3; + + // generate the sphere itself + int i = 0; + for (int iZ = 1; iZ < (zSamples - 1); iZ++) { + float fAFraction = FastMath.HALF_PI * (-1.0f + fZFactor * iZ); // in (-pi/2, pi/2) + float fZFraction; + if (useEvenSlices) { + fZFraction = -1.0f + fZFactor * iZ; // in (-1, 1) + } else { + fZFraction = FastMath.sin(fAFraction); // in (-1,1) + } + float fZ = radius * fZFraction; + + // compute center of slice + Vector3f kSliceCenter = tempVb.set(Vector3f.ZERO); + kSliceCenter.z += fZ; + + // compute radius of slice + float fSliceRadius = FastMath.sqrt(FastMath.abs(radius * radius + - fZ * fZ)); + + // compute slice vertices with duplication at end point + Vector3f kNormal; + int iSave = i; + for (int iR = 0; iR < radialSamples; iR++) { + float fRadialFraction = iR * fInvRS; // in [0,1) + Vector3f kRadial = tempVc.set(afCos[iR], afSin[iR], 0); + kRadial.mult(fSliceRadius, tempVa); + posBuf.put(kSliceCenter.x + tempVa.x).put( + kSliceCenter.y + tempVa.y).put( + kSliceCenter.z + tempVa.z); + + BufferUtils.populateFromBuffer(tempVa, posBuf, i); + kNormal = tempVa; + kNormal.normalizeLocal(); + if (!interior) // allow interior texture vs. exterior + { + normBuf.put(kNormal.x).put(kNormal.y).put( + kNormal.z); + } else { + normBuf.put(-kNormal.x).put(-kNormal.y).put( + -kNormal.z); + } + + if (textureMode == TextureMode.Original) { + texBuf.put(fRadialFraction).put( + 0.5f * (fZFraction + 1.0f)); + } else if (textureMode == TextureMode.Projected) { + texBuf.put(fRadialFraction).put( + FastMath.INV_PI + * (FastMath.HALF_PI + FastMath.asin(fZFraction))); + } else if (textureMode == TextureMode.Polar) { + float r = (FastMath.HALF_PI - FastMath.abs(fAFraction)) / FastMath.PI; + float u = r * afCos[iR] + 0.5f; + float v = r * afSin[iR] + 0.5f; + texBuf.put(u).put(v); + } + + i++; + } + + BufferUtils.copyInternalVector3(posBuf, iSave, i); + BufferUtils.copyInternalVector3(normBuf, iSave, i); + + if (textureMode == TextureMode.Original) { + texBuf.put(1.0f).put( + 0.5f * (fZFraction + 1.0f)); + } else if (textureMode == TextureMode.Projected) { + texBuf.put(1.0f).put( + FastMath.INV_PI + * (FastMath.HALF_PI + FastMath.asin(fZFraction))); + } else if (textureMode == TextureMode.Polar) { + float r = (FastMath.HALF_PI - FastMath.abs(fAFraction)) / FastMath.PI; + texBuf.put(r + 0.5f).put(0.5f); + } + + i++; + } + + vars.release(); + + // south pole + posBuf.position(i * 3); + posBuf.put(0f).put(0f).put(-radius); + + normBuf.position(i * 3); + if (!interior) { + normBuf.put(0).put(0).put(-1); // allow for inner + } // texture orientation + // later. + else { + normBuf.put(0).put(0).put(1); + } + + texBuf.position(i * 2); + + if (textureMode == TextureMode.Polar) { + texBuf.put(0.5f).put(0.5f); + } else { + texBuf.put(0.5f).put(0.0f); + } + + i++; + + // north pole + posBuf.put(0).put(0).put(radius); + + if (!interior) { + normBuf.put(0).put(0).put(1); + } else { + normBuf.put(0).put(0).put(-1); + } + + if (textureMode == TextureMode.Polar) { + texBuf.put(0.5f).put(0.5f); + } else { + texBuf.put(0.5f).put(1.0f); + } + + updateBound(); + setStatic(); + } + + /** + * sets the indices for rendering the sphere. + */ + private void setIndexData() { + // allocate connectivity + triCount = 2 * (zSamples - 2) * radialSamples; + ShortBuffer idxBuf = BufferUtils.createShortBuffer(3 * triCount); + setBuffer(Type.Index, 3, idxBuf); + + // generate connectivity + int index = 0; + for (int iZ = 0, iZStart = 0; iZ < (zSamples - 3); iZ++) { + int i0 = iZStart; + int i1 = i0 + 1; + iZStart += (radialSamples + 1); + int i2 = iZStart; + int i3 = i2 + 1; + for (int i = 0; i < radialSamples; i++, index += 6) { + if (!interior) { + idxBuf.put((short) i0++); + idxBuf.put((short) i1); + idxBuf.put((short) i2); + idxBuf.put((short) i1++); + idxBuf.put((short) i3++); + idxBuf.put((short) i2++); + } else { // inside view + idxBuf.put((short) i0++); + idxBuf.put((short) i2); + idxBuf.put((short) i1); + idxBuf.put((short) i1++); + idxBuf.put((short) i2++); + idxBuf.put((short) i3++); + } + } + } + + // south pole triangles + for (int i = 0; i < radialSamples; i++, index += 3) { + if (!interior) { + idxBuf.put((short) i); + idxBuf.put((short) (vertCount - 2)); + idxBuf.put((short) (i + 1)); + } else { // inside view + idxBuf.put((short) i); + idxBuf.put((short) (i + 1)); + idxBuf.put((short) (vertCount - 2)); + } + } + + // north pole triangles + int iOffset = (zSamples - 3) * (radialSamples + 1); + for (int i = 0; i < radialSamples; i++, index += 3) { + if (!interior) { + idxBuf.put((short) (i + iOffset)); + idxBuf.put((short) (i + 1 + iOffset)); + idxBuf.put((short) (vertCount - 1)); + } else { // inside view + idxBuf.put((short) (i + iOffset)); + idxBuf.put((short) (vertCount - 1)); + idxBuf.put((short) (i + 1 + iOffset)); + } + } + } + + /** + * @param textureMode + * The textureMode to set. + */ + public void setTextureMode(TextureMode textureMode) { + this.textureMode = textureMode; + setGeometryData(); + } + + /** + * Changes the information of the sphere into the given values. + * + * @param zSamples the number of zSamples of the sphere. + * @param radialSamples the number of radial samples of the sphere. + * @param radius the radius of the sphere. + */ + public void updateGeometry(int zSamples, int radialSamples, float radius) { + if (zSamples < 3) { + throw new IllegalArgumentException("zSamples cannot be smaller than 3"); + } + updateGeometry(zSamples, radialSamples, radius, false, false); + } + + public void updateGeometry(int zSamples, int radialSamples, float radius, boolean useEvenSlices, boolean interior) { + if (zSamples < 3) { + throw new IllegalArgumentException("zSamples cannot be smaller than 3"); + } + 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/jme3-core/src/main/java/com/jme3/scene/shape/StripBox.java b/jme3-core/src/main/java/com/jme3/scene/shape/StripBox.java new file mode 100644 index 000000000..312650caa --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/StripBox.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// $Id: Box.java 4131 2009-03-19 20:15:28Z blaine.dev $ +package com.jme3.scene.shape; + +import com.jme3.math.Vector3f; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; + +/** + * A box with solid (filled) faces. + * + * @author Mark Powell + * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $ + */ +public class StripBox extends AbstractBox { + + private static final short[] GEOMETRY_INDICES_DATA = + { 0, 1, 4, + 2, + 6, + 7, + 4, + 5, + 0, + 7, + 3, + 2, + 0, + 1}; + + private static final float[] GEOMETRY_TEXTURE_DATA = { + 1, 0, + 0, 0, + 0, 1, + 1, 1, + + 1, 0, + 0, 0, + 1, 1, + 0, 1 + }; + + /** + * Creates a new box. + *

      + * 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 x the size of the box along the x axis, in both directions. + * @param y the size of the box along the y axis, in both directions. + * @param z the size of the box along the z axis, in both directions. + */ + public StripBox(float x, float y, float z) { + super(); + updateGeometry(Vector3f.ZERO, x, y, z); + } + + /** + * Creates a new box. + *

      + * 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 center the center of the box. + * @param x the size of the box along the x axis, in both directions. + * @param y the size of the box along the y axis, in both directions. + * @param z the size of the box along the z axis, in both directions. + */ + public StripBox(Vector3f center, float x, float y, float z) { + super(); + updateGeometry(center, x, y, z); + } + + /** + * Constructor instantiates a new 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 com.jme3.scene.Spatial#setLocalTranslation(com.jme3.math.Vector3f) } and {@link com.jme3.scene.Spatial#setLocalRotation(com.jme3.math.Quaternion) } + * methods to define those properties. + * + * @param min the minimum point that defines the box. + * @param max the maximum point that defines the box. + */ + public StripBox(Vector3f min, Vector3f max) { + super(); + updateGeometry(min, max); + } + + /** + * Empty constructor for serialization only. Do not use. + */ + public StripBox(){ + super(); + } + + /** + * Creates a clone of this box. + *

      + * The cloned box will have ‘_clone’ appended to it’s name, but all other + * properties will be the same as this box. + */ + @Override + public StripBox clone() { + return new StripBox(center.clone(), xExtent, yExtent, zExtent); + } + + protected void duUpdateGeometryIndices() { + if (getBuffer(Type.Index) == null){ + setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA)); + } + } + + protected void duUpdateGeometryNormals() { + if (getBuffer(Type.Normal) == null){ + float[] normals = new float[8 * 3]; + + Vector3f[] vert = computeVertices(); + Vector3f norm = new Vector3f(); + + for (int i = 0; i < 8; i++) { + norm.set(vert[i]).normalizeLocal(); + + normals[i * 3 + 0] = norm.x; + normals[i * 3 + 1] = norm.y; + normals[i * 3 + 2] = norm.z; + } + + setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals)); + } + } + + protected void duUpdateGeometryTextures() { + if (getBuffer(Type.TexCoord) == null){ + setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA)); + } + } + + protected void duUpdateGeometryVertices() { + FloatBuffer fpb = BufferUtils.createVector3Buffer(8 * 3); + Vector3f[] v = computeVertices(); + fpb.put(new float[] { + v[0].x, v[0].y, v[0].z, + v[1].x, v[1].y, v[1].z, + v[2].x, v[2].y, v[2].z, + v[3].x, v[3].y, v[3].z, + v[4].x, v[4].y, v[4].z, + v[5].x, v[5].y, v[5].z, + v[6].x, v[6].y, v[6].z, + v[7].x, v[7].y, v[7].z, + }); + setBuffer(Type.Position, 3, fpb); + setMode(Mode.TriangleStrip); + updateBound(); + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Surface.java b/jme3-core/src/main/java/com/jme3/scene/shape/Surface.java new file mode 100644 index 000000000..2773212eb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Surface.java @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.shape; + +import com.jme3.math.CurveAndSurfaceMath; +import com.jme3.math.FastMath; +import com.jme3.math.Spline.SplineType; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.util.BufferUtils; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class represents a surface described by knots, weights and control points. + * Currently the following types are supported: + * a) NURBS + * @author Marcin Roguski (Kealthas) + */ +public class Surface extends Mesh { + + private SplineType type; //the type of the surface + private List> controlPoints; //space control points and their weights + private List[] knots; //knots of the surface + private int basisUFunctionDegree; //the degree of basis U function + private int basisVFunctionDegree; //the degree of basis V function + private int uSegments; //the amount of U segments + private int vSegments; //the amount of V segments + + /** + * Constructor. Constructs required surface. + * @param controlPoints space control points + * @param nurbKnots knots of the surface + * @param uSegments the amount of U segments + * @param vSegments the amount of V segments + * @param basisUFunctionDegree the degree of basis U function + * @param basisVFunctionDegree the degree of basis V function + */ + private Surface(List> controlPoints, List[] nurbKnots, + int uSegments, int vSegments, int basisUFunctionDegree, int basisVFunctionDegree) { + this.validateInputData(controlPoints, nurbKnots, uSegments, vSegments); + this.type = SplineType.Nurb; + this.uSegments = uSegments; + this.vSegments = vSegments; + this.controlPoints = controlPoints; + this.knots = nurbKnots; + this.basisUFunctionDegree = basisUFunctionDegree; + CurveAndSurfaceMath.prepareNurbsKnots(nurbKnots[0], basisUFunctionDegree); + if (nurbKnots[1] != null) { + this.basisVFunctionDegree = basisVFunctionDegree; + CurveAndSurfaceMath.prepareNurbsKnots(nurbKnots[1], basisVFunctionDegree); + } + + this.buildSurface(); + } + + /** + * This method creates a NURBS surface. + * @param controlPoints space control points + * @param nurbKnots knots of the surface + * @param uSegments the amount of U segments + * @param vSegments the amount of V segments + * @param basisUFunctionDegree the degree of basis U function + * @param basisVFunctionDegree the degree of basis V function + * @return an instance of NURBS surface + */ + public static final Surface createNurbsSurface(List> controlPoints, List[] nurbKnots, + int uSegments, int vSegments, int basisUFunctionDegree, int basisVFunctionDegree) { + Surface result = new Surface(controlPoints, nurbKnots, uSegments, vSegments, basisUFunctionDegree, basisVFunctionDegree); + result.type = SplineType.Nurb; + return result; + } + + /** + * This method creates the surface. + */ + private void buildSurface() { + boolean smooth = true;//TODO: take smoothing into consideration + float minUKnot = this.getMinUNurbKnot(); + float maxUKnot = this.getMaxUNurbKnot(); + float deltaU = (maxUKnot - minUKnot) / uSegments; + + float minVKnot = this.getMinVNurbKnot(); + float maxVKnot = this.getMaxVNurbKnot(); + float deltaV = (maxVKnot - minVKnot) / vSegments; + + Vector3f[] vertices = new Vector3f[(uSegments + 1) * (vSegments + 1)]; + + float u = minUKnot, v = minVKnot; + int arrayIndex = 0; + + for (int i = 0; i <= vSegments; ++i) { + for (int j = 0; j <= uSegments; ++j) { + Vector3f interpolationResult = new Vector3f(); + CurveAndSurfaceMath.interpolate(u, v, controlPoints, knots, basisUFunctionDegree, basisVFunctionDegree, interpolationResult); + vertices[arrayIndex++] = interpolationResult; + u += deltaU; + } + u = minUKnot; + v += deltaV; + } + + //adding indexes + int uVerticesAmount = uSegments + 1; + int[] indices = new int[uSegments * vSegments * 6]; + arrayIndex = 0; + for (int i = 0; i < vSegments; ++i) { + for (int j = 0; j < uSegments; ++j) { + indices[arrayIndex++] = j + i * uVerticesAmount; + indices[arrayIndex++] = j + i * uVerticesAmount + 1; + indices[arrayIndex++] = j + i * uVerticesAmount + uVerticesAmount; + indices[arrayIndex++] = j + i * uVerticesAmount + 1; + indices[arrayIndex++] = j + i * uVerticesAmount + uVerticesAmount + 1; + indices[arrayIndex++] = j + i * uVerticesAmount + uVerticesAmount; + } + } + + //normalMap merges normals of faces that will be rendered smooth + Map normalMap = new HashMap(vertices.length); + for (int i = 0; i < indices.length; i += 3) { + Vector3f n = FastMath.computeNormal(vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]); + this.addNormal(n, normalMap, smooth, vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]); + } + //preparing normal list (the order of normals must match the order of vertices) + float[] normals = new float[vertices.length * 3]; + arrayIndex = 0; + for (int i = 0; i < vertices.length; ++i) { + Vector3f n = normalMap.get(vertices[i]); + normals[arrayIndex++] = n.x; + normals[arrayIndex++] = n.y; + normals[arrayIndex++] = n.z; + } + + this.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(vertices)); + this.setBuffer(VertexBuffer.Type.Index, 3, indices); + this.setBuffer(VertexBuffer.Type.Normal, 3, normals); + this.updateBound(); + this.updateCounts(); + } + + public List> getControlPoints() { + return controlPoints; + } + + /** + * This method returns the amount of U control points. + * @return the amount of U control points + */ + public int getUControlPointsAmount() { + return controlPoints.size(); + } + + /** + * This method returns the amount of V control points. + * @return the amount of V control points + */ + public int getVControlPointsAmount() { + return controlPoints.get(0) == null ? 0 : controlPoints.get(0).size(); + } + + /** + * This method returns the degree of basis U function. + * @return the degree of basis U function + */ + public int getBasisUFunctionDegree() { + return basisUFunctionDegree; + } + + /** + * This method returns the degree of basis V function. + * @return the degree of basis V function + */ + public int getBasisVFunctionDegree() { + return basisVFunctionDegree; + } + + /** + * This method returns the knots for specified dimension (U knots - value: '0', + * V knots - value: '1'). + * @param dim an integer specifying if the U or V knots are required + * @return an array of knots + */ + public List getKnots(int dim) { + return knots[dim]; + } + + /** + * This method returns the type of the surface. + * @return the type of the surface + */ + public SplineType getType() { + return type; + } + + /** + * This method returns the minimum nurb curve U knot value. + * @return the minimum nurb curve knot value + */ + private float getMinUNurbKnot() { + return knots[0].get(basisUFunctionDegree - 1); + } + + /** + * This method returns the maximum nurb curve U knot value. + * @return the maximum nurb curve knot value + */ + private float getMaxUNurbKnot() { + return knots[0].get(knots[0].size() - basisUFunctionDegree); + } + + /** + * This method returns the minimum nurb curve U knot value. + * @return the minimum nurb curve knot value + */ + private float getMinVNurbKnot() { + return knots[1].get(basisVFunctionDegree - 1); + } + + /** + * This method returns the maximum nurb curve U knot value. + * @return the maximum nurb curve knot value + */ + private float getMaxVNurbKnot() { + return knots[1].get(knots[1].size() - basisVFunctionDegree); + } + + /** + * This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth. + * @param normalToAdd + * a normal to be added + * @param normalMap + * merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector + * @param smooth + * the variable that indicates wheather to merge normals (creating the smooth mesh) or not + * @param vertices + * a list of vertices read from the blender file + */ + private void addNormal(Vector3f normalToAdd, Map normalMap, boolean smooth, Vector3f... vertices) { + for (Vector3f v : vertices) { + Vector3f n = normalMap.get(v); + if (!smooth || n == null) { + normalMap.put(v, normalToAdd.clone()); + } else { + n.addLocal(normalToAdd).normalizeLocal(); + } + } + } + + /** + * This method validates the input data. It throws {@link IllegalArgumentException} if + * the data is invalid. + * @param controlPoints space control points + * @param nurbKnots knots of the surface + * @param uSegments the amount of U segments + * @param vSegments the amount of V segments + */ + private void validateInputData(List> controlPoints, List[] nurbKnots, + int uSegments, int vSegments) { + int uPointsAmount = controlPoints.get(0).size(); + for (int i = 1; i < controlPoints.size(); ++i) { + if (controlPoints.get(i).size() != uPointsAmount) { + throw new IllegalArgumentException("The amount of 'U' control points is invalid!"); + } + } + if (uSegments <= 0) { + throw new IllegalArgumentException("U segments amount should be positive!"); + } + if (vSegments < 0) { + throw new IllegalArgumentException("V segments amount cannot be negative!"); + } + if (nurbKnots.length != 2) { + throw new IllegalArgumentException("Nurb surface should have two rows of knots!"); + } + for (int i = 0; i < nurbKnots.length; ++i) { + for (int j = 0; j < nurbKnots[i].size() - 1; ++j) { + if (nurbKnots[i].get(j) > nurbKnots[i].get(j + 1)) { + throw new IllegalArgumentException("The knots' values cannot decrease!"); + } + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Torus.java b/jme3-core/src/main/java/com/jme3/scene/shape/Torus.java new file mode 100644 index 000000000..1be4146c6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Torus.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.shape; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + * An ordinary (single holed) torus. + *

      + * The center is by default the origin. + * + * @author Mark Powell + * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $ + */ +public class Torus extends Mesh { + + private int circleSamples; + + private int radialSamples; + + private float innerRadius; + + private float outerRadius; + + public Torus() { + } + + /** + * Constructs a new Torus. Center is the origin, but the Torus may be + * transformed. + * + * @param circleSamples + * The number of samples along the circles. + * @param radialSamples + * The number of samples along the radial. + * @param innerRadius + * The radius of the inner begining of the Torus. + * @param outerRadius + * The radius of the outter end of the Torus. + */ + public Torus(int circleSamples, int radialSamples, + float innerRadius, float outerRadius) { + super(); + updateGeometry(circleSamples, radialSamples, innerRadius, outerRadius); + } + + public int getCircleSamples() { + return circleSamples; + } + + public float getInnerRadius() { + return innerRadius; + } + + public float getOuterRadius() { + return outerRadius; + } + + public int getRadialSamples() { + return radialSamples; + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + circleSamples = capsule.readInt("circleSamples", 0); + radialSamples = capsule.readInt("radialSamples", 0); + innerRadius = capsule.readFloat("innerRadius", 0); + outerRadius = capsule.readFloat("outerRaidus", 0); + } + + private void setGeometryData() { + // allocate vertices + int vertCount = (circleSamples + 1) * (radialSamples + 1); + FloatBuffer fpb = BufferUtils.createVector3Buffer(vertCount); + setBuffer(Type.Position, 3, fpb); + + // allocate normals if requested + FloatBuffer fnb = BufferUtils.createVector3Buffer(vertCount); + setBuffer(Type.Normal, 3, fnb); + + // allocate texture coordinates + FloatBuffer ftb = BufferUtils.createVector2Buffer(vertCount); + setBuffer(Type.TexCoord, 2, ftb); + + // generate geometry + float inverseCircleSamples = 1.0f / circleSamples; + float inverseRadialSamples = 1.0f / radialSamples; + int i = 0; + // generate the cylinder itself + Vector3f radialAxis = new Vector3f(), torusMiddle = new Vector3f(), tempNormal = new Vector3f(); + for (int circleCount = 0; circleCount < circleSamples; circleCount++) { + // compute center point on torus circle at specified angle + float circleFraction = circleCount * inverseCircleSamples; + float theta = FastMath.TWO_PI * circleFraction; + float cosTheta = FastMath.cos(theta); + float sinTheta = FastMath.sin(theta); + radialAxis.set(cosTheta, sinTheta, 0); + radialAxis.mult(outerRadius, torusMiddle); + + // compute slice vertices with duplication at end point + int iSave = i; + for (int radialCount = 0; radialCount < radialSamples; radialCount++) { + float radialFraction = radialCount * inverseRadialSamples; + // in [0,1) + float phi = FastMath.TWO_PI * radialFraction; + float cosPhi = FastMath.cos(phi); + float sinPhi = FastMath.sin(phi); + tempNormal.set(radialAxis).multLocal(cosPhi); + tempNormal.z += sinPhi; + fnb.put(tempNormal.x).put(tempNormal.y).put( + tempNormal.z); + + tempNormal.multLocal(innerRadius).addLocal(torusMiddle); + fpb.put(tempNormal.x).put(tempNormal.y).put( + tempNormal.z); + + ftb.put(radialFraction).put(circleFraction); + i++; + } + + BufferUtils.copyInternalVector3(fpb, iSave, i); + BufferUtils.copyInternalVector3(fnb, iSave, i); + + ftb.put(1.0f).put(circleFraction); + + i++; + } + + // duplicate the cylinder ends to form a torus + for (int iR = 0; iR <= radialSamples; iR++, i++) { + BufferUtils.copyInternalVector3(fpb, iR, i); + BufferUtils.copyInternalVector3(fnb, iR, i); + BufferUtils.copyInternalVector2(ftb, iR, i); + ftb.put(i * 2 + 1, 1.0f); + } + } + + private void setIndexData() { + // allocate connectivity + int triCount = 2 * circleSamples * radialSamples; + + ShortBuffer sib = BufferUtils.createShortBuffer(3 * triCount); + setBuffer(Type.Index, 3, sib); + + int i; + // generate connectivity + int connectionStart = 0; + int index = 0; + for (int circleCount = 0; circleCount < circleSamples; circleCount++) { + int i0 = connectionStart; + int i1 = i0 + 1; + connectionStart += radialSamples + 1; + int i2 = connectionStart; + int i3 = i2 + 1; + for (i = 0; i < radialSamples; i++, index += 6) { +// if (true) { + sib.put((short)i0++); + sib.put((short)i2); + sib.put((short)i1); + sib.put((short)i1++); + sib.put((short)i2++); + sib.put((short)i3++); + +// getIndexBuffer().put(i0++); +// getIndexBuffer().put(i2); +// getIndexBuffer().put(i1); +// getIndexBuffer().put(i1++); +// getIndexBuffer().put(i2++); +// getIndexBuffer().put(i3++); +// } else { +// getIndexBuffer().put(i0++); +// getIndexBuffer().put(i1); +// getIndexBuffer().put(i2); +// getIndexBuffer().put(i1++); +// getIndexBuffer().put(i3++); +// getIndexBuffer().put(i2++); +// } + } + } + } + + /** + * Rebuilds this torus based on a new set of parameters. + * + * @param circleSamples the number of samples along the circles. + * @param radialSamples the number of samples along the radial. + * @param innerRadius the radius of the inner begining of the Torus. + * @param outerRadius the radius of the outter end of the Torus. + */ + public void updateGeometry(int circleSamples, int radialSamples, float innerRadius, float outerRadius) { + this.circleSamples = circleSamples; + this.radialSamples = radialSamples; + this.innerRadius = innerRadius; + this.outerRadius = outerRadius; + setGeometryData(); + setIndexData(); + updateBound(); + updateCounts(); + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(circleSamples, "circleSamples", 0); + capsule.write(radialSamples, "radialSamples", 0); + capsule.write(innerRadius, "innerRadius", 0); + capsule.write(outerRadius, "outerRadius", 0); + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/shader/Attribute.java b/jme3-core/src/main/java/com/jme3/shader/Attribute.java new file mode 100644 index 000000000..363970b36 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/Attribute.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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/jme3-core/src/main/java/com/jme3/shader/DefineList.java b/jme3-core/src/main/java/com/jme3/shader/DefineList.java new file mode 100644 index 000000000..511cd03b1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/DefineList.java @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +import com.jme3.export.*; +import com.jme3.material.MatParam; +import com.jme3.material.TechniqueDef; + +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; + +public class DefineList implements Savable, Cloneable { + + private static final String ONE = "1"; + + private TreeMap defines = new TreeMap(); + private String compiled = null; + private int cachedHashCode = 0; + + 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); + } + + 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]); + } + } + + public void clear() { + defines.clear(); + compiled = ""; + cachedHashCode = 0; + } + + public String get(String key){ + return defines.get(key); + } + + @Override + public DefineList clone() { + try { + DefineList clone = (DefineList) super.clone(); + clone.cachedHashCode = 0; + clone.compiled = null; + clone.defines = (TreeMap) defines.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + public boolean set(String key, VarType type, Object val){ + if (val == null){ + defines.remove(key); + compiled = null; + cachedHashCode = 0; + return true; + } + + switch (type){ + case Boolean: + if (((Boolean) val).booleanValue()) { + // same literal, != will work + if (defines.put(key, ONE) != ONE) { + compiled = null; + cachedHashCode = 0; + return true; + } + } else if (defines.containsKey(key)) { + defines.remove(key); + compiled = null; + cachedHashCode = 0; + return true; + } + + break; + case Float: + case Int: + String newValue = val.toString(); + String original = defines.put(key, newValue); + if (!val.equals(original)) { + compiled = null; + cachedHashCode = 0; + return true; + } + break; + default: + // same literal, != will work + if (defines.put(key, ONE) != ONE) { + compiled = null; + cachedHashCode = 0; + return true; + } + break; + } + + return false; + } + + public boolean remove(String key){ + if (defines.remove(key) != null) { + compiled = null; + cachedHashCode = 0; + return true; + } + return false; + } + + public void addFrom(DefineList other){ + if (other == null) { + return; + } + compiled = null; + cachedHashCode = 0; + 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 boolean equals(Object obj) { + final DefineList other = (DefineList) obj; + return defines.equals(other.defines); + } + + public boolean equalsParams(Collection params, TechniqueDef def) { + + int size = 0; + + for (MatParam param : params) { + String key = def.getShaderParamDefine(param.getName()); + if (key != null) { + Object val = param.getValue(); + if (val != null) { + + switch (param.getVarType()) { + case Boolean: { + String current = defines.get(key); + if (((Boolean) val).booleanValue()) { + if (current == null || current != ONE) { + return false; + } + size++; + } else { + if (current != null) { + return false; + } + } + } + break; + case Float: + case Int: { + String newValue = val.toString(); + String current = defines.get(key); + if (!newValue.equals(current)) { + return false; + } + size++; + } + break; + default: { + if (!defines.containsKey(key)) { + return false; + } + size++; + } + break; + } + + } + + } + } + + if (size != defines.size()) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + if (cachedHashCode == 0) { + cachedHashCode = defines.hashCode(); + } + return cachedHashCode; + } + + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + int i = 0; + for (Map.Entry entry : defines.entrySet()) { + sb.append(entry.getKey()).append("=").append(entry.getValue()); + if (i != defines.size() - 1) { + sb.append(", "); + } + i++; + } + return sb.toString(); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java new file mode 100644 index 000000000..d7c94c856 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java @@ -0,0 +1,577 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +import com.jme3.asset.AssetManager; +import com.jme3.material.ShaderGenerationInfo; +import com.jme3.material.plugins.ConditionParser; +import com.jme3.shader.Shader.ShaderType; +import java.util.ArrayList; +import java.util.List; + +/** + * This shader Generator can generate Vertex and Fragment shaders from + * shadernodes for GLSL 1.0 + * + * @author Nehon + */ +public class Glsl100ShaderGenerator extends ShaderGenerator { + + /** + * the indentation characters 1à tabulation characters + */ + private final static String INDENTCHAR = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + + /** + * creates a Glsl100ShaderGenerator + * @param assetManager the assetManager + */ + public Glsl100ShaderGenerator(AssetManager assetManager) { + super(assetManager); + } + + @Override + protected void generateUniforms(StringBuilder source, ShaderGenerationInfo info, ShaderType type) { + generateUniforms(source, type == ShaderType.Vertex ? info.getVertexUniforms() : info.getFragmentUniforms()); + } + + /** + * decalre a list of uniforms + * + * @param source the source to append to + * @param uniforms the list of uniforms + */ + protected void generateUniforms(StringBuilder source, List uniforms) { + source.append("\n"); + for (ShaderNodeVariable var : uniforms) { + declareVariable(source, var, false, "uniform"); + } + } + + /** + * {@inheritDoc} + * + * attributes are all declared, inPositon is decalred even if it's not in + * the list and it's condition is nulled. + */ + @Override + protected void generateAttributes(StringBuilder source, ShaderGenerationInfo info) { + source.append("\n"); + boolean inPosition = false; + for (ShaderNodeVariable var : info.getAttributes()) { + if (var.getName().equals("inPosition")) { + inPosition = true; + var.setCondition(null); + } + declareAttribute(source, var); + + } + if (!inPosition) { + declareAttribute(source, new ShaderNodeVariable("vec4", "inPosition")); + } + + } + + @Override + protected void generateVaryings(StringBuilder source, ShaderGenerationInfo info, ShaderType type) { + source.append("\n"); + for (ShaderNodeVariable var : info.getVaryings()) { + declareVarying(source, var, type == ShaderType.Vertex ? false : true); + } + } + + /** + * {@inheritDoc} + * + * if the declaration contains no code nothing is done, else it's appended + */ + @Override + protected void generateDeclarativeSection(StringBuilder source, ShaderNode shaderNode, String nodeSource, ShaderGenerationInfo info) { + if (nodeSource.replaceAll("\\n", "").trim().length() > 0) { + nodeSource = updateDefinesName(nodeSource, shaderNode); + source.append("\n"); + unIndent(); + startCondition(shaderNode.getCondition(), source); + source.append(nodeSource); + endCondition(shaderNode.getCondition(), source); + indent(); + } + } + + /** + * {@inheritDoc} + * + * Shader outputs are declared and initialized inside the main section + */ + @Override + protected void generateStartOfMainSection(StringBuilder source, ShaderGenerationInfo info, ShaderType type) { + source.append("\n"); + source.append("void main(){\n"); + indent(); + appendIndent(source); + if (type == ShaderType.Vertex) { + declareVariable(source, info.getVertexGlobal(), "inPosition"); + } else if (type == ShaderType.Fragment) { + for (ShaderNodeVariable global : info.getFragmentGlobals()) { + declareVariable(source, global, "vec4(1.0)"); + } + } + source.append("\n"); + } + + /** + * {@inheritDoc} + * + * outputs are assigned to built in glsl output. then the main section is + * closed + * + * This code accounts for multi render target and correctly output to + * gl_FragData if several output are declared for the fragment shader + */ + @Override + protected void generateEndOfMainSection(StringBuilder source, ShaderGenerationInfo info, ShaderType type) { + source.append("\n"); + if (type == ShaderType.Vertex) { + appendOutput(source, "gl_Position", info.getVertexGlobal()); + } else if (type == ShaderType.Fragment) { + List globals = info.getFragmentGlobals(); + if (globals.size() == 1) { + appendOutput(source, "gl_FragColor", globals.get(0)); + } else { + int i = 0; + //Multi Render Target + for (ShaderNodeVariable global : globals) { + appendOutput(source, "gl_FragData[" + i + "]", global); + i++; + } + } + } + unIndent(); + appendIndent(source); + source.append("}\n"); + } + + /** + * Appends an ouput assignment to a shader globalOutputName = + * nameSpace_varName; + * + * @param source the source StringBuilter to append the code. + * @param globalOutputName the name of the global output (can be gl_Position + * or gl_FragColor etc...). + * @param var the variable to assign to the output. + */ + protected void appendOutput(StringBuilder source, String globalOutputName, ShaderNodeVariable var) { + appendIndent(source); + source.append(globalOutputName); + source.append(" = "); + source.append(var.getNameSpace()); + source.append("_"); + source.append(var.getName()); + source.append(";\n"); + } + + /** + * {@inheritDoc} + * + * this methods does things in this order : + * + * 1. declaring and mapping input
      + * variables : variable replaced with MatParams or WorldParams are not + * declared and are replaced by the parma acual name in the code. For others + * variables, the name space is appended with a "_" before the variable name + * in the code to avoid names collision between shaderNodes.
      + * + * 2. declaring output variables :
      + * variables are declared if they were not already + * declared as input (inputs can also be outputs) or if they are not + * declared as varyings. The variable name is also prefixed with the s=name + * space and "_" in the shaderNode code
      + * + * 3. append of the actual ShaderNode code
      + * + * 4. mapping outputs to global output if needed
      + * + *
      + * All of this is embed in a #if coditional statement if needed + */ + @Override + protected void generateNodeMainSection(StringBuilder source, ShaderNode shaderNode, String nodeSource, ShaderGenerationInfo info) { + + nodeSource = updateDefinesName(nodeSource, shaderNode); + source.append("\n"); + comment(source, shaderNode, "Begin"); + startCondition(shaderNode.getCondition(), source); + + List declaredInputs = new ArrayList(); + for (VariableMapping mapping : shaderNode.getInputMapping()) { + + //all variables fed with a matparam or world param are replaced but the matparam itself + //it avoids issue with samplers that have to be uniforms, and it optimize a but the shader code. + if (isWorldOrMaterialParam(mapping.getRightVariable())) { + nodeSource = replace(nodeSource, mapping.getLeftVariable(), mapping.getRightVariable().getName()); + } else { + if (mapping.getLeftVariable().getType().startsWith("sampler")) { + throw new IllegalArgumentException("a Sampler must be a uniform"); + } + map(mapping, source); + String newName = shaderNode.getName() + "_" + mapping.getLeftVariable().getName(); + if (!declaredInputs.contains(newName)) { + nodeSource = replace(nodeSource, mapping.getLeftVariable(), newName); + declaredInputs.add(newName); + } + } + } + + for (ShaderNodeVariable var : shaderNode.getDefinition().getOutputs()) { + ShaderNodeVariable v = new ShaderNodeVariable(var.getType(), shaderNode.getName(), var.getName()); + if (!declaredInputs.contains(shaderNode.getName() + "_" + var.getName())) { + if (!isVarying(info, v)) { + declareVariable(source, v); + } + nodeSource = replaceVariableName(nodeSource, v); + } + } + + source.append(nodeSource); + + for (VariableMapping mapping : shaderNode.getOutputMapping()) { + map(mapping, source); + } + endCondition(shaderNode.getCondition(), source); + comment(source, shaderNode, "End"); + } + + /** + * declares a variable, embed in a conditional block if needed + * @param source the StringBuilder to use + * @param var the variable to declare + * @param appendNameSpace true to append the nameSpace + "_" + */ + protected void declareVariable(StringBuilder source, ShaderNodeVariable var, boolean appendNameSpace) { + declareVariable(source, var, appendNameSpace, null); + } + + /** + * declares a variable, embed in a conditional block if needed. the namespace is appended with "_" + * @param source the StringBuilder to use + * @param var the variable to declare + */ + protected void declareVariable(StringBuilder source, ShaderNodeVariable var) { + declareVariable(source, var, true, null); + } + + /** + * declares a variable, embed in a conditional block if needed. the namespace is appended with "_" + * @param source the StringBuilder to use + * @param var the variable to declare + * @param value the initialization value to assign the the variable + */ + protected void declareVariable(StringBuilder source, ShaderNodeVariable var, String value) { + declareVariable(source, var, value, true, null); + } + + /** + * declares a variable, embed in a conditional block if needed. + * @param source the StringBuilder to use + * @param var the variable to declare + * @param appendNameSpace true to append the nameSpace + "_" + * @param modifier the modifier of the variable (attribute, varying, in , out,...) + */ + protected void declareVariable(StringBuilder source, ShaderNodeVariable var, boolean appendNameSpace, String modifier) { + declareVariable(source, var, null, appendNameSpace, modifier); + } + + /** + * declares a variable, embed in a conditional block if needed. + * @param source the StringBuilder to use + * @param var the variable to declare + * @param value the initialization value to assign the the variable + * @param appendNameSpace true to append the nameSpace + "_" + * @param modifier the modifier of the variable (attribute, varying, in , out,...) + */ + protected void declareVariable(StringBuilder source, ShaderNodeVariable var, String value, boolean appendNameSpace, String modifier) { + startCondition(var.getCondition(), source); + appendIndent(source); + if (modifier != null) { + source.append(modifier); + source.append(" "); + } + + source.append(var.getType()); + source.append(" "); + if (appendNameSpace) { + source.append(var.getNameSpace()); + source.append("_"); + } + source.append(var.getName()); + if (var.getMultiplicity() != null) { + source.append("["); + source.append(var.getMultiplicity().toUpperCase()); + source.append("]"); + } + if (value != null) { + source.append(" = "); + source.append(value); + } + source.append(";\n"); + endCondition(var.getCondition(), source); + } + + /** + * Starts a conditional block + * @param condition the block condition + * @param source the StringBuilder to use + */ + protected void startCondition(String condition, StringBuilder source) { + if (condition != null) { + appendIndent(source); + source.append("#if "); + source.append(condition); + source.append("\n"); + indent(); + } + } + + /** + * Ends a conditional block + * @param condition the block condition + * @param source the StringBuilder to use + */ + protected void endCondition(String condition, StringBuilder source) { + if (condition != null) { + unIndent(); + appendIndent(source); + source.append("#endif\n"); + + } + } + + /** + * Appends a mapping to the source, embed in a conditional block if needed, + * with variables nameSpaces and swizzle. + * @param mapping the VariableMapping to append + * @param source the StringBuilder to use + */ + protected void map(VariableMapping mapping, StringBuilder source) { + startCondition(mapping.getCondition(), source); + appendIndent(source); + if (!mapping.getLeftVariable().isShaderOutput()) { + source.append(mapping.getLeftVariable().getType()); + source.append(" "); + } + source.append(mapping.getLeftVariable().getNameSpace()); + source.append("_"); + source.append(mapping.getLeftVariable().getName()); + if (mapping.getLeftSwizzling().length() > 0) { + source.append("."); + source.append(mapping.getLeftSwizzling()); + } + source.append(" = "); + String namePrefix = getAppendableNameSpace(mapping.getRightVariable()); + source.append(namePrefix); + source.append(mapping.getRightVariable().getName()); + if (mapping.getRightSwizzling().length() > 0) { + source.append("."); + source.append(mapping.getRightSwizzling()); + } + source.append(";\n"); + endCondition(mapping.getCondition(), source); + } + + /** + * replaces a variable name in a shaderNode source code by prefixing it + * with its nameSpace and "_" if needed. + * @param nodeSource the source ot modify + * @param var the variable to replace + * @return the modified source + */ + protected String replaceVariableName(String nodeSource, ShaderNodeVariable var) { + String namePrefix = getAppendableNameSpace(var); + String newName = namePrefix + var.getName(); + nodeSource = replace(nodeSource, var, newName); + return nodeSource; + } + + /** + * Finds if a variable is a varying + * @param info the ShaderGenerationInfo + * @param v the variable + * @return true is the given variable is a varying + */ + protected boolean isVarying(ShaderGenerationInfo info, ShaderNodeVariable v) { + boolean isVarying = false; + for (ShaderNodeVariable shaderNodeVariable : info.getVaryings()) { + if (shaderNodeVariable.equals(v)) { + isVarying = true; + } + } + return isVarying; + } + + /** + * Appends a comment to the generated code + * @param source the StringBuilder to use + * @param shaderNode the shader node being processed (to append its name) + * @param comment the comment to append + */ + protected void comment(StringBuilder source, ShaderNode shaderNode, String comment) { + appendIndent(source); + source.append("//"); + source.append(shaderNode.getName()); + source.append(" : "); + source.append(comment); + source.append("\n"); + } + + /** + * returns the name space to append for a variable. + * Attributes, WorldParam and MatParam names psace must not be appended + * @param var the variable + * @return the namespace to append for this variable + */ + protected String getAppendableNameSpace(ShaderNodeVariable var) { + String namePrefix = var.getNameSpace() + "_"; + if (namePrefix.equals("Attr_") || namePrefix.equals("WorldParam_") || namePrefix.equals("MatParam_")) { + namePrefix = ""; + } + return namePrefix; + } + + /** + * transforms defines name is the shader node code. + * One can use a #if defined(inputVariableName) in a shaderNode code. + * This method is responsible for changing the variable name with the + * appropriate defined based on the mapping condition of this variable. + * Complex condition synthax are handled. + * + * @param nodeSource the sahderNode source code + * @param shaderNode the ShaderNode being processed + * @return the modified shaderNode source. + */ + protected String updateDefinesName(String nodeSource, ShaderNode shaderNode) { + String[] lines = nodeSource.split("\\n"); + ConditionParser parser = new ConditionParser(); + for (String line : lines) { + + if (line.trim().startsWith("#if")) { + List params = parser.extractDefines(line.trim()); + String l = line.trim().replaceAll("defined", "").replaceAll("#if ", "").replaceAll("#ifdef", "");//parser.getFormattedExpression(); + boolean match = false; + for (String param : params) { + for (VariableMapping map : shaderNode.getInputMapping()) { + if ((map.getLeftVariable().getName()).equals(param)) { + if (map.getCondition() != null) { + l = l.replaceAll(param, map.getCondition()); + match = true; + } + } + } + } + if (match) { + nodeSource = nodeSource.replace(line.trim(), "#if " + l); + } + } + } + return nodeSource; + } + + /** + * replaced a variable name in a source code with the given name + * @param nodeSource the source to use + * @param var the variable + * @param newName the new name of the variable + * @return the modified source code + */ + protected String replace(String nodeSource, ShaderNodeVariable var, String newName) { + nodeSource = nodeSource.replaceAll("(\\W)" + var.getName() + "(\\W)", "$1" + newName + "$2"); + return nodeSource; + } + + /** + * Finds if a variable is a world or a material parameter + * @param var the variable + * @return true if the variable is a Word or material parameter + */ + protected boolean isWorldOrMaterialParam(ShaderNodeVariable var) { + return var.getNameSpace().equals("MatParam") || var.getNameSpace().equals("WorldParam"); + } + + @Override + protected String getLanguageAndVersion(ShaderType type) { + return "GLSL100"; + } + + /** + * appends indentation. + * @param source + */ + protected void appendIndent(StringBuilder source) { + source.append(INDENTCHAR.substring(0, indent)); + } + + /** + * Declares an attribute + * @param source the StringBuilder to use + * @param var the variable to declare as an attribute + */ + protected void declareAttribute(StringBuilder source, ShaderNodeVariable var) { + declareVariable(source, var, false, "attribute"); + } + + /** + * Declares a varying + * @param source the StringBuilder to use + * @param var the variable to declare as an varying + * @param input a boolean set to true if the this varying is an input. + * this in not used in this implementaiton but can be used in overidings + * implementation + */ + protected void declareVarying(StringBuilder source, ShaderNodeVariable var, boolean input) { + declareVariable(source, var, true, "varying"); + } + + /** + * Decrease indentation with a check so the indent is never negative. + */ + protected void unIndent() { + indent--; + indent = Math.max(0, indent); + } + + /** + * increase indentation with a check so that indentation is never over 10 + */ + protected void indent() { + indent++; + indent = Math.min(10, indent); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java new file mode 100644 index 000000000..8930d6281 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +import com.jme3.asset.AssetManager; +import com.jme3.material.ShaderGenerationInfo; +import com.jme3.shader.Shader.ShaderType; + +/** + * This shader Generator can generate Vertex and Fragment shaders from + * ShaderNodes for GLSL 1.5 + * + * @author Nehon + */ +public class Glsl150ShaderGenerator extends Glsl100ShaderGenerator { + + /** + * Creates a Glsl150ShaderGenerator + * + * @param assetManager the assetmanager + */ + public Glsl150ShaderGenerator(AssetManager assetManager) { + super(assetManager); + } + + @Override + protected String getLanguageAndVersion(ShaderType type) { + return "GLSL150"; + } + + /** + * {@inheritDoc} in glsl 1.5 attributes are prefixed with the "in" keyword + * and not the "attribute" keyword + */ + @Override + protected void declareAttribute(StringBuilder source, ShaderNodeVariable var) { + declareVariable(source, var, false, "in"); + } + + /** + * {@inheritDoc} in glsl 1.5 varying are prefixed with the "in" or "out" + * keyword and not the "varying" keyword. + * + * "in" is used for Fragment shader (maybe Geometry shader later) "out" is + * used for Vertex shader (maybe Geometry shader later) + */ + @Override + protected void declareVarying(StringBuilder source, ShaderNodeVariable var, boolean input) { + declareVariable(source, var, true, input ? "in" : "out"); + } + + /** + * {@inheritDoc} + * + * Fragment shader outputs are declared before the "void main(){" with the + * "out" keyword. + * + * after the "void main(){", the vertex output are declared and initialized + * and the frgament outputs are declared + */ + @Override + protected void generateStartOfMainSection(StringBuilder source, ShaderGenerationInfo info, Shader.ShaderType type) { + source.append("\n"); + + if (type == Shader.ShaderType.Fragment) { + for (ShaderNodeVariable global : info.getFragmentGlobals()) { + declareVariable(source, global, null, true, "out"); + } + } + source.append("\n"); + + appendIndent(source); + source.append("void main(){\n"); + indent(); + + if (type == Shader.ShaderType.Vertex) { + declareVariable(source, info.getVertexGlobal(), "inPosition"); + } else if (type == Shader.ShaderType.Fragment) { + for (ShaderNodeVariable global : info.getFragmentGlobals()) { + initVariable(source, global, "vec4(1.0)"); + } + } + } + + /** + * {@inheritDoc} + * + * only vertex shader output are mapped here, since fragment shader outputs + * must have been mapped in the main section. + */ + @Override + protected void generateEndOfMainSection(StringBuilder source, ShaderGenerationInfo info, Shader.ShaderType type) { + if (type == Shader.ShaderType.Vertex) { + appendOutput(source, "gl_Position", info.getVertexGlobal()); + } + unIndent(); + appendIndent(source); + source.append("}\n"); + } + + /** + * Append a variable initialization to the code + * + * @param source the StringBuilder to use + * @param var the variable to initialize + * @param initValue the init value to assign to the variable + */ + protected void initVariable(StringBuilder source, ShaderNodeVariable var, String initValue) { + appendIndent(source); + source.append(var.getNameSpace()); + source.append("_"); + source.append(var.getName()); + source.append(" = "); + source.append(initValue); + source.append(";\n"); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/Shader.java b/jme3-core/src/main/java/com/jme3/shader/Shader.java new file mode 100644 index 000000000..d0efe81b7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/Shader.java @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +import com.jme3.renderer.Renderer; +import com.jme3.scene.VertexBuffer; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import com.jme3.util.ListMap; +import com.jme3.util.NativeObject; +import java.util.ArrayList; +import java.util.Collection; + +public final class Shader extends NativeObject { + + /** + * A list of all shader sources currently attached. + */ + private ArrayList shaderSourceList; + + /** + * Maps uniform name to the uniform variable. + */ + private ListMap uniforms; + + /** + * Maps attribute name to the location of the attribute in the shader. + */ + private IntMap attribs; + + /** + * Type of shader. The shader will control the pipeline of it's type. + */ + public static enum ShaderType { + /** + * Control fragment rasterization. (e.g color of pixel). + */ + Fragment, + + /** + * Control vertex processing. (e.g transform of model to clip space) + */ + Vertex, + + /** + * Control geometry assembly. (e.g compile a triangle list from input data) + */ + Geometry; + } + + /** + * Shader source describes a shader object in OpenGL. Each shader source + * is assigned a certain pipeline which it controls (described by it's type). + */ + public static class ShaderSource extends NativeObject { + + ShaderType sourceType; + String language; + String name; + String source; + String defines; + + public ShaderSource(ShaderType type){ + super(); + this.sourceType = type; + if (type == null) { + throw new IllegalArgumentException("The shader type must be specified"); + } + } + + protected ShaderSource(ShaderSource ss){ + super(ss.id); + // No data needs to be copied. + // (This is a destructable clone) + } + + public ShaderSource(){ + super(); + } + + public void setName(String name){ + this.name = name; + } + + public String getName(){ + return name; + } + + public ShaderType getType() { + return sourceType; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + if (language == null) { + throw new IllegalArgumentException("Shader language cannot be null"); + } + this.language = language; + setUpdateNeeded(); + } + + public void setSource(String source){ + if (source == null) { + throw new IllegalArgumentException("Shader source cannot be null"); + } + this.source = source; + setUpdateNeeded(); + } + + public void setDefines(String defines){ + if (defines == null) { + throw new IllegalArgumentException("Shader defines cannot be null"); + } + this.defines = defines; + setUpdateNeeded(); + } + + public String getSource(){ + return source; + } + + public String getDefines(){ + return defines; + } + + @Override + public long getUniqueId() { + return ((long)OBJTYPE_SHADERSOURCE << 32) | ((long)id); + } + + @Override + public String toString(){ + String nameTxt = ""; + if (name != null) + nameTxt = "name="+name+", "; + if (defines != null) + nameTxt += "defines, "; + + + return getClass().getSimpleName() + "["+nameTxt+"type=" + + sourceType.name()+", language=" + language + "]"; + } + + public void resetObject(){ + id = -1; + setUpdateNeeded(); + } + + public void deleteObject(Object rendererObject){ + ((Renderer)rendererObject).deleteShaderSource(ShaderSource.this); + } + + public NativeObject createDestructableClone(){ + return new ShaderSource(ShaderSource.this); + } + } + + /** + * Initializes the shader for use, must be called after the + * constructor without arguments is used. + */ + public void initialize() { + shaderSourceList = new ArrayList(); + uniforms = new ListMap(); + attribs = new IntMap(); + } + + /** + * Creates a new shader, {@link #initialize() } must be called + * after this constructor for the shader to be usable. + */ + public Shader(){ + super(); + } + + /** + * Do not use this constructor. Used for destructable clones only. + */ + protected Shader(Shader s){ + super(s.id); + + // Shader sources cannot be shared, therefore they must + // be destroyed together with the parent shader. + shaderSourceList = new ArrayList(); + for (ShaderSource source : s.shaderSourceList){ + shaderSourceList.add( (ShaderSource)source.createDestructableClone() ); + } + } + + /** + * Adds source code to a certain pipeline. + * + * @param type The pipeline to control + * @param source The shader source code (in GLSL). + * @param defines Preprocessor defines (placed at the beginning of the shader) + * @param language The shader source language, currently accepted is GLSL### + * where ### is the version, e.g. GLSL100 = GLSL 1.0, GLSL330 = GLSL 3.3, etc. + */ + public void addSource(ShaderType type, String name, String source, String defines, String language){ + ShaderSource shaderSource = new ShaderSource(type); + shaderSource.setSource(source); + shaderSource.setName(name); + shaderSource.setLanguage(language); + if (defines != null) { + shaderSource.setDefines(defines); + } + shaderSourceList.add(shaderSource); + setUpdateNeeded(); + } + + public Uniform getUniform(String name){ + Uniform uniform = uniforms.get(name); + if (uniform == null){ + uniform = new Uniform(); + uniform.name = name; + uniforms.put(name, uniform); + } + return uniform; + } + + public void removeUniform(String name){ + uniforms.remove(name); + } + + public Attribute getAttribute(VertexBuffer.Type attribType){ + int ordinal = attribType.ordinal(); + Attribute attrib = attribs.get(ordinal); + if (attrib == null){ + attrib = new Attribute(); + attrib.name = attribType.name(); + attribs.put(ordinal, attrib); + } + return attrib; + } + + public ListMap getUniformMap(){ + return uniforms; + } + + public Collection getSources(){ + return shaderSourceList; + } + + @Override + public String toString() { + return getClass().getSimpleName() + + "[numSources=" + shaderSourceList.size() + + ", numUniforms=" + uniforms.size() + + ", shaderSources=" + getSources() + "]"; + } + + /** + * Removes the "set-by-current-material" flag from all uniforms. + * When a uniform is modified after this call, the flag shall + * become "set-by-current-material". + * A call to {@link #resetUniformsNotSetByCurrent() } will reset + * all uniforms that do not have the "set-by-current-material" flag + * to their default value (usually all zeroes or false). + */ + public void clearUniformsSetByCurrentFlag() { + int size = uniforms.size(); + for (int i = 0; i < size; i++) { + Uniform u = uniforms.getValue(i); + u.clearSetByCurrentMaterial(); + } + } + + /** + * Resets all uniforms that do not have the "set-by-current-material" flag + * to their default value (usually all zeroes or false). + * When a uniform is modified, that flag is set, to remove the flag, + * use {@link #clearUniformsSetByCurrent() }. + */ + public void resetUniformsNotSetByCurrent() { + int size = uniforms.size(); + for (int i = 0; i < size; i++) { + Uniform u = uniforms.getValue(i); + if (!u.isSetByCurrentMaterial()) { + u.clearValue(); + } + } + } + + /** + * Usually called when the shader itself changes or during any + * time when the variable locations need to be refreshed. + */ + public void resetLocations() { + if (uniforms != null) { + // NOTE: Shader sources will be reset seperately from the shader itself. + for (Uniform uniform : uniforms.values()) { + uniform.reset(); // fixes issue with re-initialization + } + } + if (attribs != null) { + for (Entry entry : attribs) { + entry.getValue().location = ShaderVariable.LOC_UNKNOWN; + } + } + } + + @Override + public void setUpdateNeeded(){ + super.setUpdateNeeded(); + resetLocations(); + } + + /** + * Called by the object manager to reset all object IDs. This causes + * the shader to be reuploaded to the GPU incase the display was restarted. + */ + @Override + public void resetObject() { + this.id = -1; + for (ShaderSource source : shaderSourceList){ + source.resetObject(); + } + setUpdateNeeded(); + } + + @Override + public void deleteObject(Object rendererObject) { + ((Renderer)rendererObject).deleteShader(this); + } + + public NativeObject createDestructableClone(){ + return new Shader(this); + } + + @Override + public long getUniqueId() { + return ((long)OBJTYPE_SHADER << 32) | ((long)id); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java new file mode 100644 index 000000000..e26765f77 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetManager; +import com.jme3.material.ShaderGenerationInfo; +import com.jme3.material.Technique; +import com.jme3.material.TechniqueDef; +import com.jme3.shader.Shader.ShaderType; +import java.util.List; + +/** + * This class is the base for a shader generator using the ShaderNodes system, + * it contains basis mechnaism of generation, but no actual generation code. + * This class is abstract, any Shader generator must extend it. + * + * @author Nehon + */ +public abstract class ShaderGenerator { + + //the asset manager + protected AssetManager assetManager; + //indentation value for generation + protected int indent; + //the technique to use for the shader generation + protected Technique technique = null; + + /** + * Build a shaderGenerator + * + * @param assetManager + */ + protected ShaderGenerator(AssetManager assetManager) { + this.assetManager = assetManager; + } + + public void initialize(Technique technique){ + this.technique = technique; + } + + /** + * Generate vertex and fragment shaders for the given technique + * + * @param technique the technique to use to generate the shaders + * @return a Shader program + */ + public Shader generateShader() { + if(technique == null){ + throw new UnsupportedOperationException("The shaderGenerator was not properly initialized, call initialize(Technique) before any generation"); + } + + DefineList defines = technique.getAllDefines(); + TechniqueDef def = technique.getDef(); + ShaderGenerationInfo info = def.getShaderGenerationInfo(); + + String vertexSource = buildShader(def.getShaderNodes(), info, ShaderType.Vertex); + String fragmentSource = buildShader(def.getShaderNodes(), info, ShaderType.Fragment); + + Shader shader = new Shader(); + shader.initialize(); + shader.addSource(Shader.ShaderType.Vertex, technique.getDef().getName() + ".vert", vertexSource, defines.getCompiled(), getLanguageAndVersion(ShaderType.Vertex)); + shader.addSource(Shader.ShaderType.Fragment, technique.getDef().getName() + ".frag", fragmentSource, defines.getCompiled(), getLanguageAndVersion(ShaderType.Fragment)); + + technique = null; + return shader; + } + + /** + * This method is responsible for the shader generation. + * + * @param shaderNodes the list of shader nodes + * @param info the ShaderGenerationInfo filled during the Technique loading + * @param type the type of shader to generate + * @return the code of the generated vertex shader + */ + protected String buildShader(List shaderNodes, ShaderGenerationInfo info, ShaderType type) { + indent = 0; + + StringBuilder sourceDeclaration = new StringBuilder(); + StringBuilder source = new StringBuilder(); + + generateUniforms(sourceDeclaration, info, type); + + if (type == ShaderType.Vertex) { + generateAttributes(sourceDeclaration, info); + } + generateVaryings(sourceDeclaration, info, type); + + generateStartOfMainSection(source, info, type); + + generateDeclarationAndMainBody(shaderNodes, sourceDeclaration, source, info, type); + + generateEndOfMainSection(source, info, type); + + sourceDeclaration.append(source); + + return sourceDeclaration.toString(); + } + + /** + * iterates through shader nodes to load them and generate the shader + * declaration part and main body extracted from the shader nodes, for the + * given shader type + * + * @param shaderNodes the list of shader nodes + * @param sourceDeclaration the declaration part StringBuilder of the shader + * to generate + * @param source the main part StringBuilder of the shader to generate + * @param info the ShaderGenerationInfo + * @param type the Shader type + */ + protected void generateDeclarationAndMainBody(List shaderNodes, StringBuilder sourceDeclaration, StringBuilder source, ShaderGenerationInfo info, Shader.ShaderType type) { + for (ShaderNode shaderNode : shaderNodes) { + if (info.getUnusedNodes().contains(shaderNode.getName())) { + continue; + } + if (shaderNode.getDefinition().getType() == type) { + int index = findShaderIndexFromVersion(shaderNode, type); + String loadedSource = (String) assetManager.loadAsset(new AssetKey(shaderNode.getDefinition().getShadersPath().get(index))); + appendNodeDeclarationAndMain(loadedSource, sourceDeclaration, source, shaderNode, info); + } + } + } + + /** + * Appends declaration and main part of a node to the shader declaration and + * main part. the loadedSource is split by "void main(){" to split + * declaration from main part of the node source code.The trailing "}" is + * removed from the main part. Each part is then respectively passed to + * generateDeclarativeSection and generateNodeMainSection. + * + * @see ShaderGenerator#generateDeclarativeSection + * @see ShaderGenerator#generateNodeMainSection + * + * @param loadedSource the actual source code loaded for this node. + * @param sourceDeclaration the Shader declaration part string builder. + * @param source the Shader main part StringBuilder. + * @param shaderNode the shader node. + * @param info the ShaderGenerationInfo. + */ + protected void appendNodeDeclarationAndMain(String loadedSource, StringBuilder sourceDeclaration, StringBuilder source, ShaderNode shaderNode, ShaderGenerationInfo info) { + if (loadedSource.length() > 1) { + loadedSource = loadedSource.substring(0, loadedSource.lastIndexOf("}")); + String[] sourceParts = loadedSource.split("void main\\(\\)\\{"); + generateDeclarativeSection(sourceDeclaration, shaderNode, sourceParts[0], info); + generateNodeMainSection(source, shaderNode, sourceParts[1], info); + } else { + //if source is empty, we still call generateNodeMainSection so that mappings can be done. + generateNodeMainSection(source, shaderNode, loadedSource, info); + } + + } + + /** + * returns the laguage + version of the shader should be somthing like + * "GLSL100" for glsl 1.0 "GLSL150" for glsl 1.5. + * + * @param type the shader type for wich the version should be returned. + * + * @return the shaderLanguage and version. + */ + protected abstract String getLanguageAndVersion(Shader.ShaderType type); + + /** + * generates the uniforms declaration for a shader of the given type. + * + * @param source the source StringBuilder to append generated code. + * @param info the ShaderGenerationInfo. + * @param type the shader type the uniforms have to be generated for. + */ + protected abstract void generateUniforms(StringBuilder source, ShaderGenerationInfo info, ShaderType type); + + /** + * generates the attributes declaration for the vertex shader. There is no + * Shader type passed here as attributes are only used in vertex shaders + * + * @param source the source StringBuilder to append generated code. + * @param info the ShaderGenerationInfo. + */ + protected abstract void generateAttributes(StringBuilder source, ShaderGenerationInfo info); + + /** + * generates the varyings for the given shader type shader. Note that + * varyings are deprecated in glsl 1.3, but this method will still be called + * to generate all non global inputs and output of the shaders. + * + * @param source the source StringBuilder to append generated code. + * @param info the ShaderGenerationInfo. + * @param type the shader type the varyings have to be generated for. + */ + protected abstract void generateVaryings(StringBuilder source, ShaderGenerationInfo info, ShaderType type); + + /** + * Appends the given shaderNode declarative part to the shader declarative + * part. If needed the sahder type can be determined by fetching the + * shaderNode's definition type. + * + * @see ShaderNode#getDefinition() + * @see ShaderNodeDefinition#getType() + * + * @param source the StringBuilder to append generated code. + * @param shaderNode the shaderNode. + * @param nodeSource the declaration part of the loaded shaderNode source. + * @param info the ShaderGenerationInfo. + */ + protected abstract void generateDeclarativeSection(StringBuilder source, ShaderNode shaderNode, String nodeDecalarationSource, ShaderGenerationInfo info); + + /** + * generates the start of the shader main section. this method is + * responsible of appending the "void main(){" in the shader and declaring + * all global outputs of the shader + * + * @param source the StringBuilder to append generated code. + * @param info the ShaderGenerationInfo. + * @param type the shader type the section has to be generated for. + */ + protected abstract void generateStartOfMainSection(StringBuilder source, ShaderGenerationInfo info, ShaderType type); + + /** + * generates the end of the shader main section. this method is responsible + * of appending the last "}" in the shader and mapping all global outputs of + * the shader + * + * @param source the StringBuilder to append generated code. + * @param info the ShaderGenerationInfo. + * @param type the shader type the section has to be generated for. + */ + protected abstract void generateEndOfMainSection(StringBuilder source, ShaderGenerationInfo info, ShaderType type); + + /** + * Appends the given shaderNode main part to the shader declarative part. If + * needed the sahder type can be determined by fetching the shaderNode's + * definition type. + * + * @see ShaderNode#getDefinition() + * @see ShaderNodeDefinition#getType() + * + * @param source the StringBuilder to append generated code. + * @param shaderNode the shaderNode. + * @param nodeSource the declaration part of the loaded shaderNode source. + * @param info the ShaderGenerationInfo. + */ + protected abstract void generateNodeMainSection(StringBuilder source, ShaderNode shaderNode, String nodeSource, ShaderGenerationInfo info); + + /** + * returns the shaderpath index according to the version of the generator. + * This allow to select the higher version of the shader that the generator + * can handle + * + * @param shaderNode the shaderNode being processed + * @param type the shaderType + * @return the index of the shader path in ShaderNodeDefinition shadersPath + * list + * @throws NumberFormatException + */ + protected int findShaderIndexFromVersion(ShaderNode shaderNode, ShaderType type) throws NumberFormatException { + int index = 0; + List lang = shaderNode.getDefinition().getShadersLanguage(); + int genVersion = Integer.parseInt(getLanguageAndVersion(type).substring(4)); + int curVersion = 0; + for (int i = 0; i < lang.size(); i++) { + int version = Integer.parseInt(lang.get(i).substring(4)); + if (version > curVersion && version <= genVersion) { + curVersion = version; + index = i; + } + } + return index; + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderKey.java b/jme3-core/src/main/java/com/jme3/shader/ShaderKey.java new file mode 100644 index 000000000..f6cb81508 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderKey.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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 vertLanguage; + protected String fragLanguage; + protected int cachedHashedCode = 0; + protected boolean usesShaderNodes = false; + + public ShaderKey(){ + } + + public ShaderKey(String vertName, String fragName, DefineList defines, String vertLanguage, String fragLanguage){ + super(vertName); + this.fragName = fragName; + this.defines = defines; + this.vertLanguage = vertLanguage; + this.fragLanguage = fragLanguage; + } + + @Override + public ShaderKey clone() { + ShaderKey clone = (ShaderKey) super.clone(); + clone.cachedHashedCode = 0; + clone.defines = defines.clone(); + return clone; + } + + @Override + public String toString(){ + return "V="+name + " F=" + fragName + (defines != null ? defines : ""); + } + + @Override + public boolean equals(Object obj) { + final ShaderKey other = (ShaderKey) obj; + if (name.equals(other.name) && fragName.equals(other.fragName)){ + if (defines != null && other.defines != null) { + return defines.equals(other.defines); + } else if (defines != null || other.defines != null) { + return false; + } else { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + if (cachedHashedCode == 0) { + int hash = 7; + hash = 41 * hash + name.hashCode(); + hash = 41 * hash + fragName.hashCode(); + hash = 41 * hash + (defines != null ? defines.hashCode() : 0); + cachedHashedCode = hash; + } + return cachedHashedCode; + } + + public DefineList getDefines() { + return defines; + } + + public String getVertName(){ + return name; + } + + public String getFragName() { + return fragName; + } + + /** + * @deprecated Use {@link #getVertexShaderLanguage() } instead. + */ + @Deprecated + public String getLanguage() { + return vertLanguage; + } + + public String getVertexShaderLanguage() { + return vertLanguage; + } + + public String getFragmentShaderLanguage() { + return fragLanguage; + } + + public boolean isUsesShaderNodes() { + return usesShaderNodes; + } + + public void setUsesShaderNodes(boolean usesShaderNodes) { + this.usesShaderNodes = usesShaderNodes; + } + + @Override + public void write(JmeExporter ex) throws IOException{ + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(fragName, "fragment_name", null); + oc.write(vertLanguage, "language", null); + oc.write(fragLanguage, "frag_language", null); + } + + @Override + public void read(JmeImporter im) throws IOException{ + super.read(im); + InputCapsule ic = im.getCapsule(this); + fragName = ic.readString("fragment_name", null); + vertLanguage = ic.readString("language", null); + fragLanguage = ic.readString("frag_language", null); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java b/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java new file mode 100644 index 000000000..41346104b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +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; + +/** + * A ShaderNode is the unit brick part of a shader program. A shader can be + * describe with several shader nodes that are plugged together through inputs + * and outputs. + * + * A ShaderNode is based on a definition that has a shader code, inputs and + * output variables. This node can be activated based on a condition, and has + * input and ouput mapping. + * + * This class is not intended to be used by JME users directly. It's the + * stucture for loading shader nodes from a J3md ùaterial definition file + * + * @author Nehon + */ +public class ShaderNode implements Savable { + + private String name; + private ShaderNodeDefinition definition; + private String condition; + private List inputMapping = new ArrayList(); + private List outputMapping = new ArrayList(); + + /** + * creates a ShaderNode + * + * @param name the name + * @param definition the ShaderNodeDefinition + * @param condition the conditionto activate this node + */ + public ShaderNode(String name, ShaderNodeDefinition definition, String condition) { + this.name = name; + this.definition = definition; + this.condition = condition; + } + + /** + * creates a ShaderNode + */ + public ShaderNode() { + } + + /** + * + * @return the name of the node + */ + public String getName() { + return name; + } + + /** + * sets the name of th node + * + * @param name the name + */ + public void setName(String name) { + this.name = name; + } + + /** + * returns the definition + * + * @return the ShaderNodeDefinition + */ + public ShaderNodeDefinition getDefinition() { + return definition; + } + + /** + * sets the definition + * + * @param definition the ShaderNodeDefinition + */ + public void setDefinition(ShaderNodeDefinition definition) { + this.definition = definition; + } + + /** + * + * @return the condition + */ + public String getCondition() { + return condition; + } + + /** + * sets the ocndition + * + * @param condition the condition + */ + public void setCondition(String condition) { + this.condition = condition; + } + + /** + * return a list of VariableMapping representing the input mappings of this + * node + * + * @return the input mappings + */ + public List getInputMapping() { + return inputMapping; + } + + /** + * sets the input mappings + * + * @param inputMapping the input mappings + */ + public void setInputMapping(List inputMapping) { + this.inputMapping = inputMapping; + } + + /** + * return a list of VariableMapping representing the output mappings of this + * node + * + * @return the output mappings + */ + public List getOutputMapping() { + return outputMapping; + } + + /** + * sets the output mappings + * + * @param inputMapping the output mappings + */ + public void setOutputMapping(List outputMapping) { + this.outputMapping = outputMapping; + } + + /** + * jme seralization + * + * @param ex the exporter + * @throws IOException + */ + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(name, "name", ""); + oc.write(definition, "definition", null); + oc.write(condition, "condition", null); + oc.writeSavableArrayList((ArrayList) inputMapping, "inputMapping", new ArrayList()); + oc.writeSavableArrayList((ArrayList) outputMapping, "outputMapping", new ArrayList()); + } + + /** + * jme seralization + * + * @param im the importer + * @throws IOException + */ + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = (InputCapsule) im.getCapsule(this); + name = ic.readString("name", ""); + definition = (ShaderNodeDefinition) ic.readSavable("definition", null); + condition = ic.readString("condition", null); + inputMapping = (List) ic.readSavableArrayList("inputMapping", new ArrayList()); + outputMapping = (List) ic.readSavableArrayList("outputMapping", new ArrayList()); + } + + /** + * convenience tostring + * + * @return a string + */ + @Override + public String toString() { + return "\nShaderNode{" + "\nname=" + name + ", \ndefinition=" + definition.getName() + ", \ncondition=" + condition + ", \ninputMapping=" + inputMapping + ", \noutputMapping=" + outputMapping + '}'; + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java new file mode 100644 index 000000000..c8c7089ad --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +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.shader.Shader.ShaderType; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Shader node definition structure meant for holding loaded datat from a + * material definition j3md file + * + * @author Nehon + */ +public class ShaderNodeDefinition implements Savable { + + private String name; + private Shader.ShaderType type; + private List shadersLanguage = new ArrayList(); + private List shadersPath = new ArrayList(); + private String documentation; + private List inputs = new ArrayList(); + private List outputs = new ArrayList(); + private String path = null; + private boolean noOutput = false; + + /** + * creates a ShaderNodeDefinition + * + * @param name the name of the definition + * @param type the type of the shader + * @param shaderPath the path of the shader + * @param shaderLanguage the shader language (minimum required for this + * definition) + */ + public ShaderNodeDefinition(String name, ShaderType type, String shaderPath, String shaderLanguage) { + this.name = name; + this.type = type; + shadersLanguage.add(shaderLanguage); + shadersPath.add(shaderPath); + } + + /** + * creates a ShaderNodeDefinition + */ + public ShaderNodeDefinition() { + } + + /** + * returns the name of the definition + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * sets the name of the definition + * + * @param name the name + */ + public void setName(String name) { + this.name = name; + } + + /** + * + * @return the type of shader the definition applies to + */ + public ShaderType getType() { + return type; + } + + /** + * sets the type of shader this def applies to + * + * @param type the type + */ + public void setType(ShaderType type) { + this.type = type; + } + + /** + * + * @return the docuentation for tthis definition + */ + public String getDocumentation() { + return documentation; + } + + /** + * sets the dcumentation + * + * @param documentation the documentation + */ + public void setDocumentation(String documentation) { + this.documentation = documentation; + } + + /** + * + * @return the input variables of this definition + */ + public List getInputs() { + return inputs; + } + + /** + * sets the input variables of this definition + * + * @param inputs the inputs + */ + public void setInputs(List inputs) { + this.inputs = inputs; + } + + /** + * + * @return the output variables of this definition + */ + public List getOutputs() { + return outputs; + } + + /** + * sets the output variables of this definition + * + * @param inputs the output + */ + public void setOutputs(List outputs) { + this.outputs = outputs; + } + + /** + * retrun the path of this definition + * @return + */ + public String getPath() { + return path; + } + + /** + * sets the path of this definition + * @param path + */ + public void setPath(String path) { + this.path = path; + } + + + + /** + * jme seralization (not used) + * + * @param ex the exporter + * @throws IOException + */ + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(name, "name", ""); + String[] str = new String[shadersLanguage.size()]; + oc.write(shadersLanguage.toArray(str), "shadersLanguage", null); + oc.write(shadersPath.toArray(str), "shadersPath", null); + oc.write(type, "type", null); + oc.writeSavableArrayList((ArrayList) inputs, "inputs", new ArrayList()); + oc.writeSavableArrayList((ArrayList) outputs, "inputs", new ArrayList()); + } + + public List getShadersLanguage() { + return shadersLanguage; + } + + public List getShadersPath() { + return shadersPath; + } + + public boolean isNoOutput() { + return noOutput; + } + + public void setNoOutput(boolean noOutput) { + this.noOutput = noOutput; + } + + + + /** + * jme seralization (not used) + * + * @param im the importer + * @throws IOException + */ + public void read(JmeImporter im) throws IOException { + InputCapsule ic = (InputCapsule) im.getCapsule(this); + name = ic.readString("name", ""); + + String[] str = ic.readStringArray("shadersLanguage", null); + if (str != null) { + shadersLanguage = Arrays.asList(str); + } else { + shadersLanguage = new ArrayList(); + } + + str = ic.readStringArray("shadersPath", null); + if (str != null) { + shadersPath = Arrays.asList(str); + } else { + shadersPath = new ArrayList(); + } + + type = ic.readEnum("type", Shader.ShaderType.class, null); + inputs = (List) ic.readSavableArrayList("inputs", new ArrayList()); + outputs = (List) ic.readSavableArrayList("outputs", new ArrayList()); + } + + /** + * convenience tostring + * + * @return a string + */ + @Override + public String toString() { + return "\nShaderNodeDefinition{\n" + "name=" + name + "\ntype=" + type + "\nshaderPath=" + shadersPath + "\nshaderLanguage=" + shadersLanguage + "\ndocumentation=" + documentation + "\ninputs=" + inputs + ",\noutputs=" + outputs + '}'; + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java new file mode 100644 index 000000000..81dba4f52 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +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; + +/** + * A shader node variable + * + * @author Nehon + */ +public class ShaderNodeVariable implements Savable, Cloneable { + + private String name; + private String type; + private String nameSpace; + private String condition; + private boolean shaderOutput = false; + private String multiplicity; + + /** + * creates a ShaderNodeVariable + * + * @param type the glsl type of the variable + * @param name the name of the variable + */ + public ShaderNodeVariable(String type, String name) { + this.name = name; + this.type = type; + } + + + /** + * creates a ShaderNodeVariable + * + * @param type the glsl type of the variable + * @param nameSpace the nameSpace (can be the name of the shaderNode or + * Globel,Attr,MatParam,WorldParam) + * @param name the name of the variable + * @param multiplicity the number of element if this variable is an array. Can be an Int of a declared material parameter + */ + public ShaderNodeVariable(String type, String nameSpace, String name, String multiplicity) { + this.name = name; + this.nameSpace = nameSpace; + this.type = type; + this.multiplicity = multiplicity; + } + + /** + * creates a ShaderNodeVariable + * + * @param type the glsl type of the variable + * @param nameSpace the nameSpace (can be the name of the shaderNode or + * Globel,Attr,MatParam,WorldParam) + * @param name the name of the variable + */ + public ShaderNodeVariable(String type, String nameSpace, String name) { + this.name = name; + this.nameSpace = nameSpace; + this.type = type; + } + + /** + * returns the name + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * sets the name + * + * @param name the name + */ + public void setName(String name) { + this.name = name; + } + + /** + * + * @return the glsl type + */ + public String getType() { + return type; + } + + /** + * sets the glsl type + * + * @param type the type + */ + public void setType(String type) { + this.type = type; + } + + /** + * + * @return the name space (can be the name of the shaderNode or + * Globel,Attr,MatParam,WorldParam) + */ + public String getNameSpace() { + return nameSpace; + } + + /** + * sets the nameSpace (can be the name of the shaderNode or + * Globel,Attr,MatParam,WorldParam) + * + * @param nameSpace + */ + public void setNameSpace(String nameSpace) { + this.nameSpace = nameSpace; + } + + @Override + public int hashCode() { + int hash = 5; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ShaderNodeVariable other = (ShaderNodeVariable) obj; + if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { + return false; + } + if ((this.type == null) ? (other.type != null) : !this.type.equals(other.type)) { + return false; + } + if ((this.nameSpace == null) ? (other.nameSpace != null) : !this.nameSpace.equals(other.nameSpace)) { + return false; + } + return true; + } + + /** + * jme seralization (not used) + * + * @param ex the exporter + * @throws IOException + */ + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(name, "name", ""); + oc.write(type, "type", ""); + oc.write(nameSpace, "nameSpace", ""); + oc.write(condition, "condition", null); + oc.write(shaderOutput, "shaderOutput", false); + oc.write(multiplicity, "multiplicity", null); + + } + + /** + * jme seralization (not used) + * + * @param im the importer + * @throws IOException + */ + public void read(JmeImporter im) throws IOException { + InputCapsule ic = (InputCapsule) im.getCapsule(this); + name = ic.readString("name", ""); + type = ic.readString("type", ""); + nameSpace = ic.readString("nameSpace", ""); + condition = ic.readString("condition", null); + shaderOutput = ic.readBoolean("shaderOutput", false); + multiplicity = ic.readString("multiplicity", null); + } + + /** + * + * @return the condition for this variable to be declared + */ + public String getCondition() { + return condition; + } + + /** + * sets the condition + * + * @param condition the condition + */ + public void setCondition(String condition) { + this.condition = condition; + } + + @Override + public String toString() { + return "\n" + type + ' ' + (nameSpace != null ? (nameSpace + '.') : "") + name; + } + + @Override + public ShaderNodeVariable clone() { + return new ShaderNodeVariable(type, nameSpace, name); + } + + /** + * + * @return true if this variable is a shader output + */ + public boolean isShaderOutput() { + return shaderOutput; + } + + /** + * sets to true if this variable is a shader output + * + * @param shaderOutput true if this variable is a shader output + */ + public void setShaderOutput(boolean shaderOutput) { + this.shaderOutput = shaderOutput; + } + + /** + * + * @return the number of elements if this variable is an array + */ + public String getMultiplicity() { + return multiplicity; + } + + /** + * sets the number of elements of this variable making it an array + * this value can be a number of can be a define + * @param multiplicity + */ + public void setMultiplicity(String multiplicity) { + this.multiplicity = multiplicity; + } + + +} diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java b/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java new file mode 100644 index 000000000..5b264f10f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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(); + } + + /** + * Check if a mapping is valid by checking the types and swizzle of both of + * the variables + * + * @param mapping the mapping + * @return true if this mapping is valid + */ + public static boolean typesMatch(VariableMapping mapping) { + String leftType = mapping.getLeftVariable().getType(); + String rightType = mapping.getRightVariable().getType(); + String leftSwizzling = mapping.getLeftSwizzling(); + String rightSwizzling = mapping.getRightSwizzling(); + + //types match : no error + if (leftType.equals(rightType) && leftSwizzling.length() == rightSwizzling.length()) { + return true; + } + if (isSwizzlable(leftType) && isSwizzlable(rightType)) { + if (getCardinality(leftType, leftSwizzling) == getCardinality(rightType, rightSwizzling)) { + return true; + } + } + + return false; + } + + /** + * Check if a mapping is valid by checking the multiplicity of both of + * the variables if they are arrays + * + * @param mapping the mapping + * @return true if this mapping is valid + */ + public static boolean multiplicityMatch(VariableMapping mapping) { + String leftMult = mapping.getLeftVariable().getMultiplicity(); + String rightMult = mapping.getRightVariable().getMultiplicity(); + + if(leftMult == null){ + if(rightMult != null){ + return false; + } + }else{ + if(rightMult == null){ + return false; + }else{ + if(!leftMult.equalsIgnoreCase(rightMult)){ + return false; + } + } + } + return true; + } + + /** + * return the cardinality of a type and a swizzle example : vec4 cardinality + * is 4 float cardinality is 1 vec4.xyz cardinality is 3. sampler2D + * cardinality is 0 + * + * @param type the glsl type + * @param swizzling the swizzling of a variable + * @return the cardinality + */ + public static int getCardinality(String type, String swizzling) { + int card = 0; + if (isSwizzlable(type)) { + if (type.equals("float")) { + card = 1; + if (swizzling.length() != 0) { + card = 0; + } + } else { + card = Integer.parseInt(type.replaceAll("vec", "")); + + if (swizzling.length() > 0) { + if (card >= swizzling.length()) { + card = swizzling.length(); + } else { + card = 0; + } + } + } + } + return card; + } + + /** + * returns true if a variable of the given type can have a swizzle + * + * @param type the glsl type + * @return true if a variable of the given type can have a swizzle + */ + public static boolean isSwizzlable(String type) { + return type.equals("vec4") || type.equals("vec3") || type.equals("vec2") || type.equals("float"); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderVariable.java b/jme3-core/src/main/java/com/jme3/shader/ShaderVariable.java new file mode 100644 index 000000000..76b39de54 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderVariable.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +public class ShaderVariable { + + public static final int LOC_UNKNOWN = -2, + LOC_NOT_DEFINED = -1; + + // if -2, location not known + // if -1, not defined in shader + // if >= 0, uniform defined and available. + protected int location = LOC_UNKNOWN; + + /** + * 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 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/jme3-core/src/main/java/com/jme3/shader/Uniform.java b/jme3-core/src/main/java/com/jme3/shader/Uniform.java new file mode 100644 index 000000000..5d5fd2fe3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/Uniform.java @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +import com.jme3.math.*; +import com.jme3.util.BufferUtils; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +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; + + /** + * For arrays or matrices, efficient format + * that can be sent to GL faster. + */ + protected FloatBuffer multiData = null; + + /** + * Type of uniform + */ + protected VarType varType; + + /** + * Binding to a renderer value, or null if user-defined uniform + */ + protected UniformBinding binding; + + /** + * Used to track which uniforms to clear to avoid + * values leaking from other materials that use that shader. + */ + protected boolean setByCurrentMaterial = false; + + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + 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; + } + + private static void setVector4(Vector4f vec, Object value) { + if (value instanceof ColorRGBA) { + ColorRGBA color = (ColorRGBA) value; + vec.set(color.r, color.g, color.b, color.a); + } else if (value instanceof Quaternion) { + Quaternion quat = (Quaternion) value; + vec.set(quat.getX(), quat.getY(), quat.getZ(), quat.getW()); + } else if (value instanceof Vector4f) { + Vector4f vec4 = (Vector4f) value; + vec.set(vec4); + } else{ + throw new IllegalArgumentException(); + } + } + + public void clearValue(){ + updateNeeded = true; + + if (multiData != null){ + multiData.clear(); + + while (multiData.remaining() > 0){ + ZERO_BUF.clear(); + 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: + this.value = Vector4f.ZERO; + break; + default: + // won't happen because those are either textures + // or multidata types + } + } + + public void setValue(VarType type, Object value){ + if (location == LOC_NOT_DEFINED) { + 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 IntArray: + int[] ia = (int[]) value; + if (this.value == null) { + this.value = BufferUtils.createIntBuffer(ia); + } else { + this.value = BufferUtils.ensureLargeEnough((IntBuffer)this.value, ia.length); + } + ((IntBuffer)this.value).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: + Vector4f[] v4a = (Vector4f[]) 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 * 4) { + 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; + } + + public void deleteNativeBuffers() { + if (value instanceof Buffer) { + BufferUtils.destroyDirectBuffer((Buffer)value); + value = null; // ???? + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/UniformBinding.java b/jme3-core/src/main/java/com/jme3/shader/UniformBinding.java new file mode 100644 index 000000000..3ac5cc0e0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/UniformBinding.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +public enum UniformBinding { + + /** + * The world matrix. Converts Model space to World space. + * Type: mat4 + */ + WorldMatrix("mat4"), + + /** + * The view matrix. Converts World space to View space. + * Type: mat4 + */ + ViewMatrix("mat4"), + + /** + * The projection matrix. Converts View space to Clip/Projection space. + * Type: mat4 + */ + ProjectionMatrix("mat4"), + + /** + * The world view matrix. Converts Model space to View space. + * Type: mat4 + */ + WorldViewMatrix("mat4"), + + /** + * The normal matrix. The inverse transpose of the worldview matrix. + * Converts normals from model space to view space. + * Type: mat3 + */ + NormalMatrix("mat3"), + + /** + * The world view projection matrix. Converts Model space to Clip/Projection + * space. + * Type: mat4 + */ + WorldViewProjectionMatrix("mat4"), + + /** + * The view projection matrix. Converts World space to Clip/Projection + * space. + * Type: mat4 + */ + ViewProjectionMatrix("mat4"), + + /** + * The world matrix inverse transpose. Converts a normals from Model space + * to world space. + * Type: mat3 + */ + WorldMatrixInverseTranspose("mat3"), + + + + WorldMatrixInverse("mat4"), + ViewMatrixInverse("mat4"), + ProjectionMatrixInverse("mat4"), + ViewProjectionMatrixInverse("mat4"), + WorldViewMatrixInverse("mat4"), + NormalMatrixInverse("mat3"), + WorldViewProjectionMatrixInverse("mat4"), + + /** + * Contains the four viewport parameters in this order: + * X = Left, + * Y = Top, + * Z = Right, + * W = Bottom. + * Type: vec4 + */ + ViewPort("vec4"), + + /** + * The near and far values for the camera frustum. + * X = Near + * Y = Far. + * Type: vec2 + */ + FrustumNearFar("vec2"), + + /** + * The width and height of the camera. + * Type: vec2 + */ + Resolution("vec2"), + + /** + * The inverse of the resolution, 1/width and 1/height. + * Type: vec2 + */ + ResolutionInverse("vec2"), + + /** + * Aspect ratio of the resolution currently set. Width/Height. + * Type: float + */ + Aspect("float"), + + /** + * Camera position in world space. + * Type: vec3 + */ + CameraPosition("vec3"), + + /** + * Direction of the camera. + * Type: vec3 + */ + CameraDirection("vec3"), + + /** + * Left vector of the camera. + * Type: vec3 + */ + CameraLeft("vec3"), + + /** + * Up vector of the camera. + * Type: vec3 + */ + CameraUp("vec3"), + + /** + * Time in seconds since the application was started. + * Type: float + */ + Time("float"), + + /** + * Time in seconds that the last frame took. + * Type: float + */ + Tpf("float"), + + /** + * Frames per second. + * Type: float + */ + FrameRate("float"), + + /** + * The light position when rendering in multi pass mode + * Type: vec4 + */ + LightDirection("vec4"), + + /** + * The light direction when rendering in multi pass mode + * Type: vec4 + */ + LightPosition("vec4"), + + /** + * Ambient light color + * Type: vec4 + */ + AmbientLightColor("vec4"), + + /** + * The light color when rendering in multi pass mode + * Type: vec4 + */ + LightColor("vec4"); + + String glslType; + + private UniformBinding() { + } + + private UniformBinding(String glslType) { + this.glslType = glslType; + } + + public String getGlslType() { + return glslType; + } + + +} diff --git a/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java b/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java new file mode 100644 index 000000000..9bcd24448 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.system.Timer; +import java.util.List; + +/** + * UniformBindingManager helps {@link RenderManager} to manage + * {@link UniformBinding uniform bindings}. + * + * The {@link #updateUniformBindings(java.util.List) } will update + * a given list of uniforms based on the current state + * of the manager. + * + * @author Kirill Vainer + */ +public class UniformBindingManager { + + private Timer timer; + private float near, far; + private int viewX, viewY, viewWidth, viewHeight; + private Vector3f camUp = new Vector3f(), + camLeft = new Vector3f(), + camDir = new Vector3f(), + camLoc = new Vector3f(); + private Matrix4f tempMatrix = new Matrix4f(); + private Matrix4f viewMatrix = new Matrix4f(); + private Matrix4f projMatrix = new Matrix4f(); + private Matrix4f viewProjMatrix = new Matrix4f(); + private Matrix4f worldMatrix = new Matrix4f(); + private Matrix4f worldViewMatrix = new Matrix4f(); + private Matrix4f worldViewProjMatrix = new Matrix4f(); + private Matrix3f normalMatrix = new Matrix3f(); + private Matrix4f worldMatrixInv = new Matrix4f(); + private Matrix3f worldMatrixInvTrsp = new Matrix3f(); + private Matrix4f viewMatrixInv = new Matrix4f(); + private Matrix4f projMatrixInv = new Matrix4f(); + private Matrix4f viewProjMatrixInv = new Matrix4f(); + private Matrix4f worldViewMatrixInv = new Matrix4f(); + private Matrix3f normalMatrixInv = new Matrix3f(); + private Matrix4f worldViewProjMatrixInv = new Matrix4f(); + private Vector4f viewPort = new Vector4f(); + private Vector2f resolution = new Vector2f(); + private Vector2f resolutionInv = new Vector2f(); + private Vector2f nearFar = new Vector2f(); + + /** + * Internal use only. + * Updates the given list of uniforms with {@link UniformBinding uniform bindings} + * based on the current world state. + */ + public void updateUniformBindings(List params) { + 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: + worldViewMatrix.set(viewMatrix); + worldViewMatrix.multLocal(worldMatrix); + u.setValue(VarType.Matrix4, worldViewMatrix); + break; + case NormalMatrix: + tempMatrix.set(viewMatrix); + tempMatrix.multLocal(worldMatrix); + tempMatrix.toRotationMatrix(normalMatrix); + normalMatrix.invertLocal(); + normalMatrix.transposeLocal(); + u.setValue(VarType.Matrix3, normalMatrix); + break; + case WorldViewProjectionMatrix: + worldViewProjMatrix.set(viewProjMatrix); + worldViewProjMatrix.multLocal(worldMatrix); + u.setValue(VarType.Matrix4, worldViewProjMatrix); + break; + case WorldMatrixInverse: + worldMatrixInv.set(worldMatrix); + worldMatrixInv.invertLocal(); + u.setValue(VarType.Matrix4, worldMatrixInv); + break; + case WorldMatrixInverseTranspose: + worldMatrix.toRotationMatrix(worldMatrixInvTrsp); + worldMatrixInvTrsp.invertLocal().transposeLocal(); + u.setValue(VarType.Matrix3, worldMatrixInvTrsp); + break; + case ViewMatrixInverse: + viewMatrixInv.set(viewMatrix); + viewMatrixInv.invertLocal(); + u.setValue(VarType.Matrix4, viewMatrixInv); + break; + case ProjectionMatrixInverse: + projMatrixInv.set(projMatrix); + projMatrixInv.invertLocal(); + u.setValue(VarType.Matrix4, projMatrixInv); + break; + case ViewProjectionMatrixInverse: + viewProjMatrixInv.set(viewProjMatrix); + viewProjMatrixInv.invertLocal(); + u.setValue(VarType.Matrix4, viewProjMatrixInv); + break; + case WorldViewMatrixInverse: + worldViewMatrixInv.set(viewMatrix); + worldViewMatrixInv.multLocal(worldMatrix); + worldViewMatrixInv.invertLocal(); + u.setValue(VarType.Matrix4, worldViewMatrixInv); + break; + case NormalMatrixInverse: + tempMatrix.set(viewMatrix); + tempMatrix.multLocal(worldMatrix); + tempMatrix.toRotationMatrix(normalMatrixInv); + normalMatrixInv.invertLocal(); + normalMatrixInv.transposeLocal(); + normalMatrixInv.invertLocal(); + u.setValue(VarType.Matrix3, normalMatrixInv); + break; + case WorldViewProjectionMatrixInverse: + worldViewProjMatrixInv.set(viewProjMatrix); + worldViewProjMatrixInv.multLocal(worldMatrix); + worldViewProjMatrixInv.invertLocal(); + u.setValue(VarType.Matrix4, worldViewProjMatrixInv); + break; + case ViewPort: + viewPort.set(viewX, viewY, viewWidth, viewHeight); + u.setValue(VarType.Vector4, viewPort); + break; + case Resolution: + resolution.set(viewWidth, viewHeight); + u.setValue(VarType.Vector2, resolution); + break; + case ResolutionInverse: + resolutionInv.set(1f / viewWidth, 1f / viewHeight); + u.setValue(VarType.Vector2, resolutionInv); + break; + case Aspect: + float aspect = ((float) viewWidth) / viewHeight; + u.setValue(VarType.Float, aspect); + break; + case FrustumNearFar: + nearFar.set(near, far); + u.setValue(VarType.Vector2, nearFar); + 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; + } + } + } + + /** + * Internal use only. Sets the world matrix to use for future + * rendering. This has no effect unless objects are rendered manually + * using {@link Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) }. + * Using {@link #renderGeometry(com.jme3.scene.Geometry) } will + * override this value. + * + * @param mat The world matrix to set + */ + public void setWorldMatrix(Matrix4f mat) { + worldMatrix.set(mat); + } + + /** + * Set the timer that should be used to query the time based + * {@link UniformBinding}s for material world parameters. + * + * @param timer The timer to query time world parameters + */ + public void setTimer(com.jme3.system.Timer timer) { + this.timer = timer; + } + + public void setCamera(Camera cam, Matrix4f viewMatrix, Matrix4f projMatrix, Matrix4f viewProjMatrix) { + this.viewMatrix.set(viewMatrix); + this.projMatrix.set(projMatrix); + this.viewProjMatrix.set(viewProjMatrix); + + camLoc.set(cam.getLocation()); + cam.getLeft(camLeft); + cam.getUp(camUp); + cam.getDirection(camDir); + + near = cam.getFrustumNear(); + far = cam.getFrustumFar(); + } + + public void setViewPort(int viewX, int viewY, int viewWidth, int viewHeight) { + this.viewX = viewX; + this.viewY = viewY; + this.viewWidth = viewWidth; + this.viewHeight = viewHeight; + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/VarType.java b/jme3-core/src/main/java/com/jme3/shader/VarType.java new file mode 100644 index 000000000..b494daf57 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/VarType.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +public enum VarType { + + Float("float"), + Vector2("vec2"), + Vector3("vec3"), + Vector4("vec4"), + + IntArray(true,false,"int"), + FloatArray(true,false,"float"), + Vector2Array(true,false,"vec2"), + Vector3Array(true,false,"vec3"), + Vector4Array(true,false,"vec4"), + + Boolean("bool"), + + Matrix3(true,false,"mat3"), + Matrix4(true,false,"mat4"), + + Matrix3Array(true,false,"mat3"), + Matrix4Array(true,false,"mat4"), + + TextureBuffer(false,true,"sampler1D|sampler1DShadow"), + Texture2D(false,true,"sampler2D|sampler2DShadow"), + Texture3D(false,true,"sampler3D"), + TextureArray(false,true,"sampler2DArray"), + TextureCubeMap(false,true,"samplerCube"), + Int("int"); + + private boolean usesMultiData = false; + private boolean textureType = false; + private String glslType; + + + VarType(String glslType){ + this.glslType = glslType; + } + + VarType(boolean multiData, boolean textureType,String glslType){ + usesMultiData = multiData; + this.textureType = textureType; + this.glslType = glslType; + } + + public boolean isTextureType() { + return textureType; + } + + public boolean usesMultiData() { + return usesMultiData; + } + + public String getGlslType() { + return glslType; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/shader/VariableMapping.java b/jme3-core/src/main/java/com/jme3/shader/VariableMapping.java new file mode 100644 index 000000000..032411e2d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/VariableMapping.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +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; + +/** + * represents a mapping between 2 ShaderNodeVariables + * + * @author Nehon + */ +public class VariableMapping implements Savable { + + private ShaderNodeVariable leftVariable; + private ShaderNodeVariable rightVariable; + private String condition; + private String leftSwizzling = ""; + private String rightSwizzling = ""; + + /** + * creates a VariableMapping + */ + public VariableMapping() { + } + + /** + * creates a VariableMapping + * + * @param leftVariable the left hand side variable of the expression + * @param leftSwizzling the swizzling of the left variable + * @param rightVariable the right hand side variable of the expression + * @param rightSwizzling the swizzling of the right variable + * @param condition the condition for this mapping + */ + public VariableMapping(ShaderNodeVariable leftVariable, String leftSwizzling, ShaderNodeVariable rightVariable, String rightSwizzling, String condition) { + this.leftVariable = leftVariable; + this.rightVariable = rightVariable; + this.condition = condition; + this.leftSwizzling = leftSwizzling; + this.rightSwizzling = rightSwizzling; + } + + /** + * + * @return the left variable + */ + public ShaderNodeVariable getLeftVariable() { + return leftVariable; + } + + /** + * sets the left variable + * + * @param leftVariable the left variable + */ + public void setLeftVariable(ShaderNodeVariable leftVariable) { + this.leftVariable = leftVariable; + } + + /** + * + * @return the right variable + */ + public ShaderNodeVariable getRightVariable() { + return rightVariable; + } + + /** + * sets the right variable + * + * @param leftVariable the right variable + */ + public void setRightVariable(ShaderNodeVariable rightVariable) { + this.rightVariable = rightVariable; + } + + /** + * + * @return the condition + */ + public String getCondition() { + return condition; + } + + /** + * sets the condition + * + * @param condition the condition + */ + public void setCondition(String condition) { + this.condition = condition; + } + + /** + * + * @return the left swizzle + */ + public String getLeftSwizzling() { + return leftSwizzling; + } + + /** + * sets the left swizzle + * + * @param leftSwizzling the left swizzle + */ + public void setLeftSwizzling(String leftSwizzling) { + this.leftSwizzling = leftSwizzling; + } + + /** + * + * @return the right swizzle + */ + public String getRightSwizzling() { + return rightSwizzling; + } + + /** + * sets the right swizzle + * + * @param leftSwizzling the right swizzle + */ + public void setRightSwizzling(String rightSwizzling) { + this.rightSwizzling = rightSwizzling; + } + + /** + * jme seralization (not used) + * + * @param ex the exporter + * @throws IOException + */ + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(leftVariable, "leftVariable", null); + oc.write(rightVariable, "rightVariable", null); + oc.write(condition, "condition", ""); + oc.write(leftSwizzling, "leftSwizzling", ""); + oc.write(rightSwizzling, "rightSwizzling", ""); + } + + /** + * jme seralization (not used) + * + * @param im the importer + * @throws IOException + */ + public void read(JmeImporter im) throws IOException { + InputCapsule ic = (InputCapsule) im.getCapsule(this); + leftVariable = (ShaderNodeVariable) ic.readSavable("leftVariable", null); + rightVariable = (ShaderNodeVariable) ic.readSavable("rightVariable", null); + condition = ic.readString("condition", ""); + leftSwizzling = ic.readString("leftSwizzling", ""); + rightSwizzling = ic.readString("rightSwizzling", ""); + } + + @Override + public String toString() { + return "\n{" + leftVariable.toString() + (leftSwizzling.length() > 0 ? ("." + leftSwizzling) : "") + " = " + rightVariable.getType() + " " + rightVariable.getNameSpace() + "." + rightVariable.getName() + (rightSwizzling.length() > 0 ? ("." + rightSwizzling) : "") + " : " + condition + "}"; + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java new file mode 100644 index 000000000..ce7e35bc5 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +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.Matrix4f; +import com.jme3.math.Vector4f; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; +import java.io.IOException; + +/** + * + * Generic abstract filter that holds common implementations for the different + * shadow filtesr + * + * @author Rémy Bouquet aka Nehon + */ +public abstract class AbstractShadowFilter extends Filter { + + protected T shadowRenderer; + protected ViewPort viewPort; + + /** + * Abstract class constructor + * + * @param manager the application asset manager + * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, + * etc...) + * @param nbShadowMaps the number of shadow maps rendered (the more shadow + * maps the more quality, the less fps). + * @param shadowRenderer the shadowRenderer to use for this Filter + */ + @SuppressWarnings("all") + protected AbstractShadowFilter(AssetManager manager, int shadowMapSize, T shadowRenderer) { + super("Post Shadow"); + material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md"); + this.shadowRenderer = shadowRenderer; + this.shadowRenderer.setPostShadowMaterial(material); + } + + @Override + protected Material getMaterial() { + return material; + } + + @Override + protected boolean isRequiresDepthTexture() { + return true; + } + + public Material getShadowMaterial() { + return material; + } + Vector4f tmpv = new Vector4f(); + + @Override + protected void preFrame(float tpf) { + shadowRenderer.preFrame(tpf); + material.setMatrix4("ViewProjectionMatrixInverse", viewPort.getCamera().getViewProjectionMatrix().invert()); + Matrix4f m = viewPort.getCamera().getViewProjectionMatrix(); + material.setVector4("ViewProjectionMatrixRow2", tmpv.set(m.m20, m.m21, m.m22, m.m23)); + + } + + @Override + protected void postQueue(RenderQueue queue) { + shadowRenderer.postQueue(queue); + if(shadowRenderer.skipPostPass){ + //removing the shadow map so that the post pass is skipped + material.setTexture("ShadowMap0", null); + } + } + + @Override + protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) { + if(!shadowRenderer.skipPostPass){ + shadowRenderer.setPostShadowParams(); + } + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + shadowRenderer.needsfallBackMaterial = true; + shadowRenderer.initialize(renderManager, vp); + this.viewPort = vp; + } + + /** + * returns the shdaow intensity + * + * @see #setShadowIntensity(float shadowIntensity) + * @return shadowIntensity + */ + public float getShadowIntensity() { + return shadowRenderer.getShadowIntensity(); + } + + /** + * 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 + */ + final public void setShadowIntensity(float shadowIntensity) { + shadowRenderer.setShadowIntensity(shadowIntensity); + } + + /** + * returns the edges thickness
      + * + * @see #setEdgesThickness(int edgesThickness) + * @return edgesThickness + */ + public int getEdgesThickness() { + return shadowRenderer.getEdgesThickness(); + } + + /** + * Sets the shadow edges thickness. default is 1, setting it to lower values + * can help to reduce the jagged effect of the shadow edges + * + * @param edgesThickness + */ + public void setEdgesThickness(int edgesThickness) { + shadowRenderer.setEdgesThickness(edgesThickness); + } + + /** + * returns true if the PssmRenderer flushed the shadow queues + * + * @return flushQueues + */ + public boolean isFlushQueues() { + return shadowRenderer.isFlushQueues(); + } + + /** + * Set this to false if you want to use several PssmRederers to have + * multiple shadows cast by multiple light sources. Make sure the last + * PssmRenderer in the stack DO flush the queues, but not the others + * + * @param flushQueues + */ + public void setFlushQueues(boolean flushQueues) { + shadowRenderer.setFlushQueues(flushQueues); + } + + /** + * sets the shadow compare mode see {@link CompareMode} for more info + * + * @param compareMode + */ + final public void setShadowCompareMode(CompareMode compareMode) { + shadowRenderer.setShadowCompareMode(compareMode); + } + + /** + * returns the shadow compare mode + * + * @see CompareMode + * @return the shadowCompareMode + */ + public CompareMode getShadowCompareMode() { + return shadowRenderer.getShadowCompareMode(); + } + + /** + * Sets the filtering mode for shadow edges see {@link EdgeFilteringMode} + * for more info + * + * @param filterMode + */ + final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) { + shadowRenderer.setEdgeFilteringMode(filterMode); + } + + /** + * returns the the edge filtering mode + * + * @see EdgeFilteringMode + * @return + */ + public EdgeFilteringMode getEdgeFilteringMode() { + return shadowRenderer.getEdgeFilteringMode(); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java new file mode 100644 index 000000000..094291a95 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java @@ -0,0 +1,634 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +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.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.Camera; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.GeometryList; +import com.jme3.renderer.queue.OpaqueComparator; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.WireFrustum; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.Texture.ShadowCompareMode; +import com.jme3.texture.Texture2D; +import com.jme3.ui.Picture; + +/** + * abstract shadow renderer that holds commons feature to have for a shadow + * renderer + * + * @author Rémy Bouquet aka Nehon + */ +public abstract class AbstractShadowRenderer implements SceneProcessor, Savable { + + protected int nbShadowMaps = 1; + protected float shadowMapSize; + protected float shadowIntensity = 0.7f; + protected RenderManager renderManager; + protected ViewPort viewPort; + protected FrameBuffer[] shadowFB; + protected Texture2D[] shadowMaps; + protected Texture2D dummyTex; + protected Material preshadowMat; + protected Material postshadowMat; + protected Matrix4f[] lightViewProjectionsMatrices; + protected AssetManager assetManager; + protected boolean debug = false; + protected float edgesThickness = 1.0f; + protected EdgeFilteringMode edgeFilteringMode = EdgeFilteringMode.Bilinear; + protected CompareMode shadowCompareMode = CompareMode.Hardware; + protected Picture[] dispPic; + protected boolean flushQueues = true; + // define if the fallback material should be used. + protected boolean needsfallBackMaterial = false; + //Name of the post material technique + protected String postTechniqueName = "PostShadow"; + //flags to know when to change params in the materials + //a list of material of the post shadow queue geometries. + protected List matCache = new ArrayList(); + protected GeometryList sceneReceivers; + protected GeometryList lightReceivers = new GeometryList(new OpaqueComparator()); + protected GeometryList shadowMapOccluders = new GeometryList(new OpaqueComparator()); + private String[] shadowMapStringCache; + private String[] lightViewStringCache; + //used to skip the post pass when there are no shadow casters. + protected boolean skipPostPass; + + + /** + * used for serialization + */ + protected AbstractShadowRenderer(){ + } + + /** + * Create an abstract shadow renderer, this is to be called in extending + * classes + * + * @param assetManager the application asset manager + * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, + * etc...) + * @param nbShadowMaps the number of shadow maps rendered (the more shadow + * maps the more quality, the less fps). + */ + protected AbstractShadowRenderer(AssetManager assetManager, int shadowMapSize, int nbShadowMaps) { + + this.assetManager = assetManager; + this.nbShadowMaps = nbShadowMaps; + this.shadowMapSize = shadowMapSize; + init(assetManager, nbShadowMaps, shadowMapSize); + + } + + private void init(AssetManager assetManager, int nbShadowMaps, int shadowMapSize) { + this.postshadowMat = new Material(assetManager, "Common/MatDefs/Shadow/PostShadow.j3md"); + shadowFB = new FrameBuffer[nbShadowMaps]; + shadowMaps = new Texture2D[nbShadowMaps]; + dispPic = new Picture[nbShadowMaps]; + lightViewProjectionsMatrices = new Matrix4f[nbShadowMaps]; + shadowMapStringCache = new String[nbShadowMaps]; + lightViewStringCache = new String[nbShadowMaps]; + + //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) + dummyTex = new Texture2D(shadowMapSize, shadowMapSize, Format.RGBA8); + + preshadowMat = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md"); + postshadowMat.setFloat("ShadowMapSize", shadowMapSize); + + for (int i = 0; i < nbShadowMaps; i++) { + lightViewProjectionsMatrices[i] = new Matrix4f(); + shadowFB[i] = new FrameBuffer(shadowMapSize, shadowMapSize, 1); + shadowMaps[i] = new Texture2D(shadowMapSize, shadowMapSize, Format.Depth); + + shadowFB[i].setDepthTexture(shadowMaps[i]); + + //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) + shadowFB[i].setColorTexture(dummyTex); + shadowMapStringCache[i] = "ShadowMap" + i; + lightViewStringCache[i] = "LightViewProjectionMatrix" + i; + + postshadowMat.setTexture(shadowMapStringCache[i], shadowMaps[i]); + + //quads for debuging purpose + dispPic[i] = new Picture("Picture" + i); + dispPic[i].setTexture(assetManager, shadowMaps[i], false); + } + + setShadowCompareMode(shadowCompareMode); + setEdgeFilteringMode(edgeFilteringMode); + setShadowIntensity(shadowIntensity); + } + + /** + * set the post shadow material for this renderer + * + * @param postShadowMat + */ + protected final void setPostShadowMaterial(Material postShadowMat) { + this.postshadowMat = postShadowMat; + postshadowMat.setFloat("ShadowMapSize", shadowMapSize); + for (int i = 0; i < nbShadowMaps; i++) { + postshadowMat.setTexture(shadowMapStringCache[i], shadowMaps[i]); + } + setShadowCompareMode(shadowCompareMode); + setEdgeFilteringMode(edgeFilteringMode); + setShadowIntensity(shadowIntensity); + } + + /** + * Sets the filtering mode for shadow edges see {@link EdgeFilteringMode} + * for more info + * + * @param EdgeFilteringMode + */ + final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) { + if (filterMode == null) { + throw new NullPointerException(); + } + + this.edgeFilteringMode = filterMode; + postshadowMat.setInt("FilterMode", filterMode.getMaterialParamValue()); + postshadowMat.setFloat("PCFEdge", edgesThickness); + if (shadowCompareMode == CompareMode.Hardware) { + for (Texture2D shadowMap : shadowMaps) { + if (filterMode == EdgeFilteringMode.Bilinear) { + shadowMap.setMagFilter(MagFilter.Bilinear); + shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps); + } else { + shadowMap.setMagFilter(MagFilter.Nearest); + shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); + } + } + } + } + + /** + * returns the the edge filtering mode + * + * @see EdgeFilteringMode + * @return + */ + public EdgeFilteringMode getEdgeFilteringMode() { + return edgeFilteringMode; + } + + /** + * sets the shadow compare mode see {@link CompareMode} for more info + * + * @param compareMode + */ + final public void setShadowCompareMode(CompareMode compareMode) { + if (compareMode == null) { + throw new IllegalArgumentException("Shadow compare mode cannot be null"); + } + + this.shadowCompareMode = compareMode; + for (Texture2D shadowMap : shadowMaps) { + if (compareMode == CompareMode.Hardware) { + shadowMap.setShadowCompareMode(ShadowCompareMode.LessOrEqual); + if (edgeFilteringMode == EdgeFilteringMode.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); + } + + /** + * returns the shadow compare mode + * + * @see CompareMode + * @return the shadowCompareMode + */ + public CompareMode getShadowCompareMode() { + return shadowCompareMode; + } + + //debug function that create a displayable frustrum + protected Geometry createFrustum(Vector3f[] pts, int i) { + WireFrustum frustum = new WireFrustum(pts); + Geometry frustumMdl = new Geometry("f", frustum); + frustumMdl.setCullHint(Spatial.CullHint.Never); + frustumMdl.setShadowMode(ShadowMode.Off); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + frustumMdl.setMaterial(mat); + switch (i) { + case 0: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Pink); + break; + case 1: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red); + break; + case 2: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Green); + break; + case 3: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Blue); + break; + default: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.White); + break; + } + + frustumMdl.updateGeometricState(); + return frustumMdl; + } + + public void initialize(RenderManager rm, ViewPort vp) { + renderManager = rm; + viewPort = vp; + //checking for caps to chosse the appropriate post material technique + if (renderManager.getRenderer().getCaps().contains(Caps.GLSL150)) { + postTechniqueName = "PostShadow15"; + } else { + postTechniqueName = "PostShadow"; + } + } + + public boolean isInitialized() { + return viewPort != null; + } + + /** + * This mehtod is called once per frame. it is responsible for updating the + * shadow cams according to the light view. + * + * @param viewCam the scene cam + */ + protected abstract void updateShadowCams(Camera viewCam); + + /** + * this method must return the geomtryList that contains the oclluders to be + * rendered in the shadow map + * + * @param shadowMapIndex the index of the shadow map being rendered + * @param sceneOccluders the occluders of the whole scene + * @param sceneReceivers the recievers of the whole scene + * @return + */ + protected abstract GeometryList getOccludersToRender(int shadowMapIndex, GeometryList sceneOccluders, GeometryList sceneReceivers, GeometryList shadowMapOccluders); + + /** + * return the shadow camera to use for rendering the shadow map according + * the given index + * + * @param shadowMapIndex the index of the shadow map being rendered + * @return the shadowCam + */ + protected abstract Camera getShadowCam(int shadowMapIndex); + + /** + * responsible for displaying the frustum of the shadow cam for debug + * purpose + * + * @param shadowMapIndex + */ + protected void doDisplayFrustumDebug(int shadowMapIndex) { + } + + @SuppressWarnings("fallthrough") + public void postQueue(RenderQueue rq) { + GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast); + sceneReceivers = rq.getShadowQueueContent(ShadowMode.Receive); + skipPostPass = false; + if (sceneReceivers.size() == 0 || occluders.size() == 0) { + skipPostPass = true; + return; + } + + updateShadowCams(viewPort.getCamera()); + + Renderer r = renderManager.getRenderer(); + renderManager.setForcedMaterial(preshadowMat); + renderManager.setForcedTechnique("PreShadow"); + + for (int shadowMapIndex = 0; shadowMapIndex < nbShadowMaps; shadowMapIndex++) { + + if (debugfrustums) { + doDisplayFrustumDebug(shadowMapIndex); + } + renderShadowMap(shadowMapIndex, occluders, sceneReceivers); + + } + + debugfrustums = false; + if (flushQueues) { + occluders.clear(); + } + //restore setting for future rendering + r.setFrameBuffer(viewPort.getOutputFrameBuffer()); + renderManager.setForcedMaterial(null); + renderManager.setForcedTechnique(null); + renderManager.setCamera(viewPort.getCamera(), false); + + } + + protected void renderShadowMap(int shadowMapIndex, GeometryList occluders, GeometryList receivers) { + shadowMapOccluders = getOccludersToRender(shadowMapIndex, occluders, receivers, shadowMapOccluders); + Camera shadowCam = getShadowCam(shadowMapIndex); + + //saving light view projection matrix for this split + lightViewProjectionsMatrices[shadowMapIndex].set(shadowCam.getViewProjectionMatrix()); + renderManager.setCamera(shadowCam, false); + + renderManager.getRenderer().setFrameBuffer(shadowFB[shadowMapIndex]); + renderManager.getRenderer().clearBuffers(false, true, false); + + // render shadow casters to shadow map + viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true); + } + boolean debugfrustums = false; + + public void displayFrustum() { + debugfrustums = true; + } + + //debug only : displays depth shadow maps + protected 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((128 * i) + (150 + 64 * (i + 1)), 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; + } + + abstract GeometryList getReceivers(GeometryList sceneReceivers, GeometryList lightReceivers); + + public void postFrame(FrameBuffer out) { + if (skipPostPass) { + return; + } + if (debug) { + displayShadowMap(renderManager.getRenderer()); + } + + lightReceivers = getReceivers(sceneReceivers, lightReceivers); + + if (lightReceivers.size() != 0) { + //setting params to recieving geometry list + setMatParams(); + + Camera cam = viewPort.getCamera(); + //some materials in the scene does not have a post shadow technique so we're using the fall back material + if (needsfallBackMaterial) { + renderManager.setForcedMaterial(postshadowMat); + } + + //forcing the post shadow technique and render state + renderManager.setForcedTechnique(postTechniqueName); + + //rendering the post shadow pass + viewPort.getQueue().renderShadowQueue(lightReceivers, renderManager, cam, false); + if (flushQueues) { + sceneReceivers.clear(); + } + + //resetting renderManager settings + renderManager.setForcedTechnique(null); + renderManager.setForcedMaterial(null); + renderManager.setCamera(cam, false); + + } + + } + + /** + * This method is called once per frame and is responsible of setting the + * material parameters than sub class may need to set on the post material + * + * @param material the materail to use for the post shadow pass + */ + protected abstract void setMaterialParameters(Material material); + + private void setMatParams() { + + GeometryList l = viewPort.getQueue().getShadowQueueContent(ShadowMode.Receive); + + //iteration throught all the geometries of the list to gather the materials + + matCache.clear(); + for (int i = 0; i < l.size(); i++) { + Material mat = l.get(i).getMaterial(); + //checking if the material has the post technique and adding it to the material cache + if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) { + if (!matCache.contains(mat)) { + matCache.add(mat); + } + } else { + needsfallBackMaterial = true; + } + } + + //iterating through the mat cache and setting the parameters + for (Material mat : matCache) { + + mat.setFloat("ShadowMapSize", shadowMapSize); + + for (int j = 0; j < nbShadowMaps; j++) { + mat.setMatrix4(lightViewStringCache[j], lightViewProjectionsMatrices[j]); + } + for (int j = 0; j < nbShadowMaps; j++) { + mat.setTexture(shadowMapStringCache[j], shadowMaps[j]); + } + mat.setBoolean("HardwareShadows", shadowCompareMode == CompareMode.Hardware); + mat.setInt("FilterMode", edgeFilteringMode.getMaterialParamValue()); + mat.setFloat("PCFEdge", edgesThickness); + mat.setFloat("ShadowIntensity", shadowIntensity); + + setMaterialParameters(mat); + } + + //At least one material of the receiving geoms does not support the post shadow techniques + //so we fall back to the forced material solution (transparent shadows won't be supported for these objects) + if (needsfallBackMaterial) { + setPostShadowParams(); + } + + } + + /** + * for internal use only + */ + protected void setPostShadowParams() { + setMaterialParameters(postshadowMat); + for (int j = 0; j < nbShadowMaps; j++) { + postshadowMat.setMatrix4(lightViewStringCache[j], lightViewProjectionsMatrices[j]); + postshadowMat.setTexture(shadowMapStringCache[j], shadowMaps[j]); + } + } + + public void preFrame(float tpf) { + } + + public void cleanup() { + } + + public void reshape(ViewPort vp, int w, int h) { + } + + /** + * returns the shdaow intensity + * + * @see #setShadowIntensity(float shadowIntensity) + * @return shadowIntensity + */ + public float getShadowIntensity() { + return shadowIntensity; + } + + /** + * Set the shadowIntensity, the value should be between 0 and 1, a 0 value + * gives a bright and invisilble shadow, a 1 value gives a pitch black + * shadow, default is 0.7 + * + * @param shadowIntensity the darkness of the shadow + */ + final public void setShadowIntensity(float shadowIntensity) { + this.shadowIntensity = shadowIntensity; + postshadowMat.setFloat("ShadowIntensity", shadowIntensity); + } + + /** + * returns the edges thickness + * + * @see #setEdgesThickness(int edgesThickness) + * @return edgesThickness + */ + public int getEdgesThickness() { + return (int) (edgesThickness * 10); + } + + /** + * Sets the shadow edges thickness. default is 1, setting it to lower values + * can help to reduce the jagged effect of the shadow edges + * + * @param edgesThickness + */ + public void setEdgesThickness(int edgesThickness) { + this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10)); + this.edgesThickness *= 0.1f; + postshadowMat.setFloat("PCFEdge", edgesThickness); + } + + /** + * returns true if the PssmRenderer flushed the shadow queues + * + * @return flushQueues + */ + public boolean isFlushQueues() { + return flushQueues; + } + + /** + * Set this to false if you want to use several PssmRederers to have + * multiple shadows cast by multiple light sources. Make sure the last + * PssmRenderer in the stack DO flush the queues, but not the others + * + * @param flushQueues + */ + public void setFlushQueues(boolean flushQueues) { + this.flushQueues = flushQueues; + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = (InputCapsule) im.getCapsule(this); + assetManager = im.getAssetManager(); + nbShadowMaps = ic.readInt("nbShadowMaps", 1); + shadowMapSize = ic.readInt("shadowMapSize", 0); + shadowIntensity = ic.readFloat("shadowIntensity", 0.7f); + edgeFilteringMode = ic.readEnum("edgeFilteringMode", EdgeFilteringMode.class, EdgeFilteringMode.Bilinear); + shadowCompareMode = ic.readEnum("shadowCompareMode", CompareMode.class, CompareMode.Hardware); + flushQueues = ic.readBoolean("flushQueues", false); + init(assetManager, nbShadowMaps, (int) shadowMapSize); + edgesThickness = ic.readFloat("edgesThickness", 1.0f); + postshadowMat.setFloat("PCFEdge", edgesThickness); + + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(nbShadowMaps, "nbShadowMaps", 1); + oc.write(shadowMapSize, "shadowMapSize", 0); + oc.write(shadowIntensity, "shadowIntensity", 0.7f); + oc.write(edgeFilteringMode, "edgeFilteringMode", EdgeFilteringMode.Bilinear); + oc.write(shadowCompareMode, "shadowCompareMode", CompareMode.Hardware); + oc.write(flushQueues, "flushQueues", false); + oc.write(edgesThickness, "edgesThickness", 1.0f); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java new file mode 100644 index 000000000..50be4c91d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.GeometryList; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture2D; +import com.jme3.ui.Picture; + +/** + * BasicShadowRenderer uses standard shadow mapping with one map + * it's useful to render shadows in a small scene, but edges might look a bit jagged. + * + * @author Kirill Vainer + * @deprecated use {@link DirectionalLightShadowRenderer} with one split. + */ +@Deprecated +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(); + protected Texture2D dummyTex; + private float shadowMapSize; + + /** + * Creates a BasicShadowRenderer + * @param manager the asset manager + * @param size the size of the shadow map (the map is square) + */ + public BasicShadowRenderer(AssetManager manager, int size) { + shadowFB = new FrameBuffer(size, size, 1); + shadowMap = new Texture2D(size, size, Format.Depth); + shadowFB.setDepthTexture(shadowMap); + shadowCam = new Camera(size, size); + + //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) + dummyTex = new Texture2D(size, size, Format.RGBA8); + shadowFB.setColorTexture(dummyTex); + shadowMapSize = (float)size; + preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md"); + postshadowMat = new Material(manager, "Common/MatDefs/Shadow/BasicPostShadow.j3md"); + postshadowMat.setTexture("ShadowMap", shadowMap); + + dispPic.setTexture(manager, shadowMap, false); + + for (int i = 0; i < points.length; i++) { + points[i] = new Vector3f(); + } + } + + public void initialize(RenderManager rm, ViewPort vp) { + renderManager = rm; + viewPort = vp; + + reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight()); + } + + public boolean isInitialized() { + return viewPort != null; + } + + /** + * returns the light direction used for this processor + * @return + */ + public Vector3f getDirection() { + return direction; + } + + /** + * sets the light direction to use to computs shadows + * @param direction + */ + public void setDirection(Vector3f direction) { + this.direction.set(direction).normalizeLocal(); + } + + /** + * debug only + * @return + */ + public Vector3f[] getPoints() { + return points; + } + + /** + * debug only + * returns the shadow camera + * @return + */ + public Camera getShadowCamera() { + return shadowCam; + } + + public void postQueue(RenderQueue rq) { + GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast); + if (occluders.size() == 0) { + noOccluders = true; + return; + } else { + noOccluders = false; + } + + GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive); + + // update frustum points based on current camera + Camera viewCam = viewPort.getCamera(); + ShadowUtil.updateFrustumPoints(viewCam, + viewCam.getFrustumNear(), + viewCam.getFrustumFar(), + 1.0f, + points); + + Vector3f frustaCenter = new Vector3f(); + for (Vector3f point : points) { + frustaCenter.addLocal(point); + } + frustaCenter.multLocal(1f / 8f); + + // update light direction + shadowCam.setProjectionMatrix(null); + shadowCam.setParallelProjection(true); +// shadowCam.setFrustumPerspective(45, 1, 1, 20); + + shadowCam.lookAtDirection(direction, Vector3f.UNIT_Y); + shadowCam.update(); + shadowCam.setLocation(frustaCenter); + shadowCam.update(); + shadowCam.updateViewProjection(); + + // render shadow casters to shadow map + ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points, shadowMapSize); + + Renderer r = renderManager.getRenderer(); + renderManager.setCamera(shadowCam, false); + renderManager.setForcedMaterial(preshadowMat); + + r.setFrameBuffer(shadowFB); + r.clearBuffers(false, true, false); + viewPort.getQueue().renderShadowQueue(ShadowMode.Cast, renderManager, shadowCam, true); + r.setFrameBuffer(viewPort.getOutputFrameBuffer()); + + renderManager.setForcedMaterial(null); + renderManager.setCamera(viewCam, false); + } + + /** + * debug only + * @return + */ + public Picture getDisplayPicture() { + return dispPic; + } + + public void postFrame(FrameBuffer out) { + if (!noOccluders) { + postshadowMat.setMatrix4("LightViewProjectionMatrix", shadowCam.getViewProjectionMatrix()); + renderManager.setForcedMaterial(postshadowMat); + viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, viewPort.getCamera(), true); + renderManager.setForcedMaterial(null); + } + } + + public void preFrame(float tpf) { + } + + public void cleanup() { + } + + public void reshape(ViewPort vp, int w, int h) { + dispPic.setPosition(w / 20f, h / 20f); + dispPic.setWidth(w / 5f); + dispPic.setHeight(h / 5f); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/CompareMode.java b/jme3-core/src/main/java/com/jme3/shadow/CompareMode.java new file mode 100644 index 000000000..27e1bcc18 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/CompareMode.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +/** + * Specifies the shadow comparison mode + */ +public enum CompareMode { + + /** + * Shadow depth comparisons are done by using shader code + */ + Software, + /** + * Shadow depth comparisons are done by using the GPU's dedicated shadowing + * pipeline. + */ + Hardware; +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java new file mode 100644 index 000000000..21b032ec1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.DirectionalLight; +import java.io.IOException; + +/** + * + * This Filter does basically the same as a DirectionalLightShadowRenderer + * except it renders the post shadow pass as a fulscreen quad pass instead of a + * geometry pass. It's mostly faster than PssmShadowRenderer as long as you have + * more than a about ten shadow recieving objects. The expense is the draw back + * that the shadow Recieve mode set on spatial is ignored. So basically all and + * only objects that render depth in the scene receive shadows. See this post + * for more details + * http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599 + * + * API is basically the same as the PssmShadowRenderer; + * + * @author Rémy Bouquet aka Nehon + */ +public class DirectionalLightShadowFilter extends AbstractShadowFilter { + + /** + * Creates a DirectionalLightShadowFilter Shadow Filter More info on the + * technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html + * + * @param assetManager the application asset manager + * @param shadowMapSize 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 DirectionalLightShadowFilter(AssetManager assetManager, int shadowMapSize, int nbSplits) { + super(assetManager, shadowMapSize, new DirectionalLightShadowRenderer(assetManager, shadowMapSize, nbSplits)); + } + + /** + * return the light used to cast shadows + * + * @return the DirectionalLight + */ + public DirectionalLight getLight() { + return shadowRenderer.getLight(); + } + + /** + * Sets the light to use to cast shadows + * + * @param light a DirectionalLight + */ + public void setLight(DirectionalLight light) { + shadowRenderer.setLight(light); + } + + /** + * returns the labda parameter + * + * @see #setLambda(float lambda) + * @return lambda + */ + public float getLambda() { + return shadowRenderer.getLambda(); + } + + /** + * 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) { + shadowRenderer.setLambda(lambda); + } + + /** + * How far the shadows are rendered in the view + * + * @see setShadowZExtend(float zFar) + * @return shadowZExtend + */ + public float getShadowZExtend() { + return shadowRenderer.getShadowZExtend(); + } + + /** + * 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) { + shadowRenderer.setShadowZExtend(zFar); + } + + /** + * Define the length over which the shadow will fade out when using a + * shadowZextend + * + * @param length the fade length in world units + */ + public void setShadowZFadeLength(float length) { + shadowRenderer.setShadowZFadeLength(length); + } + + /** + * get the length over which the shadow will fade out when using a + * shadowZextend + * + * @return the fade length in world units + */ + public float getShadowZFadeLength() { + return shadowRenderer.getShadowZFadeLength(); + } + + /** + * retruns true if stabilization is enabled + * @return + */ + public boolean isEnabledStabilization() { + return shadowRenderer.isEnabledStabilization(); + } + + /** + * Enables the stabilization of the shadows's edges. (default is true) + * This prevents shadows' edges to flicker when the camera moves + * However it can lead to some shadow quality loss in some particular scenes. + * @param stabilize + */ + public void setEnabledStabilization(boolean stabilize) { + shadowRenderer.setEnabledStabilization(stabilize); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(shadowRenderer, "shadowRenderer", null); + + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + shadowRenderer = (DirectionalLightShadowRenderer) ic.readSavable("shadowRenderer", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java new file mode 100644 index 000000000..060ed5ea4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.GeometryList; +import com.jme3.scene.Node; +import java.io.IOException; + +/** + * DirectionalLightShadowRenderer renderer use Parrallel Split Shadow Mapping + * technique (pssm)
      It splits the view frustum in several parts and compute + * a shadow map for each one.
      splits are distributed so that the closer they + * are from the camera, the smaller they are to maximize the resolution used of + * the shadow map.
      This result in a better quality shadow than standard + * shadow mapping.
      for more informations on this read this http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html
      + *

      + * @author Rémy Bouquet aka Nehon + */ +public class DirectionalLightShadowRenderer extends AbstractShadowRenderer { + + protected float lambda = 0.65f; + protected float zFarOverride = 0; + protected Camera shadowCam; + protected ColorRGBA splits; + protected float[] splitsArray; + protected DirectionalLight light; + protected Vector3f[] points = new Vector3f[8]; + //Holding the info for fading shadows in the far distance + protected Vector2f fadeInfo; + protected float fadeLength; + private boolean stabilize = true; + + /** + * Used for serialzation use + * DirectionalLightShadowRenderer#DirectionalLightShadowRenderer(AssetManager + * assetManager, int shadowMapSize, int nbSplits) + */ + public DirectionalLightShadowRenderer() { + super(); + } + + /** + * Create a DirectionalLightShadowRenderer More info on the technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html + * + * @param assetManager the application asset manager + * @param shadowMapSize 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 DirectionalLightShadowRenderer(AssetManager assetManager, int shadowMapSize, int nbSplits) { + super(assetManager, shadowMapSize, nbSplits); + init(nbSplits, shadowMapSize); + } + + private void init(int nbSplits, int shadowMapSize) { + nbShadowMaps = Math.max(Math.min(nbSplits, 4), 1); + if (nbShadowMaps != nbSplits) { + throw new IllegalArgumentException("Number of splits must be between 1 and 4. Given value : " + nbSplits); + } + splits = new ColorRGBA(); + splitsArray = new float[nbSplits + 1]; + shadowCam = new Camera(shadowMapSize, shadowMapSize); + shadowCam.setParallelProjection(true); + for (int i = 0; i < points.length; i++) { + points[i] = new Vector3f(); + } + } + + /** + * return the light used to cast shadows + * + * @return the DirectionalLight + */ + public DirectionalLight getLight() { + return light; + } + + /** + * Sets the light to use to cast shadows + * + * @param light a DirectionalLight + */ + public void setLight(DirectionalLight light) { + this.light = light; + } + + @Override + protected void updateShadowCams(Camera viewCam) { + + float zFar = zFarOverride; + if (zFar == 0) { + zFar = viewCam.getFrustumFar(); + } + + //We prevent computing the frustum points and splits with zeroed or negative near clip value + float frustumNear = Math.max(viewCam.getFrustumNear(), 0.001f); + ShadowUtil.updateFrustumPoints(viewCam, frustumNear, zFar, 1.0f, points); + + //shadowCam.setDirection(direction); + shadowCam.getRotation().lookAt(light.getDirection(), shadowCam.getUp()); + shadowCam.update(); + shadowCam.updateViewProjection(); + + PssmShadowUtil.updateFrustumSplits(splitsArray, frustumNear, zFar, lambda); + + // in parallel projection shadow position goe from 0 to 1 + if(viewCam.isParallelProjection()){ + for (int i = 0; i < nbShadowMaps; i++) { + splitsArray[i] = splitsArray[i]/(zFar- frustumNear); + } + } + + 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; + } + + } + + @Override + protected GeometryList getOccludersToRender(int shadowMapIndex, GeometryList sceneOccluders, GeometryList sceneReceivers, GeometryList shadowMapOccluders) { + + // update frustum points based on current camera and split + ShadowUtil.updateFrustumPoints(viewPort.getCamera(), splitsArray[shadowMapIndex], splitsArray[shadowMapIndex + 1], 1.0f, points); + + //Updating shadow cam with curent split frustra + ShadowUtil.updateShadowCamera(sceneOccluders, sceneReceivers, shadowCam, points, shadowMapOccluders, stabilize?shadowMapSize:0); + + return shadowMapOccluders; + } + + @Override + GeometryList getReceivers(GeometryList sceneReceivers, GeometryList lightReceivers) { + return sceneReceivers; + } + + @Override + protected Camera getShadowCam(int shadowMapIndex) { + return shadowCam; + } + + @Override + protected void doDisplayFrustumDebug(int shadowMapIndex) { + ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, shadowMapIndex)); + ShadowUtil.updateFrustumPoints2(shadowCam, points); + ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, shadowMapIndex)); + } + + @Override + protected void setMaterialParameters(Material material) { + material.setColor("Splits", splits); + } + + /** + * returns the labda parameter see #setLambda(float lambda) + * + * @return lambda + */ + public float getLambda() { + return lambda; + } + + /* + * Adjust the repartition of the different shadow maps in the shadow extend + * usualy goes from 0.0 to 1.0 + * a low value give a more linear repartition resulting in a constant quality in the shadow over the extends, but near shadows could look very jagged + * a high value give a more logarithmic repartition resulting in a high quality for near shadows, but the quality quickly decrease over the extend. + * the default value is set to 0.65f (theoric optimal value). + * @param lambda the lambda value. + */ + public void setLambda(float lambda) { + this.lambda = lambda; + } + + /** + * How far the shadows are rendered in the view + * + * @see #setShadowZExtend(float zFar) + * @return shadowZExtend + */ + public float getShadowZExtend() { + return zFarOverride; + } + + /** + * Set the distance from the eye where the shadows will be rendered default + * value is dynamicaly computed to the shadow casters/receivers union bound + * zFar, capped to view frustum far value. + * + * @param zFar the zFar values that override the computed one + */ + public void setShadowZExtend(float zFar) { + if (fadeInfo != null) { + fadeInfo.set(zFar - fadeLength, 1f / fadeLength); + } + this.zFarOverride = zFar; + + } + + /** + * Define the length over which the shadow will fade out when using a + * shadowZextend This is useful to make dynamic shadows fade into baked + * shadows in the distance. + * + * @param length the fade length in world units + */ + public void setShadowZFadeLength(float length) { + if (length == 0) { + fadeInfo = null; + fadeLength = 0; + postshadowMat.clearParam("FadeInfo"); + } else { + if (zFarOverride == 0) { + fadeInfo = new Vector2f(0, 0); + } else { + fadeInfo = new Vector2f(zFarOverride - length, 1.0f / length); + } + fadeLength = length; + postshadowMat.setVector2("FadeInfo", fadeInfo); + } + } + + /** + * get the length over which the shadow will fade out when using a + * shadowZextend + * + * @return the fade length in world units + */ + public float getShadowZFadeLength() { + if (fadeInfo != null) { + return zFarOverride - fadeInfo.x; + } + return 0f; + } + + /** + * retruns true if stabilization is enabled + * @return + */ + public boolean isEnabledStabilization() { + return stabilize; + } + + /** + * Enables the stabilization of the shadows's edges. (default is true) + * This prevents shadows' edges to flicker when the camera moves + * However it can lead to some shadow quality loss in some particular scenes. + * @param stabilize + */ + public void setEnabledStabilization(boolean stabilize) { + this.stabilize = stabilize; + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = (InputCapsule) im.getCapsule(this); + lambda = ic.readFloat("lambda", 0.65f); + zFarOverride = ic.readInt("zFarOverride", 0); + light = (DirectionalLight) ic.readSavable("light", null); + fadeInfo = (Vector2f) ic.readSavable("fadeInfo", null); + fadeLength = ic.readFloat("fadeLength", 0f); + init(nbShadowMaps, (int) shadowMapSize); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(lambda, "lambda", 0.65f); + oc.write(zFarOverride, "zFarOverride", 0); + oc.write(light, "light", null); + oc.write(fadeInfo, "fadeInfo", null); + oc.write(fadeLength, "fadeLength", 0f); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/EdgeFilteringMode.java b/jme3-core/src/main/java/com/jme3/shadow/EdgeFilteringMode.java new file mode 100644 index 000000000..6c9f3d922 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/EdgeFilteringMode.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +/** + * ShadowEdgeFiltering specifies how shadows are filtered + */ +public enum EdgeFilteringMode { + + /** + * Shadows are not filtered. Nearest sample is used, causing in blocky + * shadows. + */ + Nearest(0), + /** + * Bilinear filtering is used. Has the potential of being hardware + * accelerated on some GPUs + */ + Bilinear(1), + /** + * Dither-based sampling is used, very cheap but can look bad at low + * resolutions. + */ + Dither(2), + /** + * 4x4 percentage-closer filtering is used. Shadows will be smoother at the + * cost of performance + */ + PCF4(3), + /** + * 12 samples percentage-closer filtering with a POISON disc distribution + * is used. + * http://devmag.org.za/2009/05/03/poisson-disk-sampling/ + * The principle is to eliminate the regular blurring pattern that can be + * seen with pcf4x4 by randomizing the samble position with a poisson disc. + * Shadows will look smoother than 4x4 PCF but with slightly better or + * similar performance. + */ + PCFPOISSON(4), + /** + * 8x8 percentage-closer filtering is used. Shadows will be smoother at the + * cost of performance + */ + PCF8(5); + + int materialParamValue; + + private EdgeFilteringMode(int val) { + materialParamValue = val; + } + + public int getMaterialParamValue() { + return materialParamValue; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowFilter.java new file mode 100644 index 000000000..82730860a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowFilter.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.PointLight; +import java.io.IOException; + +/** + * + * This Filter does basically the same as a PointLightShadowRenderer except it + * renders the post shadow pass as a fulscreen quad pass instead of a geometry + * pass. It's mostly faster than PointLightShadowRenderer as long as you have + * more than a about ten shadow recieving objects. The expense is the draw back + * that the shadow Recieve mode set on spatial is ignored. So basically all and + * only objects that render depth in the scene receive shadows. See this post + * for more details + * http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599 + * + * API is basically the same as the PssmShadowRenderer; + * + * @author Rémy Bouquet aka Nehon + */ +public class PointLightShadowFilter extends AbstractShadowFilter { + + /** + * Creates a PointLightShadowFilter + * + * @param assetManager the application asset manager + * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, + * etc...) + */ + public PointLightShadowFilter(AssetManager assetManager, int shadowMapSize) { + super(assetManager, shadowMapSize, new PointLightShadowRenderer(assetManager, shadowMapSize)); + } + + /** + * gets the point light used to cast shadows with this processor + * + * @return the point light + */ + public PointLight getLight() { + return shadowRenderer.getLight(); + } + + /** + * sets the light to use for casting shadows with this processor + * + * @param light the point light + */ + public void setLight(PointLight light) { + shadowRenderer.setLight(light); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(shadowRenderer, "shadowRenderer", null); + + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + shadowRenderer = (PointLightShadowRenderer) ic.readSavable("shadowRenderer", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowRenderer.java new file mode 100644 index 000000000..d82ee837c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowRenderer.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.GeometryList; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import java.io.IOException; + +/** + * PointLightShadowRenderer renders shadows for a point light + * + * @author Rémy Bouquet aka Nehon + */ +public class PointLightShadowRenderer extends AbstractShadowRenderer { + + public static final int CAM_NUMBER = 6; + protected PointLight light; + protected Camera[] shadowCams; + private Geometry[] frustums = null; + + /** + * Used for serialization use + * PointLightShadowRenderer"PointLightShadowRenderer(AssetManager + * assetManager, int shadowMapSize) + */ + public PointLightShadowRenderer() { + super(); + } + + /** + * Creates a PointLightShadowRenderer + * + * @param assetManager the application asset manager + * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, + * etc...) + */ + public PointLightShadowRenderer(AssetManager assetManager, int shadowMapSize) { + super(assetManager, shadowMapSize, CAM_NUMBER); + init(shadowMapSize); + } + + private void init(int shadowMapSize) { + shadowCams = new Camera[CAM_NUMBER]; + for (int i = 0; i < CAM_NUMBER; i++) { + shadowCams[i] = new Camera(shadowMapSize, shadowMapSize); + } + } + + @Override + protected void updateShadowCams(Camera viewCam) { + + if (light == null) { + throw new IllegalStateException("The light can't be null for a " + this.getClass().getName()); + } + + //bottom + shadowCams[0].setAxes(Vector3f.UNIT_X.mult(-1f), Vector3f.UNIT_Z.mult(-1f), Vector3f.UNIT_Y.mult(-1f)); + + //top + shadowCams[1].setAxes(Vector3f.UNIT_X.mult(-1f), Vector3f.UNIT_Z, Vector3f.UNIT_Y); + + //forward + shadowCams[2].setAxes(Vector3f.UNIT_X.mult(-1f), Vector3f.UNIT_Y, Vector3f.UNIT_Z.mult(-1f)); + + //backward + shadowCams[3].setAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z); + + //left + shadowCams[4].setAxes(Vector3f.UNIT_Z, Vector3f.UNIT_Y, Vector3f.UNIT_X.mult(-1f)); + + //right + shadowCams[5].setAxes(Vector3f.UNIT_Z.mult(-1f), Vector3f.UNIT_Y, Vector3f.UNIT_X); + + for (int i = 0; i < CAM_NUMBER; i++) { + shadowCams[i].setFrustumPerspective(90f, 1f, 0.1f, light.getRadius()); + shadowCams[i].setLocation(light.getPosition()); + shadowCams[i].update(); + shadowCams[i].updateViewProjection(); + } + + } + + @Override + protected GeometryList getOccludersToRender(int shadowMapIndex, GeometryList sceneOccluders, GeometryList sceneReceivers, GeometryList shadowMapOccluders) { + ShadowUtil.getGeometriesInCamFrustum(sceneOccluders, shadowCams[shadowMapIndex], shadowMapOccluders); + return shadowMapOccluders; + } + + @Override + GeometryList getReceivers(GeometryList sceneReceivers, GeometryList lightReceivers) { + lightReceivers.clear(); + ShadowUtil.getGeometriesInLightRadius(sceneReceivers, shadowCams, lightReceivers); + return lightReceivers; + } + + @Override + protected Camera getShadowCam(int shadowMapIndex) { + return shadowCams[shadowMapIndex]; + } + + @Override + protected void doDisplayFrustumDebug(int shadowMapIndex) { + if (frustums == null) { + frustums = new Geometry[CAM_NUMBER]; + Vector3f[] points = new Vector3f[8]; + for (int i = 0; i < 8; i++) { + points[i] = new Vector3f(); + } + for (int i = 0; i < CAM_NUMBER; i++) { + ShadowUtil.updateFrustumPoints2(shadowCams[i], points); + frustums[i] = createFrustum(points, i); + } + } + if (frustums[shadowMapIndex].getParent() == null) { + ((Node) viewPort.getScenes().get(0)).attachChild(frustums[shadowMapIndex]); + } + } + + @Override + protected void setMaterialParameters(Material material) { + material.setVector3("LightPos", light.getPosition()); + } + + /** + * gets the point light used to cast shadows with this processor + * + * @return the point light + */ + public PointLight getLight() { + return light; + } + + /** + * sets the light to use for casting shadows with this processor + * + * @param light the point light + */ + public void setLight(PointLight light) { + this.light = light; + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = (InputCapsule) im.getCapsule(this); + light = (PointLight) ic.readSavable("light", null); + init((int) shadowMapSize); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(light, "light", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/PssmShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowFilter.java new file mode 100644 index 000000000..eda1c5ee6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowFilter.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.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.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.shadow.PssmShadowRenderer.CompareMode; +import com.jme3.shadow.PssmShadowRenderer.FilterMode; +import com.jme3.texture.FrameBuffer; +import java.io.IOException; + +/** + * + * This Filter does basically the same as a PssmShadowRenderer except it renders + * the post shadow pass as a fulscreen quad pass instead of a geometry pass. + * It's mostly faster than PssmShadowRenderer as long as you have more than a about ten shadow recieving objects. + * The expense is the draw back that the shadow Recieve mode set on spatial is ignored. + * So basically all and only objects that render depth in the scene receive shadows. + * See this post for more details http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599 + * + * API is basically the same as the PssmShadowRenderer; + * + * @author Rémy Bouquet aka Nehon + * @deprecated use {@link DirectionalLightShadowFilter} + */ +@Deprecated +public class PssmShadowFilter extends Filter { + + private PssmShadowRenderer pssmRenderer; + private ViewPort viewPort; + + /** + * Creates a PSSM Shadow Filter + * 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 PssmShadowFilter(AssetManager manager, int size, int nbSplits) { + super("Post Shadow"); + material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md"); + pssmRenderer = new PssmShadowRenderer(manager, size, nbSplits, material); + pssmRenderer.needsfallBackMaterial = true; + } + + @Override + protected Material getMaterial() { + return material; + } + + @Override + protected boolean isRequiresDepthTexture() { + return true; + } + + public Material getShadowMaterial() { + return material; + } + Vector4f tmpv = new Vector4f(); + + @Override + protected void preFrame(float tpf) { + pssmRenderer.preFrame(tpf); + material.setMatrix4("ViewProjectionMatrixInverse", viewPort.getCamera().getViewProjectionMatrix().invert()); + Matrix4f m = viewPort.getCamera().getViewProjectionMatrix(); + material.setVector4("ViewProjectionMatrixRow2", tmpv.set(m.m20, m.m21, m.m22, m.m23)); + + } + + @Override + protected void postQueue(RenderQueue queue) { + pssmRenderer.postQueue(queue); + } + + @Override + protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) { + pssmRenderer.setPostShadowParams(); + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + pssmRenderer.initialize(renderManager, vp); + this.viewPort = vp; + } + + /** + * returns the light direction used by the processor + * @return + */ + public Vector3f getDirection() { + return pssmRenderer.getDirection(); + } + + /** + * Sets the light direction to use to compute shadows + * @param direction + */ + public void setDirection(Vector3f direction) { + pssmRenderer.setDirection(direction); + } + + /** + * returns the labda parameter + * @see #setLambda(float lambda) + * @return lambda + */ + public float getLambda() { + return pssmRenderer.getLambda(); + } + + /** + * 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) { + pssmRenderer.setLambda(lambda); + } + + /** + * How far the shadows are rendered in the view + * @see setShadowZExtend(float zFar) + * @return shadowZExtend + */ + public float getShadowZExtend() { + return pssmRenderer.getShadowZExtend(); + } + + /** + * 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) { + pssmRenderer.setShadowZExtend(zFar); + } + + /** + * returns the shdaow intensity + * @see #setShadowIntensity(float shadowIntensity) + * @return shadowIntensity + */ + public float getShadowIntensity() { + return pssmRenderer.getShadowIntensity(); + } + + /** + * 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 + */ + final public void setShadowIntensity(float shadowIntensity) { + pssmRenderer.setShadowIntensity(shadowIntensity); + } + + /** + * returns the edges thickness
      + * @see #setEdgesThickness(int edgesThickness) + * @return edgesThickness + */ + public int getEdgesThickness() { + return pssmRenderer.getEdgesThickness(); + } + + /** + * Sets the shadow edges thickness. default is 1, setting it to lower values can help to reduce the jagged effect of the shadow edges + * @param edgesThickness + */ + public void setEdgesThickness(int edgesThickness) { + pssmRenderer.setEdgesThickness(edgesThickness); + } + + /** + * returns true if the PssmRenderer flushed the shadow queues + * @return flushQueues + */ + public boolean isFlushQueues() { + return pssmRenderer.isFlushQueues(); + } + + /** + * Set this to false if you want to use several PssmRederers to have multiple shadows cast by multiple light sources. + * Make sure the last PssmRenderer in the stack DO flush the queues, but not the others + * @param flushQueues + */ + public void setFlushQueues(boolean flushQueues) { + pssmRenderer.setFlushQueues(flushQueues); + } + + /** + * sets the shadow compare mode see {@link CompareMode} for more info + * @param compareMode + */ + final public void setCompareMode(CompareMode compareMode) { + pssmRenderer.setCompareMode(compareMode); + } + + /** + * Sets the filtering mode for shadow edges see {@link FilterMode} for more info + * @param filterMode + */ + final public void setFilterMode(FilterMode filterMode) { + pssmRenderer.setFilterMode(filterMode); + } + + /** + * Define the length over which the shadow will fade out when using a shadowZextend + * @param length the fade length in world units + */ + public void setShadowZFadeLength(float length){ + pssmRenderer.setShadowZFadeLength(length); + } + + /** + * get the length over which the shadow will fade out when using a shadowZextend + * @return the fade length in world units + */ + public float getShadowZFadeLength(){ + return pssmRenderer.getShadowZFadeLength(); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java new file mode 100644 index 000000000..080c91fea --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java @@ -0,0 +1,751 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.Camera; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.GeometryList; +import com.jme3.renderer.queue.OpaqueComparator; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +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; +import java.util.ArrayList; +import java.util.List; + +/** + * PssmShadow renderer use Parrallel Split Shadow Mapping technique (pssm)
      + * It splits the view frustum in several parts and compute a shadow map for each + * one.
      splits are distributed so that the closer they are from the camera, + * the smaller they are to maximize the resolution used of the shadow map.
      + * This result in a better quality shadow than standard shadow mapping.
      for + * more informations on this read this http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html
      + *

      + * @author Rémy Bouquet aka Nehon + * @deprecated use {@link DirectionalLightShadowRenderer} + */ +@Deprecated +public class PssmShadowRenderer implements SceneProcessor { + + /** + * FilterMode specifies how shadows are filtered + * @deprecated use {@link EdgeFilteringMode} + */ + @Deprecated + 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 + */ + PCFPOISSON, + /** + * 8x8 percentage-closer filtering is used. Shadows will be smoother at + * the cost of performance + */ + PCF8 + } + + /** + * Specifies the shadow comparison mode + * @deprecated use {@link CompareMode} + */ + @Deprecated + 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; + } + protected int nbSplits = 3; + protected float shadowMapSize; + protected float lambda = 0.65f; + protected float shadowIntensity = 0.7f; + protected float zFarOverride = 0; + protected RenderManager renderManager; + protected ViewPort viewPort; + protected FrameBuffer[] shadowFB; + protected Texture2D[] shadowMaps; + protected Texture2D dummyTex; + protected Camera shadowCam; + protected Material preshadowMat; + protected Material postshadowMat; + protected GeometryList splitOccluders = new GeometryList(new OpaqueComparator()); + protected Matrix4f[] lightViewProjectionsMatrices; + protected ColorRGBA splits; + protected float[] splitsArray; + protected boolean noOccluders = false; + protected Vector3f direction = new Vector3f(); + protected AssetManager assetManager; + protected boolean debug = false; + protected float edgesThickness = 1.0f; + protected FilterMode filterMode; + protected CompareMode compareMode; + protected Picture[] dispPic; + protected Vector3f[] points = new Vector3f[8]; + protected boolean flushQueues = true; + // define if the fallback material should be used. + protected boolean needsfallBackMaterial = false; + //Name of the post material technique + protected String postTechniqueName = "PostShadow"; + //flags to know when to change params in the materials + protected boolean applyHWShadows = true; + protected boolean applyFilterMode = true; + protected boolean applyPCFEdge = true; + protected boolean applyShadowIntensity = true; + //a list of material of the post shadow queue geometries. + protected List matCache = new ArrayList(); + //Holding the info for fading shadows in the far distance + protected Vector2f fadeInfo; + protected float fadeLength; + protected boolean applyFadeInfo = false; + + /** + * 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). + * @param nbSplits the number of shadow maps rendered (the more shadow maps + * the more quality, the less fps). + */ + public PssmShadowRenderer(AssetManager manager, int size, int nbSplits) { + this(manager, size, nbSplits, new Material(manager, "Common/MatDefs/Shadow/PostShadow.j3md")); + } + + /** + * 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). + * @param postShadowMat the material used for post shadows if you need to + * override it + */ + protected PssmShadowRenderer(AssetManager manager, int size, int nbSplits, Material postShadowMat) { + + this.postshadowMat = postShadowMat; + assetManager = manager; + nbSplits = Math.max(Math.min(nbSplits, 4), 1); + this.nbSplits = nbSplits; + shadowMapSize = size; + + 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.setFloat("ShadowMapSize", size); + + for (int i = 0; i < nbSplits; i++) { + lightViewProjectionsMatrices[i] = new Matrix4f(); + shadowFB[i] = new FrameBuffer(size, size, 1); + shadowMaps[i] = new Texture2D(size, size, Format.Depth); + + shadowFB[i].setDepthTexture(shadowMaps[i]); + + //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) + shadowFB[i].setColorTexture(dummyTex); + + postshadowMat.setTexture("ShadowMap" + i, shadowMaps[i]); + + //quads for debuging purpose + dispPic[i] = new Picture("Picture" + i); + dispPic[i].setTexture(manager, shadowMaps[i], false); + } + + setCompareMode(CompareMode.Hardware); + setFilterMode(FilterMode.Bilinear); + setShadowIntensity(0.7f); + + shadowCam = new Camera(size, size); + shadowCam.setParallelProjection(true); + + for (int i = 0; i < points.length; i++) { + points[i] = new Vector3f(); + } + + } + + /** + * Sets the filtering mode for shadow edges see {@link FilterMode} for more + * info + * + * @param filterMode + */ + final 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); + } + } + } + applyFilterMode = true; + } + + /** + * sets the shadow compare mode see {@link CompareMode} for more info + * + * @param compareMode + */ + final 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); + applyHWShadows = true; + } + + //debug function that create a displayable frustrum + private Geometry createFrustum(Vector3f[] pts, int i) { + WireFrustum frustum = new WireFrustum(pts); + Geometry frustumMdl = new Geometry("f", frustum); + frustumMdl.setCullHint(Spatial.CullHint.Never); + frustumMdl.setShadowMode(ShadowMode.Off); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + frustumMdl.setMaterial(mat); + switch (i) { + case 0: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Pink); + break; + case 1: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red); + break; + case 2: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Green); + break; + case 3: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Blue); + break; + default: + frustumMdl.getMaterial().setColor("Color", ColorRGBA.White); + break; + } + + frustumMdl.updateGeometricState(); + return frustumMdl; + } + + public void initialize(RenderManager rm, ViewPort vp) { + renderManager = rm; + viewPort = vp; + //checking for caps to chosse the appropriate post material technique + if (renderManager.getRenderer().getCaps().contains(Caps.GLSL150)) { + postTechniqueName = "PostShadow15"; + } else { + postTechniqueName = "PostShadow"; + } + } + + public boolean isInitialized() { + return viewPort != null; + } + + /** + * returns the light direction used by the processor + * + * @return + */ + public Vector3f getDirection() { + return direction; + } + + /** + * Sets the light direction to use to compute shadows + * + * @param direction + */ + public void setDirection(Vector3f direction) { + this.direction.set(direction).normalizeLocal(); + } + + @SuppressWarnings("fallthrough") + public void postQueue(RenderQueue rq) { + GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast); + if (occluders.size() == 0) { + return; + } + + GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive); + if (receivers.size() == 0) { + return; + } + + Camera viewCam = viewPort.getCamera(); + + float zFar = zFarOverride; + if (zFar == 0) { + zFar = viewCam.getFrustumFar(); + } + + //We prevent computing the frustum points and splits with zeroed or negative near clip value + float frustumNear = Math.max(viewCam.getFrustumNear(), 0.001f); + ShadowUtil.updateFrustumPoints(viewCam, frustumNear, zFar, 1.0f, points); + + //shadowCam.setDirection(direction); + shadowCam.getRotation().lookAt(direction, shadowCam.getUp()); + shadowCam.update(); + shadowCam.updateViewProjection(); + + PssmShadowUtil.updateFrustumSplits(splitsArray, frustumNear, zFar, lambda); + + + switch (splitsArray.length) { + case 5: + splits.a = splitsArray[4]; + case 4: + splits.b = splitsArray[3]; + case 3: + splits.g = splitsArray[2]; + case 2: + case 1: + splits.r = splitsArray[1]; + break; + } + + Renderer r = renderManager.getRenderer(); + renderManager.setForcedMaterial(preshadowMat); + renderManager.setForcedTechnique("PreShadow"); + + for (int i = 0; i < nbSplits; i++) { + + // update frustum points based on current camera and split + ShadowUtil.updateFrustumPoints(viewCam, splitsArray[i], splitsArray[i + 1], 1.0f, points); + + //Updating shadow cam with curent split frustra + ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points, splitOccluders, shadowMapSize); + + //saving light view projection matrix for this split + lightViewProjectionsMatrices[i].set(shadowCam.getViewProjectionMatrix()); + renderManager.setCamera(shadowCam, false); + + if (debugfrustums) { +// frustrumFromBound(b.casterBB,ColorRGBA.Blue ); +// frustrumFromBound(b.receiverBB,ColorRGBA.Green ); +// frustrumFromBound(b.splitBB,ColorRGBA.Yellow ); + ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, i)); + ShadowUtil.updateFrustumPoints2(shadowCam, points); + ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, i)); + + } + + r.setFrameBuffer(shadowFB[i]); + r.clearBuffers(false, true, false); + + // render shadow casters to shadow map + viewPort.getQueue().renderShadowQueue(splitOccluders, renderManager, shadowCam, true); + } + debugfrustums = false; + if (flushQueues) { + occluders.clear(); + } + //restore setting for future rendering + r.setFrameBuffer(viewPort.getOutputFrameBuffer()); + renderManager.setForcedMaterial(null); + renderManager.setForcedTechnique(null); + renderManager.setCamera(viewCam, false); + + } + boolean debugfrustums = false; + + public void displayFrustum() { + debugfrustums = true; + } + + //debug only : displays depth shadow maps + protected 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((128 * i) + (150 + 64 * (i + 1)), 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) { + + if (debug) { + displayShadowMap(renderManager.getRenderer()); + } + if (!noOccluders) { + //setting params to recieving geometry list + setMatParams(); + + Camera cam = viewPort.getCamera(); + //some materials in the scene does not have a post shadow technique so we're using the fall back material + if (needsfallBackMaterial) { + renderManager.setForcedMaterial(postshadowMat); + } + + //forcing the post shadow technique and render state + renderManager.setForcedTechnique(postTechniqueName); + + //rendering the post shadow pass + viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, cam, flushQueues); + + //resetting renderManager settings + renderManager.setForcedTechnique(null); + renderManager.setForcedMaterial(null); + renderManager.setCamera(cam, false); + + } + + } + + private void setMatParams() { + + GeometryList l = viewPort.getQueue().getShadowQueueContent(ShadowMode.Receive); + + //iteration throught all the geometries of the list to gather the materials + + matCache.clear(); + for (int i = 0; i < l.size(); i++) { + Material mat = l.get(i).getMaterial(); + //checking if the material has the post technique and adding it to the material cache + if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) { + if (!matCache.contains(mat)) { + matCache.add(mat); + } + } else { + needsfallBackMaterial = true; + } + } + + //iterating through the mat cache and setting the parameters + for (Material mat : matCache) { + mat.setColor("Splits", splits); + mat.setFloat("ShadowMapSize", shadowMapSize); + + for (int j = 0; j < nbSplits; j++) { + mat.setMatrix4("LightViewProjectionMatrix" + j, lightViewProjectionsMatrices[j]); + } + for (int j = 0; j < nbSplits; j++) { + mat.setTexture("ShadowMap" + j, shadowMaps[j]); + } + mat.setBoolean("HardwareShadows", compareMode == CompareMode.Hardware); + mat.setInt("FilterMode", filterMode.ordinal()); + mat.setFloat("PCFEdge", edgesThickness); + mat.setFloat("ShadowIntensity", shadowIntensity); + + if (fadeInfo != null) { + mat.setVector2("FadeInfo", fadeInfo); + } + + } + + applyHWShadows = false; + applyFilterMode = false; + applyPCFEdge = false; + applyShadowIntensity = false; + applyFadeInfo = false; + + //At least one material of the receiving geoms does not support the post shadow techniques + //so we fall back to the forced material solution (transparent shadows won't be supported for these objects) + if (needsfallBackMaterial) { + setPostShadowParams(); + } + + } + + protected void setPostShadowParams() { + postshadowMat.setColor("Splits", splits); + for (int j = 0; j < nbSplits; j++) { + postshadowMat.setMatrix4("LightViewProjectionMatrix" + j, lightViewProjectionsMatrices[j]); + postshadowMat.setTexture("ShadowMap" + j, shadowMaps[j]); + } + } + + public void preFrame(float tpf) { + } + + public void cleanup() { + } + + public void reshape(ViewPort vp, int w, int h) { + } + + /** + * returns the labda parameter see #setLambda(float lambda) + * + * @return lambda + */ + public float getLambda() { + return lambda; + } + + /* + * Adjust the repartition of the different shadow maps in the shadow extend + * usualy goes from 0.0 to 1.0 + * a low value give a more linear repartition resulting in a constant quality in the shadow over the extends, but near shadows could look very jagged + * a high value give a more logarithmic repartition resulting in a high quality for near shadows, but the quality quickly decrease over the extend. + * the default value is set to 0.65f (theoric optimal value). + * @param lambda the lambda value. + */ + public void setLambda(float lambda) { + this.lambda = lambda; + } + + /** + * How far the shadows are rendered in the view + * + * @see #setShadowZExtend(float zFar) + * @return shadowZExtend + */ + public float getShadowZExtend() { + return zFarOverride; + } + + /** + * Set the distance from the eye where the shadows will be rendered default + * value is dynamicaly computed to the shadow casters/receivers union bound + * zFar, capped to view frustum far value. + * + * @param zFar the zFar values that override the computed one + */ + public void setShadowZExtend(float zFar) { + if (fadeInfo != null) { + fadeInfo.set(zFar - fadeLength, 1f / fadeLength); + } + this.zFarOverride = zFar; + + } + + /** + * returns the shdaow intensity + * + * @see #setShadowIntensity(float shadowIntensity) + * @return shadowIntensity + */ + public float getShadowIntensity() { + return shadowIntensity; + } + + /** + * Set the shadowIntensity, the value should be between 0 and 1, a 0 value + * gives a bright and invisilble shadow, a 1 value gives a pitch black + * shadow, default is 0.7 + * + * @param shadowIntensity the darkness of the shadow + */ + final public void setShadowIntensity(float shadowIntensity) { + this.shadowIntensity = shadowIntensity; + postshadowMat.setFloat("ShadowIntensity", shadowIntensity); + applyShadowIntensity = true; + } + + /** + * returns the edges thickness + * + * @see #setEdgesThickness(int edgesThickness) + * @return edgesThickness + */ + public int getEdgesThickness() { + return (int) (edgesThickness * 10); + } + + /** + * Sets the shadow edges thickness. default is 1, setting it to lower values + * can help to reduce the jagged effect of the shadow edges + * + * @param edgesThickness + */ + public void setEdgesThickness(int edgesThickness) { + this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10)); + this.edgesThickness *= 0.1f; + postshadowMat.setFloat("PCFEdge", edgesThickness); + applyPCFEdge = true; + } + + /** + * returns true if the PssmRenderer flushed the shadow queues + * + * @return flushQueues + */ + public boolean isFlushQueues() { + return flushQueues; + } + + /** + * Set this to false if you want to use several PssmRederers to have + * multiple shadows cast by multiple light sources. Make sure the last + * PssmRenderer in the stack DO flush the queues, but not the others + * + * @param flushQueues + */ + public void setFlushQueues(boolean flushQueues) { + this.flushQueues = flushQueues; + } + + /** + * Define the length over which the shadow will fade out when using a + * shadowZextend This is useful to make dynamic shadows fade into baked + * shadows in the distance. + * + * @param length the fade length in world units + */ + public void setShadowZFadeLength(float length) { + if (length == 0) { + fadeInfo = null; + fadeLength = 0; + postshadowMat.clearParam("FadeInfo"); + } else { + if (zFarOverride == 0) { + fadeInfo = new Vector2f(0, 0); + } else { + fadeInfo = new Vector2f(zFarOverride - length, 1.0f / length); + } + fadeLength = length; + postshadowMat.setVector2("FadeInfo", fadeInfo); + } + } + + /** + * get the length over which the shadow will fade out when using a + * shadowZextend + * + * @return the fade length in world units + */ + public float getShadowZFadeLength() { + if (fadeInfo != null) { + return zFarOverride - fadeInfo.x; + } + return 0f; + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/PssmShadowUtil.java b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowUtil.java new file mode 100644 index 000000000..888dc2fa8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowUtil.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.bounding.BoundingBox; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.GeometryList; +import static java.lang.Math.max; +import static java.lang.Math.min; + +/** + * Includes various useful shadow mapping functions. + * + * @see + *

      + * for more info. + */ +public final class PssmShadowUtil { + + /** + * 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/jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java b/jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java new file mode 100644 index 000000000..eb6e3812b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingVolume; +import com.jme3.math.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 com.jme3.util.TempVars; +import static java.lang.Math.max; +import static java.lang.Math.min; +import java.util.ArrayList; +import java.util.List; + +/** + * Includes various useful shadow mapping functions. + * + * @see for more info. + */ +public class ShadowUtil { + + /** + * Updates a points arrays with the frustum corners of the provided camera. + * + * @param viewCam + * @param points + */ + public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points) { + int w = viewCam.getWidth(); + int h = viewCam.getHeight(); + + points[0].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), 0)); + points[1].set(viewCam.getWorldCoordinates(new Vector2f(0, h), 0)); + points[2].set(viewCam.getWorldCoordinates(new Vector2f(w, h), 0)); + points[3].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), 0)); + + points[4].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), 1)); + points[5].set(viewCam.getWorldCoordinates(new Vector2f(0, h), 1)); + points[6].set(viewCam.getWorldCoordinates(new Vector2f(w, h), 1)); + points[7].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), 1)); + } + + /** + * Updates the points array to contain the frustum corners of the given + * camera. The nearOverride and farOverride variables can be used to + * override the camera's near/far values with own values. + * + * TODO: Reduce creation of new vectors + * + * @param viewCam + * @param nearOverride + * @param farOverride + */ + public static void updateFrustumPoints(Camera viewCam, + float nearOverride, + float farOverride, + float scale, + Vector3f[] points) { + + Vector3f pos = viewCam.getLocation(); + Vector3f dir = viewCam.getDirection(); + Vector3f up = viewCam.getUp(); + + float depthHeightRatio = viewCam.getFrustumTop() / viewCam.getFrustumNear(); + float near = nearOverride; + float far = farOverride; + float ftop = viewCam.getFrustumTop(); + float fright = viewCam.getFrustumRight(); + float ratio = fright / ftop; + + float near_height; + float near_width; + float far_height; + float far_width; + + if (viewCam.isParallelProjection()) { + near_height = ftop; + near_width = near_height * ratio; + far_height = ftop; + far_width = far_height * ratio; + } else { + near_height = depthHeightRatio * near; + near_width = near_height * ratio; + far_height = depthHeightRatio * far; + far_width = far_height * ratio; + } + + Vector3f right = dir.cross(up).normalizeLocal(); + + Vector3f temp = new Vector3f(); + temp.set(dir).multLocal(far).addLocal(pos); + Vector3f farCenter = temp.clone(); + temp.set(dir).multLocal(near).addLocal(pos); + Vector3f nearCenter = temp.clone(); + + Vector3f nearUp = temp.set(up).multLocal(near_height).clone(); + Vector3f farUp = temp.set(up).multLocal(far_height).clone(); + Vector3f nearRight = temp.set(right).multLocal(near_width).clone(); + Vector3f farRight = temp.set(right).multLocal(far_width).clone(); + + points[0].set(nearCenter).subtractLocal(nearUp).subtractLocal(nearRight); + points[1].set(nearCenter).addLocal(nearUp).subtractLocal(nearRight); + points[2].set(nearCenter).addLocal(nearUp).addLocal(nearRight); + points[3].set(nearCenter).subtractLocal(nearUp).addLocal(nearRight); + + points[4].set(farCenter).subtractLocal(farUp).subtractLocal(farRight); + points[5].set(farCenter).addLocal(farUp).subtractLocal(farRight); + points[6].set(farCenter).addLocal(farUp).addLocal(farRight); + points[7].set(farCenter).subtractLocal(farUp).addLocal(farRight); + + if (scale != 1.0f) { + // find center of frustum + Vector3f center = new Vector3f(); + for (int i = 0; i < 8; i++) { + center.addLocal(points[i]); + } + center.divideLocal(8f); + + Vector3f cDir = new Vector3f(); + for (int i = 0; i < 8; i++) { + cDir.set(points[i]).subtractLocal(center); + cDir.multLocal(scale - 1.0f); + points[i].addLocal(cDir); + } + } + } + + /** + * Compute bounds of a geomList + * @param list + * @param transform + * @return + */ + public static BoundingBox computeUnionBound(GeometryList list, Transform transform) { + BoundingBox bbox = new BoundingBox(); + for (int i = 0; i < list.size(); i++) { + BoundingVolume vol = list.get(i).getWorldBound(); + BoundingVolume newVol = vol.transform(transform); + //Nehon : prevent NaN and infinity values to screw the final bounding box + if (!Float.isNaN(newVol.getCenter().x) && !Float.isInfinite(newVol.getCenter().x)) { + bbox.mergeLocal(newVol); + } + } + return bbox; + } + + /** + * Compute bounds of a geomList + * @param list + * @param mat + * @return + */ + public static BoundingBox computeUnionBound(GeometryList list, Matrix4f mat) { + BoundingBox bbox = new BoundingBox(); + BoundingVolume store = null; + for (int i = 0; i < list.size(); i++) { + BoundingVolume vol = list.get(i).getWorldBound(); + store = vol.clone().transform(mat, null); + //Nehon : prevent NaN and infinity values to screw the final bounding box + if (!Float.isNaN(store.getCenter().x) && !Float.isInfinite(store.getCenter().x)) { + bbox.mergeLocal(store); + } + } + return bbox; + } + + /** + * Computes the bounds of multiple bounding volumes + * + * @param bv + * @return + */ + public static BoundingBox computeUnionBound(List bv) { + BoundingBox bbox = new BoundingBox(); + for (int i = 0; i < bv.size(); i++) { + BoundingVolume vol = bv.get(i); + bbox.mergeLocal(vol); + } + return bbox; + } + + /** + * Compute bounds from an array of points + * + * @param pts + * @param transform + * @return + */ + public static BoundingBox computeBoundForPoints(Vector3f[] pts, Transform transform) { + Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY); + Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY); + Vector3f temp = new Vector3f(); + for (int i = 0; i < pts.length; i++) { + transform.transformVector(pts[i], temp); + + min.minLocal(temp); + max.maxLocal(temp); + } + Vector3f center = min.add(max).multLocal(0.5f); + Vector3f extent = max.subtract(min).multLocal(0.5f); + return new BoundingBox(center, extent.x, extent.y, extent.z); + } + + /** + * Compute bounds from an array of points + * @param pts + * @param mat + * @return + */ + public static BoundingBox computeBoundForPoints(Vector3f[] pts, Matrix4f mat) { + Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY); + Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY); + TempVars vars = TempVars.get(); + Vector3f temp = vars.vect1; + + 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); + } + vars.release(); + Vector3f center = min.add(max).multLocal(0.5f); + Vector3f extent = max.subtract(min).multLocal(0.5f); + //Nehon 08/18/2010 : Added an offset to the extend to avoid banding artifacts when the frustum are aligned + return new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f); + } + + /** + * Updates the shadow camera to properly contain the given points (which + * contain the eye camera frustum corners) + * + * @param shadowCam + * @param points + */ + public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) { + boolean ortho = shadowCam.isParallelProjection(); + shadowCam.setProjectionMatrix(null); + + if (ortho) { + shadowCam.setFrustum(-1, 1, -1, 1, 1, -1); + } else { + shadowCam.setFrustumPerspective(45, 1, 1, 150); + } + + Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix(); + Matrix4f projMatrix = shadowCam.getProjectionMatrix(); + + BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix); + + TempVars vars = TempVars.get(); + + Vector3f splitMin = splitBB.getMin(vars.vect1); + Vector3f splitMax = splitBB.getMax(vars.vect2); + +// 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 = vars.tempMat4; + cropMatrix.set(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); + + vars.release(); + shadowCam.setProjectionMatrix(result); + } + + /** + * Updates the shadow camera to properly contain the given points (which + * contain the eye camera frustum corners) and the shadow occluder objects. + * + * @param occluders + * @param receivers + * @param shadowCam + * @param points + */ + public static void updateShadowCamera(GeometryList occluders, + GeometryList receivers, + Camera shadowCam, + Vector3f[] points, + float shadowMapSize) { + updateShadowCamera(occluders, receivers, shadowCam, points, null, shadowMapSize); + } + + /** + * Updates the shadow camera to properly contain the given points (which + * contain the eye camera frustum corners) and the shadow occluder objects. + * + * @param occluders + * @param shadowCam + * @param points + */ + public static void updateShadowCamera(GeometryList occluders, + GeometryList receivers, + Camera shadowCam, + Vector3f[] points, + GeometryList splitOccluders, + float shadowMapSize) { + + boolean ortho = shadowCam.isParallelProjection(); + + shadowCam.setProjectionMatrix(null); + + if (ortho) { + shadowCam.setFrustum(-1, 1, -1, 1, 1, -1); + } + + // create transform to rotate points to viewspace + Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix(); + + 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); + } + + TempVars vars = TempVars.get(); + + Vector3f casterMin = casterBB.getMin(vars.vect1); + Vector3f casterMax = casterBB.getMax(vars.vect2); + + Vector3f receiverMin = receiverBB.getMin(vars.vect3); + Vector3f receiverMax = receiverBB.getMax(vars.vect4); + + Vector3f splitMin = splitBB.getMin(vars.vect5); + Vector3f splitMax = splitBB.getMax(vars.vect6); + + splitMin.z = 0; + +// if (!ortho) { +// shadowCam.setFrustumPerspective(45, 1, 1, splitMax.z); +// } + + Matrix4f projMatrix = shadowCam.getProjectionMatrix(); + + Vector3f cropMin = vars.vect7; + Vector3f cropMax = vars.vect8; + + // IMPORTANT: Special handling for Z values + cropMin.x = max(max(casterMin.x, receiverMin.x), splitMin.x); + cropMax.x = min(min(casterMax.x, receiverMax.x), splitMax.x); + + cropMin.y = max(max(casterMin.y, receiverMin.y), splitMin.y); + cropMax.y = min(min(casterMax.y, receiverMax.y), splitMax.y); + + cropMin.z = min(casterMin.z, splitMin.z); + cropMax.z = min(receiverMax.z, splitMax.z); + + + // Create the crop matrix. + float scaleX, scaleY, scaleZ; + float offsetX, offsetY, offsetZ; + + scaleX = (2.0f) / (cropMax.x - cropMin.x); + scaleY = (2.0f) / (cropMax.y - cropMin.y); + + //Shadow map stabilization approximation from shaderX 7 + //from Practical Cascaded Shadow maps adapted to PSSM + //scale stabilization + float halfTextureSize = shadowMapSize * 0.5f; + + if (halfTextureSize != 0 && scaleX >0 && scaleY>0) { + float scaleQuantizer = 0.1f; + scaleX = 1.0f / FastMath.ceil(1.0f / scaleX * scaleQuantizer) * scaleQuantizer; + scaleY = 1.0f / FastMath.ceil(1.0f / scaleY * scaleQuantizer) * scaleQuantizer; + } + + offsetX = -0.5f * (cropMax.x + cropMin.x) * scaleX; + offsetY = -0.5f * (cropMax.y + cropMin.y) * scaleY; + + + //Shadow map stabilization approximation from shaderX 7 + //from Practical Cascaded Shadow maps adapted to PSSM + //offset stabilization + if (halfTextureSize != 0 && scaleX >0 && scaleY>0) { + offsetX = FastMath.ceil(offsetX * halfTextureSize) / halfTextureSize; + offsetY = FastMath.ceil(offsetY * halfTextureSize) / halfTextureSize; + } + + scaleZ = 1.0f / (cropMax.z - cropMin.z); + offsetZ = -cropMin.z * scaleZ; + + + + + Matrix4f cropMatrix = vars.tempMat4; + cropMatrix.set(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); + vars.release(); + + shadowCam.setProjectionMatrix(result); + + } + + /** + * Populates the outputGeometryList with the geometry of the + * inputGeomtryList that are in the frustum of the given camera + * + * @param inputGeometryList The list containing all geometry to check + * against the camera frustum + * @param camera the camera to check geometries against + * @param outputGeometryList the list of all geometries that are in the + * camera frustum + */ + public static void getGeometriesInCamFrustum(GeometryList inputGeometryList, + Camera camera, + GeometryList outputGeometryList) { + for (int i = 0; i < inputGeometryList.size(); i++) { + Geometry g = inputGeometryList.get(i); + int planeState = camera.getPlaneState(); + camera.setPlaneState(0); + if (camera.contains(g.getWorldBound()) != Camera.FrustumIntersect.Outside) { + outputGeometryList.add(g); + } + camera.setPlaneState(planeState); + } + + } + + /** + * Populates the outputGeometryList with the geometry of the + * inputGeomtryList that are in the radius of a light. + * The array of camera must be an array of 6 cameara initialized so they represent the light viewspace of a pointlight + * + * @param inputGeometryList The list containing all geometry to check + * against the camera frustum + * @param cameras the camera array to check geometries against + * @param outputGeometryList the list of all geometries that are in the + * camera frustum + */ + public static void getGeometriesInLightRadius(GeometryList inputGeometryList, + Camera[] cameras, + GeometryList outputGeometryList) { + for (int i = 0; i < inputGeometryList.size(); i++) { + Geometry g = inputGeometryList.get(i); + boolean inFrustum = false; + for (int j = 0; j < cameras.length && inFrustum == false; j++) { + Camera camera = cameras[j]; + int planeState = camera.getPlaneState(); + camera.setPlaneState(0); + inFrustum = camera.contains(g.getWorldBound()) != Camera.FrustumIntersect.Outside; + camera.setPlaneState(planeState); + } + if (inFrustum) { + outputGeometryList.add(g); + } + } + + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowFilter.java new file mode 100644 index 000000000..69439bb27 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowFilter.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.SpotLight; +import java.io.IOException; + +/** + * + * This Filter does basically the same as a SpotLightShadowRenderer except it + * renders the post shadow pass as a fulscreen quad pass instead of a geometry + * pass. It's mostly faster than PssmShadowRenderer as long as you have more + * than a about ten shadow recieving objects. The expense is the draw back that + * the shadow Recieve mode set on spatial is ignored. So basically all and only + * objects that render depth in the scene receive shadows. See this post for + * more details + * http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599 + * + * API is basically the same as the PssmShadowRenderer; + * + * @author Rémy Bouquet aka Nehon + */ +public class SpotLightShadowFilter extends AbstractShadowFilter { + + /** + * Creates a SpotLight Shadow Filter + * + * @param assetManager the application asset manager + * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, + * etc...) the more quality, the less fps). + */ + public SpotLightShadowFilter(AssetManager assetManager, int shadowMapSize) { + super(assetManager, shadowMapSize, new SpotLightShadowRenderer(assetManager, shadowMapSize)); + } + + /** + * return the light used to cast shadows + * + * @return the SpotLight + */ + public SpotLight getLight() { + return shadowRenderer.getLight(); + } + + /** + * Sets the light to use to cast shadows + * + * @param light a SpotLight + */ + public void setLight(SpotLight light) { + shadowRenderer.setLight(light); + } + + /** + * How far the shadows are rendered in the view + * + * @see setShadowZExtend(float zFar) + * @return shadowZExtend + */ + public float getShadowZExtend() { + return shadowRenderer.getShadowZExtend(); + } + + /** + * 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) { + shadowRenderer.setShadowZExtend(zFar); + } + + /** + * Define the length over which the shadow will fade out when using a + * shadowZextend + * + * @param length the fade length in world units + */ + public void setShadowZFadeLength(float length) { + shadowRenderer.setShadowZFadeLength(length); + } + + /** + * get the length over which the shadow will fade out when using a + * shadowZextend + * + * @return the fade length in world units + */ + public float getShadowZFadeLength() { + return shadowRenderer.getShadowZFadeLength(); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(shadowRenderer, "shadowRenderer", null); + + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + shadowRenderer = (SpotLightShadowRenderer) ic.readSavable("shadowRenderer", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java new file mode 100644 index 000000000..f257e0caa --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.SpotLight; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +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.Node; +import java.io.IOException; + +/** + * SpotLightShadowRenderer renderer use Parrallel Split Shadow Mapping technique + * (pssm)
      It splits the view frustum in several parts and compute a shadow + * map for each one.
      splits are distributed so that the closer they are from + * the camera, the smaller they are to maximize the resolution used of the + * shadow map.
      This result in a better quality shadow than standard shadow + * mapping.
      for more informations on this read this http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html
      + *

      + * @author Rémy Bouquet aka Nehon + */ +public class SpotLightShadowRenderer extends AbstractShadowRenderer { + + protected float zFarOverride = 0; + protected Camera shadowCam; + protected SpotLight light; + protected Vector3f[] points = new Vector3f[8]; + //Holding the info for fading shadows in the far distance + protected Vector2f fadeInfo; + protected float fadeLength; + + + /** + * Used for serialization use SpotLightShadowRenderer#SpotLightShadowRenderer(AssetManager assetManager, int shadowMapSize) + */ + public SpotLightShadowRenderer() { + super(); + } + + /** + * Create a SpotLightShadowRenderer This use standard shadow mapping + * + * @param assetManager the application asset manager + * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, + * etc...) the more quality, the less fps). + */ + public SpotLightShadowRenderer(AssetManager assetManager, int shadowMapSize) { + super(assetManager, shadowMapSize, 1); + init(shadowMapSize); + } + + + private void init(int shadowMapSize) { + shadowCam = new Camera(shadowMapSize, shadowMapSize); + for (int i = 0; i < points.length; i++) { + points[i] = new Vector3f(); + } + } + + /** + * return the light used to cast shadows + * + * @return the SpotLight + */ + public SpotLight getLight() { + return light; + } + + /** + * Sets the light to use to cast shadows + * + * @param light a SpotLight + */ + public void setLight(SpotLight light) { + this.light = light; + } + + @Override + protected void updateShadowCams(Camera viewCam) { + + float zFar = zFarOverride; + if (zFar == 0) { + zFar = viewCam.getFrustumFar(); + } + + //We prevent computing the frustum points and splits with zeroed or negative near clip value + float frustumNear = Math.max(viewCam.getFrustumNear(), 0.001f); + ShadowUtil.updateFrustumPoints(viewCam, frustumNear, zFar, 1.0f, points); + //shadowCam.setDirection(direction); + + shadowCam.setFrustumPerspective(light.getSpotOuterAngle() * FastMath.RAD_TO_DEG * 2.0f, 1, 1f, light.getSpotRange()); + shadowCam.getRotation().lookAt(light.getDirection(), shadowCam.getUp()); + shadowCam.setLocation(light.getPosition()); + + shadowCam.update(); + shadowCam.updateViewProjection(); + + } + + @Override + protected GeometryList getOccludersToRender(int shadowMapIndex, GeometryList sceneOccluders, GeometryList sceneReceivers, GeometryList shadowMapOccluders) { + ShadowUtil.getGeometriesInCamFrustum(sceneOccluders, shadowCam, shadowMapOccluders); + return shadowMapOccluders; + } + + @Override + GeometryList getReceivers(GeometryList sceneReceivers, GeometryList lightReceivers) { + lightReceivers.clear(); + ShadowUtil.getGeometriesInCamFrustum(sceneReceivers, shadowCam, lightReceivers); + return lightReceivers; + } + + @Override + protected Camera getShadowCam(int shadowMapIndex) { + return shadowCam; + } + + @Override + protected void doDisplayFrustumDebug(int shadowMapIndex) { + Vector3f[] points2 = points.clone(); + + ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, shadowMapIndex)); + ShadowUtil.updateFrustumPoints2(shadowCam, points2); + ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points2, shadowMapIndex)); + } + + @Override + protected void setMaterialParameters(Material material) { + } + + /** + * How far the shadows are rendered in the view + * + * @see #setShadowZExtend(float zFar) + * @return shadowZExtend + */ + public float getShadowZExtend() { + return zFarOverride; + } + + /** + * Set the distance from the eye where the shadows will be rendered default + * value is dynamicaly computed to the shadow casters/receivers union bound + * zFar, capped to view frustum far value. + * + * @param zFar the zFar values that override the computed one + */ + public void setShadowZExtend(float zFar) { + if (fadeInfo != null) { + fadeInfo.set(zFar - fadeLength, 1f / fadeLength); + } + this.zFarOverride = zFar; + + } + + /** + * Define the length over which the shadow will fade out when using a + * shadowZextend This is useful to make dynamic shadows fade into baked + * shadows in the distance. + * + * @param length the fade length in world units + */ + public void setShadowZFadeLength(float length) { + if (length == 0) { + fadeInfo = null; + fadeLength = 0; + postshadowMat.clearParam("FadeInfo"); + } else { + if (zFarOverride == 0) { + fadeInfo = new Vector2f(0, 0); + } else { + fadeInfo = new Vector2f(zFarOverride - length, 1.0f / length); + } + fadeLength = length; + postshadowMat.setVector2("FadeInfo", fadeInfo); + } + } + + /** + * get the length over which the shadow will fade out when using a + * shadowZextend + * + * @return the fade length in world units + */ + public float getShadowZFadeLength() { + if (fadeInfo != null) { + return zFarOverride - fadeInfo.x; + } + return 0f; + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = (InputCapsule) im.getCapsule(this); + zFarOverride = ic.readInt("zFarOverride", 0); + light = (SpotLight) ic.readSavable("light", null); + fadeInfo = (Vector2f) ic.readSavable("fadeInfo", null); + fadeLength = ic.readFloat("fadeLength", 0f); + init((int) shadowMapSize); + + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + oc.write(zFarOverride, "zFarOverride", 0); + oc.write(light, "light", null); + oc.write(fadeInfo, "fadeInfo", null); + oc.write(fadeLength, "fadeLength", 0f); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/system/Annotations.java b/jme3-core/src/main/java/com/jme3/system/Annotations.java new file mode 100644 index 000000000..46b5ef478 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/Annotations.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import checkers.quals.TypeQualifier; +import java.lang.annotation.*; + +/** + * This class contains the Annotation definitions for jME3. Mostly these are used + * for code error checking. + * @author normenhansen + */ +public class Annotations { + + /** + * Annotation used for math primitive fields, method parameters or method return values. + * Specifies that the primitve is read only and should not be changed. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @TypeQualifier + @Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.TYPE, ElementType.METHOD}) + public @interface ReadOnly { + } + + /** + * Annotation used for methods in math primitives that are destructive to the + * object (xxxLocal, setXXX etc.). + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @TypeQualifier + @Target({ElementType.METHOD}) + public @interface Destructive { + } + + /** + * Annotation used for public methods that are not to be called by users. + * Examples include update() methods etc. + */ + public @interface Internal { + } +} diff --git a/jme3-core/src/main/java/com/jme3/system/AppSettings.java b/jme3-core/src/main/java/com/jme3/system/AppSettings.java new file mode 100644 index 000000000..d06211671 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/AppSettings.java @@ -0,0 +1,917 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +/** + * AppSettings provides a store of configuration + * to be used by the application. + *

      + * By default only the {@link JmeContext context} uses the configuration, + * however the user may set and retrieve the settings as well. + * The settings can be stored either in the Java preferences + * (using {@link #save(java.lang.String) } or + * a .properties file (using {@link #save(java.io.OutputStream) }. + * + * @author Kirill Vainer + */ +public final class AppSettings extends HashMap { + + private static final AppSettings defaults = new AppSettings(false); + + /** + * Use LWJGL as the display system and force using the OpenGL1.1 renderer. + * + * @see AppSettings#setRenderer(java.lang.String) + */ + public static final String LWJGL_OPENGL1 = "LWJGL-OPENGL1"; + + /** + * Use LWJGL as the display system and force using the OpenGL2.0 renderer. + *

      + * If the underlying system does not support OpenGL2.0, then the context + * initialization will throw an exception. + * + * @see AppSettings#setRenderer(java.lang.String) + */ + public static final String LWJGL_OPENGL2 = "LWJGL-OpenGL2"; + + /** + * Use LWJGL as the display system and force using the core OpenGL3.3 renderer. + *

      + * If the underlying system does not support OpenGL3.2, then the context + * initialization will throw an exception. Note that currently jMonkeyEngine + * does not have any shaders that support OpenGL3.2 therefore this + * option is not useful. + *

      + * Note: OpenGL 3.2 is used to give 3.x support to Mac users. + * + * @see AppSettings#setRenderer(java.lang.String) + */ + public static final String LWJGL_OPENGL3 = "LWJGL-OpenGL3"; + + /** + * Use LWJGL as the display system and allow the context + * to choose an appropriate renderer based on system capabilities. + *

      + * If the GPU supports OpenGL2 or later, then the OpenGL2.0 renderer will + * be used, otherwise, the OpenGL1.1 renderer is used. + * + * @see AppSettings#setRenderer(java.lang.String) + */ + public static final String LWJGL_OPENGL_ANY = "LWJGL-OpenGL-Any"; + + /** + * Use the LWJGL OpenAL based renderer for audio capabilities. + * + * @see AppSettings#setAudioRenderer(java.lang.String) + */ + public static final String LWJGL_OPENAL = "LWJGL"; + + /** + * Use the Android MediaPlayer / SoundPool based renderer for Android audio capabilities. + *

      + * NOTE: Supports Android 2.2+ platforms. This is the current default for + * Android platforms. + * + * @see AppSettings#setAudioRenderer(java.lang.String) + */ + public static final String ANDROID_MEDIAPLAYER = "MediaPlayer"; + + /** + * Use the OpenAL Soft based renderer for Android audio capabilities. + *

      + * NOTE: Only to be used on Android 2.3+ platforms due to using OpenSL. + * + * @see AppSettings#setAudioRenderer(java.lang.String) + */ + public static final String ANDROID_OPENAL_SOFT = "OpenAL_SOFT"; + + static { + defaults.put("Width", 640); + defaults.put("Height", 480); + defaults.put("BitsPerPixel", 24); + defaults.put("Frequency", 60); + defaults.put("DepthBits", 24); + defaults.put("StencilBits", 0); + defaults.put("Samples", 0); + defaults.put("Fullscreen", false); + defaults.put("Title", "jMonkey Engine 3.0"); + defaults.put("Renderer", LWJGL_OPENGL2); + defaults.put("AudioRenderer", LWJGL_OPENAL); + defaults.put("DisableJoysticks", true); + defaults.put("UseInput", true); + defaults.put("VSync", false); + defaults.put("FrameRate", -1); + defaults.put("SettingsDialogImage", "/com/jme3/app/Monkey.png"); + defaults.put("MinHeight", 0); + defaults.put("MinWidth", 0); + // defaults.put("Icons", null); + } + + /** + * Create a new instance of AppSettings. + *

      + * If loadDefaults is true, then the default settings + * will be set on the AppSettings. + * Use false if you want to change some settings but you would like the + * application to load settings from previous launches. + * + * @param loadDefaults If default settings are to be loaded. + */ + public AppSettings(boolean loadDefaults) { + if (loadDefaults) { + putAll(defaults); + } + } + + /** + * Copies all settings from other to this + * AppSettings. + *

      + * Any settings that are specified in other will overwrite settings + * set on this AppSettings. + * + * @param other The AppSettings to copy the settings from + */ + public void copyFrom(AppSettings other) { + this.putAll(other); + } + + /** + * Same as {@link #copyFrom(com.jme3.system.AppSettings) }, except + * doesn't overwrite settings that are already set. + * + * @param other The AppSettings to merge the settings from + */ + public void mergeFrom(AppSettings other) { + for (String key : other.keySet()) { + if (get(key) == null) { + put(key, other.get(key)); + } + } + } + + /** + * Loads the settings from the given properties input stream. + * + * @param in The InputStream to load from + * @throws IOException If an IOException occurs + * + * @see #save(java.io.OutputStream) + */ + public void load(InputStream in) throws IOException { + Properties props = new Properties(); + props.load(in); + for (Map.Entry 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 if (key.endsWith("(float)")) { + float fVal = Float.parseFloat(val); + putFloat(key.substring(0, key.length() - 7), fVal); + } else { + throw new IOException("Cannot parse key: " + key); + } + } + } + + /** + * Saves all settings to the given properties output stream. + * + * @param out The OutputStream to write to + * @throws IOException If an IOException occurs + * + * @see #load(java.io.InputStream) + */ + public void save(OutputStream out) throws IOException { + Properties props = new Properties(); + for (Map.Entry 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 if (val instanceof Float) { + type = "(float)"; + } else { + // See the note in the AppSettings.save(String) + // method regarding object type settings. + continue; + } + props.setProperty(entry.getKey() + type, val.toString()); + } + props.store(out, "jME3 AppSettings"); + } + + /** + * Loads settings previously saved in the Java preferences. + * + * @param preferencesKey The preferencesKey previously used to save the settings. + * @throws BackingStoreException If an exception occurs with the preferences + * + * @see #save(java.lang.String) + */ + public void load(String preferencesKey) throws BackingStoreException { + Preferences prefs = Preferences.userRoot().node(preferencesKey); + String[] keys = prefs.keys(); + if (keys != null) { + for (String key : keys) { + if (key.charAt(1) == '_') { + // Try loading using new method + switch (key.charAt(0)) { + case 'I': + put(key.substring(2), prefs.getInt(key, (Integer) 0)); + break; + case 'F': + put(key.substring(2), prefs.getFloat(key, (Float) 0f)); + break; + case 'S': + put(key.substring(2), prefs.get(key, (String) null)); + break; + case 'B': + put(key.substring(2), prefs.getBoolean(key, (Boolean) false)); + break; + default: + throw new UnsupportedOperationException("Undefined setting type: " + key.charAt(0)); + } + } else { + // Use old method for compatibility with older preferences + // TODO: Remove when no longer neccessary + Object defaultValue = defaults.get(key); + if (defaultValue instanceof Integer) { + put(key, prefs.getInt(key, (Integer) defaultValue)); + } else if (defaultValue instanceof String) { + put(key, prefs.get(key, (String) defaultValue)); + } else if (defaultValue instanceof Boolean) { + put(key, prefs.getBoolean(key, (Boolean) defaultValue)); + } + } + } + } + } + + /** + * Saves settings into the Java preferences. + *

      + * On the Windows operating system, the preferences are saved in the registry + * at the following key:
      + * HKEY_CURRENT_USER\Software\JavaSoft\Prefs\[preferencesKey] + * + * @param preferencesKey The preferences key to save at. Generally the + * application's unique name. + * + * @throws BackingStoreException If an exception occurs with the preferences + */ + public void save(String preferencesKey) throws BackingStoreException { + Preferences prefs = Preferences.userRoot().node(preferencesKey); + + // Clear any previous settings set before saving, this will + // purge any other parameters set in older versions of the app, so + // that they don't leak onto the AppSettings of newer versions. + prefs.clear(); + + for (String key : keySet()) { + Object val = get(key); + if (val instanceof Integer) { + prefs.putInt("I_" + key, (Integer) val); + } else if (val instanceof Float) { + prefs.putFloat("F_" + key, (Float) val); + } else if (val instanceof String) { + prefs.put("S_" + key, (String) val); + } else if (val instanceof Boolean) { + prefs.putBoolean("B_" + key, (Boolean) val); + } + // NOTE: Ignore any parameters of unsupported types instead + // of throwing exception. This is specifically for handling + // BufferedImage which is used in setIcons(), as you do not + // want to export such data in the preferences. + } + + // Ensure the data is properly written into preferences before + // continuing. + prefs.sync(); + } + + /** + * Get an integer from the settings. + *

      + * If the key is not set, then 0 is returned. + */ + public int getInteger(String key) { + Integer i = (Integer) get(key); + if (i == null) { + return 0; + } + + return i.intValue(); + } + + /** + * Get a boolean from the settings. + *

      + * If the key is not set, then false is returned. + */ + public boolean getBoolean(String key) { + Boolean b = (Boolean) get(key); + if (b == null) { + return false; + } + + return b.booleanValue(); + } + + /** + * Get a string from the settings. + *

      + * If the key is not set, then null is returned. + */ + public String getString(String key) { + String s = (String) get(key); + if (s == null) { + return null; + } + + return s; + } + + /** + * Get a float from the settings. + *

      + * If the key is not set, then 0.0 is returned. + */ + public float getFloat(String key) { + Float f = (Float) get(key); + if (f == null) { + return 0f; + } + + return f.floatValue(); + } + + /** + * Set an integer on the settings. + */ + public void putInteger(String key, int value) { + put(key, Integer.valueOf(value)); + } + + /** + * Set a boolean on the settings. + */ + public void putBoolean(String key, boolean value) { + put(key, Boolean.valueOf(value)); + } + + /** + * Set a string on the settings. + */ + public void putString(String key, String value) { + put(key, value); + } + + /** + * Set a float on the settings. + */ + public void putFloat(String key, float value) { + put(key, Float.valueOf(value)); + } + + /** + * Enable or disable mouse emulation on touchscreen based devices. + * This will convert taps on the touchscreen or movement of finger + * over touchscreen (only the first) into the appropriate mouse events. + * + * @param emulateMouse If mouse emulation should be enabled. + */ + public void setEmulateMouse(boolean emulateMouse) { + putBoolean("TouchEmulateMouse", emulateMouse); + } + + /** + * Returns true if mouse emulation is enabled, false otherwise. + * + * @return Mouse emulation mode. + */ + public boolean isEmulateMouse() { + return getBoolean("TouchEmulateMouse"); + } + + /** + * Specify if the X or Y (or both) axes should be flipped for emulated mouse. + * + * @param flipX Set to flip X axis + * @param flipY Set to flip Y axis + * + * @see #setEmulateMouse(boolean) + */ + public void setEmulateMouseFlipAxis(boolean flipX, boolean flipY) { + putBoolean("TouchEmulateMouseFlipX", flipX); + putBoolean("TouchEmulateMouseFlipY", flipY); + } + + public boolean isEmulateMouseFlipX() { + return getBoolean("TouchEmulateMouseFlipX"); + } + + public boolean isEmulateMouseFlipY() { + return getBoolean("TouchEmulateMouseFlipY"); + } + + /** + * @param frameRate The frame-rate is the upper limit on how high + * the application's frames-per-second can go. + * (Default: -1 no frame rate limit imposed) + */ + public void setFrameRate(int frameRate) { + putInteger("FrameRate", frameRate); + } + + /** + * @param use If true, the application will initialize and use input. + * Set to false for headless applications that do not require keyboard + * or mouse input. + * (Default: true) + */ + public void setUseInput(boolean use) { + putBoolean("UseInput", use); + } + + /** + * @param use If true, the application will initialize and use joystick + * input. Set to false if no joystick input is desired. + * (Default: false) + */ + public void setUseJoysticks(boolean use) { + putBoolean("DisableJoysticks", !use); + } + + /** + * Set the graphics renderer to use, one of:
      + *

        + *
      • AppSettings.LWJGL_OPENGL1 - Force OpenGL1.1 compatability
      • + *
      • AppSettings.LWJGL_OPENGL2 - Force OpenGL2 compatability
      • + *
      • AppSettings.LWJGL_OPENGL3 - Force OpenGL3.3 compatability
      • + *
      • AppSettings.LWJGL_OPENGL_ANY - Choose an appropriate + * OpenGL version based on system capabilities
      • + *
      • null - Disable graphics rendering
      • + *
      + * @param renderer The renderer to set + * (Default: AppSettings.LWJGL_OPENGL2) + */ + public void setRenderer(String renderer) { + putString("Renderer", renderer); + } + + /** + * Set a custom graphics renderer to use. The class should implement + * the {@link JmeContext} interface. + * @param clazz The custom context class. + * (Default: not set) + */ + public void setCustomRenderer(Class clazz){ + put("Renderer", "CUSTOM" + clazz.getName()); + } + + /** + * Set the audio renderer to use. One of:
      + *
        + *
      • AppSettings.LWJGL_OPENAL - Default for LWJGL
      • + *
      • null - Disable audio
      • + *
      + * @param audioRenderer + * (Default: LWJGL) + */ + public void setAudioRenderer(String audioRenderer) { + putString("AudioRenderer", audioRenderer); + } + + /** + * @param value the width for the rendering display. + * (Default: 640) + */ + public void setWidth(int value) { + putInteger("Width", value); + } + + /** + * @param value the height for the rendering display. + * (Default: 480) + */ + public void setHeight(int value) { + putInteger("Height", value); + } + + /** + * Set the resolution for the rendering display + * @param width The width + * @param height The height + * (Default: 640x480) + */ + public void setResolution(int width, int height) { + setWidth(width); + setHeight(height); + } + + + /** + * @param value the minimum width the settings window will allow for the rendering display. + * (Default: 0) + */ + public void setMinWidth(int value) { + putInteger("MinWidth", value); + } + + /** + * @param value the minimum height the settings window will allow for the rendering display. + * (Default: 0) + */ + public void setMinHeight(int value) { + putInteger("MinHeight", value); + } + + /** + * Set the minimum resolution the settings window will allow for the rendering display + * @param width The minimum width + * @param height The minimum height + * (Default: 0x0) + */ + public void setMinResolution(int width, int height) { + setMinWidth(width); + setMinHeight(height); + } + + + + /** + * Set the frequency, also known as refresh rate, for the + * rendering display. + * @param value The frequency + * (Default: 60) + */ + public void setFrequency(int value) { + putInteger("Frequency", value); + } + + /** + * Sets the number of depth bits to use. + *

      + * The number of depth bits specifies the precision of the depth buffer. + * To increase precision, specify 32 bits. To decrease precision, specify + * 16 bits. On some platforms 24 bits might not be supported, in that case, + * specify 16 bits.

      + * (Default: 24) + * + * @param value The depth bits + */ + public void setDepthBits(int value){ + putInteger("DepthBits", value); + } + + /** + * Android Only + * Sets the number of alpha bits to use. + *

      + * The number of alpha bits specifies the precision of the surface view + * background alpha value. To set the surface view to opaque (fastest setting), + * leave the number of alpha bits = 0. This will cause faster rendering, + * but android views located behind the surface view will not be viewable. + * To set the surface view to translucent, set the number of alphaBits to 8 + * or higher. Values less than 8 (except 0) will set the surface view pixel + * format to transparent.

      + * (Default: 0) + * + * @param value The alpha bits + */ + public void setAlphaBits(int value){ + putInteger("AlphaBits", value); + } + + /** + * Set the number of stencil bits. + *

      + * This value is only relevant when the stencil buffer is being used. + * Specify 8 to indicate an 8-bit stencil buffer, specify 0 to disable + * the stencil buffer. + *

      + * (Default: 0) + * + * @param value Number of stencil bits + */ + public void setStencilBits(int value){ + putInteger("StencilBits", value); + } + + /** + * Set the bits per pixel for the display. Appropriate + * values are 16 for RGB565 color format, or 24 for RGB8 color format. + * + * @param value The bits per pixel to set + * (Default: 24) + */ + public void setBitsPerPixel(int value) { + putInteger("BitsPerPixel", value); + } + + /** + * Set the number of samples per pixel. A value of 1 indicates + * each pixel should be single-sampled, higher values indicate + * a pixel should be multi-sampled. + * + * @param value The number of samples + * (Default: 1) + */ + public void setSamples(int value) { + putInteger("Samples", value); + } + + /** + * @param title The title of the rendering display + * (Default: jMonkeyEngine 3.0) + */ + public void setTitle(String title) { + putString("Title", title); + } + + /** + * @param value true to enable full-screen rendering, false to render in a window + * (Default: false) + */ + public void setFullscreen(boolean value) { + putBoolean("Fullscreen", value); + } + + /** + * Set to true to enable vertical-synchronization, limiting and synchronizing + * every frame rendered to the monitor's refresh rate. + * @param value + * (Default: false) + */ + public void setVSync(boolean value) { + putBoolean("VSync", value); + } + + /** + * Enable 3D stereo. + *

      This feature requires hardware support from the GPU driver. + * @see http://en.wikipedia.org/wiki/Quad_buffering
      + * Once enabled, filters or scene processors that handle 3D stereo rendering + * could use this feature to render using hardware 3D stereo.

      + * (Default: false) + */ + public void setStereo3D(boolean value){ + putBoolean("Stereo3D", value); + } + + /** + * Sets the application icons to be used, with the most preferred first. + * For Windows you should supply at least one 16x16 icon and one 32x32. The former is used for the title/task bar, + * the latter for the alt-tab icon. + * Linux (and similar platforms) expect one 32x32 icon. + * Mac OS X should be supplied one 128x128 icon. + *
      + * The icon is used for the settings window, and the LWJGL render window. Not currently supported for JOGL. + * Note that a bug in Java 6 (bug ID 6445278, currently hidden but available in Google cache) currently prevents + * the icon working for alt-tab on the settings dialog in Windows. + * + * @param value An array of BufferedImages to use as icons. + * (Default: not set) + */ + public void setIcons(Object[] value) { + put("Icons", value); + } + + /** + * Sets the path of the settings dialog image to use. + *

      + * The image will be displayed in the settings dialog when the + * application is started. + *

      + * (Default: /com/jme3/app/Monkey.png) + * + * @param path The path to the image in the classpath. + */ + public void setSettingsDialogImage(String path) { + putString("SettingsDialogImage", path); + } + + /** + * Get the framerate. + * @see #setFrameRate(int) + */ + public int getFrameRate() { + return getInteger("FrameRate"); + } + + /** + * Get the use input state. + * @see #setUseInput(boolean) + */ + public boolean useInput() { + return getBoolean("UseInput"); + } + + /** + * Get the renderer + * @see #setRenderer(java.lang.String) + */ + public String getRenderer() { + return getString("Renderer"); + } + + /** + * Get the width + * @see #setWidth(int) + */ + public int getWidth() { + return getInteger("Width"); + } + + /** + * Get the height + * @see #setHeight(int) + */ + public int getHeight() { + return getInteger("Height"); + } + + /** + * Get the width + * @see #setWidth(int) + */ + public int getMinWidth() { + return getInteger("MinWidth"); + } + + /** + * Get the height + * @see #setHeight(int) + */ + public int getMinHeight() { + return getInteger("MinHeight"); + } + + /** + * Get the bits per pixel + * @see #setBitsPerPixel(int) + */ + public int getBitsPerPixel() { + return getInteger("BitsPerPixel"); + } + + /** + * Get the frequency + * @see #setFrequency(int) + */ + public int getFrequency() { + return getInteger("Frequency"); + } + + /** + * Get the number of depth bits + * @see #setDepthBits(int) + */ + public int getDepthBits() { + return getInteger("DepthBits"); + } + + /** + * Android Only + * Get the number of alpha bits for the surface view to use. + * @see #setAlphaBits(int) + */ + public int getAlphaBits() { + return getInteger("AlphaBits"); + } + + /** + * Get the number of stencil bits + * @see #setStencilBits(int) + */ + public int getStencilBits() { + return getInteger("StencilBits"); + } + + /** + * Get the number of samples + * @see #setSamples(int) + */ + public int getSamples() { + return getInteger("Samples"); + } + + /** + * Get the application title + * @see #setTitle(java.lang.String) + */ + public String getTitle() { + return getString("Title"); + } + + /** + * Get the vsync state + * @see #setVSync(boolean) + */ + public boolean isVSync() { + return getBoolean("VSync"); + } + + /** + * Get the fullscreen state + * @see #setFullscreen(boolean) + */ + public boolean isFullscreen() { + return getBoolean("Fullscreen"); + } + + /** + * Get the use joysticks state + * @see #setUseJoysticks(boolean) + */ + public boolean useJoysticks() { + return !getBoolean("DisableJoysticks"); + } + + /** + * Get the audio renderer + * @see #setAudioRenderer(java.lang.String) + */ + public String getAudioRenderer() { + return getString("AudioRenderer"); + } + + /** + * Get the stereo 3D state + * @see #setStereo3D(boolean) + */ + public boolean useStereo3D(){ + return getBoolean("Stereo3D"); + } + + /** + * Get the icon array + * @see #setIcons(java.lang.Object[]) + */ + public Object[] getIcons() { + return (Object[]) get("Icons"); + } + + /** + * Get the settings dialog image + * @see #setSettingsDialogImage(java.lang.String) + */ + public String getSettingsDialogImage() { + return getString("SettingsDialogImage"); + } +} diff --git a/jme3-core/src/main/java/com/jme3/system/JmeContext.java b/jme3-core/src/main/java/com/jme3/system/JmeContext.java new file mode 100644 index 000000000..f4f47ae67 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/JmeContext.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; +import com.jme3.renderer.Renderer; + +/** + * Represents a rendering context within the engine. + */ +public interface JmeContext { + + /** + * The type of context. + */ + public enum Type { + /** + * A display can represent a windowed or a fullscreen-exclusive display. + * If windowed, the graphics are rendered to a new on-screen surface + * enclosed in a window defined by the operating system. Implementations + * are encouraged to not use AWT or Swing to create the OpenGL display + * but rather use native operating system functions to set up a native + * display with the windowing system. + */ + Display, + + /** + * A canvas type context makes a rendering surface available as an + * AWT {@link java.awt.Canvas} object that can be embedded in a Swing/AWT + * frame. To retrieve the Canvas object, you should cast the context + * to {@link JmeCanvasContext}. + */ + Canvas, + + /** + * An 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 Touch device input implementation. May be null if not available. + */ + public TouchInput getTouchInput(); + + /** + * @return The timer for this context, or null if not created yet. + */ + public Timer getTimer(); + + /** + * Sets the title of the display (if available). This does nothing + * for fullscreen, headless, or canvas contexts. + * @param title The new title of the display. + */ + public void setTitle(String title); + + /** + * @return True if the context has been created but not yet destroyed. + */ + public boolean isCreated(); + + /** + * @return True if the context contains a valid render surface, + * if any of the rendering methods in {@link Renderer} are called + * while this is false, then the result is undefined. + */ + public boolean isRenderable(); + + /** + * @param enabled If enabled, the context will automatically flush + * frames to the video card (swap buffers) after an update cycle. + */ + public void setAutoFlushFrames(boolean enabled); + + /** + * Creates the context and makes it active. + * + * @param waitFor If true, will wait until context has initialized. + */ + public void create(boolean waitFor); + + /** + * Destroys and then re-creates the context. This should be called after + * the display settings have been changed. + */ + public void restart(); + + /** + * Destroys the context completely, making it inactive. + * + * @param waitFor If true, will wait until the context is destroyed fully. + */ + public void destroy(boolean waitFor); + +} diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystem.java b/jme3-core/src/main/java/com/jme3/system/JmeSystem.java new file mode 100644 index 000000000..b0b4ce729 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystem.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import com.jme3.asset.AssetManager; +import com.jme3.audio.AudioRenderer; +import com.jme3.input.SoftTextDialogInput; +import com.jme3.texture.Image; +import com.jme3.texture.image.ImageRaster; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class JmeSystem { + + private static final Logger logger = Logger.getLogger(JmeSystem.class.getName()); + public static enum StorageFolderType { + Internal, + External, + } + + private static JmeSystemDelegate systemDelegate; + + public static void setSystemDelegate(JmeSystemDelegate systemDelegate) { + JmeSystem.systemDelegate = systemDelegate; + } + + public static synchronized File getStorageFolder() { + return getStorageFolder(StorageFolderType.External); + } + + public static synchronized File getStorageFolder(StorageFolderType type) { + checkDelegate(); + return systemDelegate.getStorageFolder(type); + } + + public static String getFullName() { + checkDelegate(); + return systemDelegate.getFullName(); + } + + public static InputStream getResourceAsStream(String name) { + checkDelegate(); + return systemDelegate.getResourceAsStream(name); + } + + public static URL getResource(String name) { + checkDelegate(); + return systemDelegate.getResource(name); + } + + public static boolean trackDirectMemory() { + checkDelegate(); + return systemDelegate.trackDirectMemory(); + } + + public static void setLowPermissions(boolean lowPerm) { + checkDelegate(); + systemDelegate.setLowPermissions(lowPerm); + } + + public static boolean isLowPermissions() { + checkDelegate(); + return systemDelegate.isLowPermissions(); + } + + public static void setSoftTextDialogInput(SoftTextDialogInput input) { + checkDelegate(); + systemDelegate.setSoftTextDialogInput(input); + } + + public static SoftTextDialogInput getSoftTextDialogInput() { + checkDelegate(); + return systemDelegate.getSoftTextDialogInput(); + } + + public static void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException { + checkDelegate(); + systemDelegate.writeImageFile(outStream, format, imageData, width, height); + } + + public static AssetManager newAssetManager(URL configFile) { + checkDelegate(); + return systemDelegate.newAssetManager(configFile); + } + + public static AssetManager newAssetManager() { + checkDelegate(); + return systemDelegate.newAssetManager(); + } + + public static boolean showSettingsDialog(AppSettings sourceSettings, final boolean loadFromRegistry) { + checkDelegate(); + return systemDelegate.showSettingsDialog(sourceSettings, loadFromRegistry); + } + + public static Platform getPlatform() { + checkDelegate(); + return systemDelegate.getPlatform(); + } + + public static JmeContext newContext(AppSettings settings, JmeContext.Type contextType) { + checkDelegate(); + return systemDelegate.newContext(settings, contextType); + } + + public static AudioRenderer newAudioRenderer(AppSettings settings) { + checkDelegate(); + return systemDelegate.newAudioRenderer(settings); + } + + public static ImageRaster createImageRaster(Image image, int slice) { + checkDelegate(); + return systemDelegate.createImageRaster(image, slice); + } + + /** + * Displays an error message to the user in whichever way the context + * feels is appropriate. If this is a headless or an offscreen surface + * context, this method should do nothing. + * + * @param message The error message to display. May contain new line + * characters. + */ + public static void showErrorDialog(String message){ + checkDelegate(); + systemDelegate.showErrorDialog(message); + } + + public static void initialize(AppSettings settings) { + checkDelegate(); + systemDelegate.initialize(settings); + } + + private static JmeSystemDelegate tryLoadDelegate(String className) throws InstantiationException, IllegalAccessException { + try { + return (JmeSystemDelegate) Class.forName(className).newInstance(); + } catch (ClassNotFoundException ex) { + return null; + } + } + + @SuppressWarnings("unchecked") + private static void checkDelegate() { + if (systemDelegate == null) { + try { + systemDelegate = tryLoadDelegate("com.jme3.system.JmeDesktopSystem"); + if (systemDelegate == null) { + systemDelegate = tryLoadDelegate("com.jme3.system.android.JmeAndroidSystem"); + if (systemDelegate == null) { + systemDelegate = tryLoadDelegate("com.jme3.system.ios.JmeIosSystem"); + if (systemDelegate == null) { + // None of the system delegates were found .. + Logger.getLogger(JmeSystem.class.getName()).log(Level.SEVERE, + "Failed to find a JmeSystem delegate!\n" + + "Ensure either desktop or android jME3 jar is in the classpath."); + } + } + } + } catch (InstantiationException ex) { + Logger.getLogger(JmeSystem.class.getName()).log(Level.SEVERE, "Failed to create JmeSystem delegate:\n{0}", ex); + } catch (IllegalAccessException ex) { + Logger.getLogger(JmeSystem.class.getName()).log(Level.SEVERE, "Failed to create JmeSystem delegate:\n{0}", ex); + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java new file mode 100644 index 000000000..136ff152e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import com.jme3.asset.AssetManager; +import com.jme3.audio.AudioRenderer; +import com.jme3.input.SoftTextDialogInput; +import com.jme3.texture.Image; +import com.jme3.texture.image.ImageRaster; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.nio.ByteBuffer; +import java.util.EnumMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author Kirill Vainer, normenhansen + */ +public abstract class JmeSystemDelegate { + + protected final Logger logger = Logger.getLogger(JmeSystem.class.getName()); + protected boolean initialized = false; + protected boolean lowPermissions = false; + protected Map storageFolders = new EnumMap(JmeSystem.StorageFolderType.class); + protected SoftTextDialogInput softTextDialogInput = null; + + public synchronized File getStorageFolder(JmeSystem.StorageFolderType type) { + File storageFolder = null; + + switch (type) { + // Internal and External are currently the same folder + case Internal: + case External: + if (lowPermissions) { + throw new UnsupportedOperationException("File system access restricted"); + } + storageFolder = storageFolders.get(type); + if (storageFolder == null) { + // Initialize storage folder + storageFolder = new File(System.getProperty("user.home"), ".jme3"); + if (!storageFolder.exists()) { + storageFolder.mkdir(); + } + storageFolders.put(type, storageFolder); + } + break; + default: + break; + } + if (storageFolder != null) { + logger.log(Level.FINE, "Storage Folder Path: {0}", storageFolder.getAbsolutePath()); + } else { + logger.log(Level.FINE, "Storage Folder not found!"); + } + return storageFolder; + } + + public String getFullName() { + return JmeVersion.getFullName(); + } + + public InputStream getResourceAsStream(String name) { + return this.getClass().getResourceAsStream(name); + } + + public URL getResource(String name) { + return this.getClass().getResource(name); + } + + public boolean trackDirectMemory() { + return false; + } + + public void setLowPermissions(boolean lowPerm) { + lowPermissions = lowPerm; + } + + public boolean isLowPermissions() { + return lowPermissions; + } + + public void setSoftTextDialogInput(SoftTextDialogInput input) { + softTextDialogInput = input; + } + public SoftTextDialogInput getSoftTextDialogInput() { + return softTextDialogInput; + } + + public abstract void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException; + + public abstract AssetManager newAssetManager(URL configFile); + + public abstract AssetManager newAssetManager(); + + public abstract void showErrorDialog(String message); + + public abstract boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry); + + private boolean is64Bit(String arch) { + if (arch.equals("x86")) { + return false; + } else if (arch.equals("amd64")) { + return true; + } else if (arch.equals("x86_64")) { + return true; + } else if (arch.equals("ppc") || arch.equals("PowerPC")) { + return false; + } else if (arch.equals("ppc64")) { + return true; + } else if (arch.equals("i386") || arch.equals("i686")) { + return false; + } else if (arch.equals("universal")) { + return false; + } else if (arch.equals("arm")) { + return false; + } else { + throw new UnsupportedOperationException("Unsupported architecture: " + arch); + } + } + + public Platform getPlatform() { + String os = System.getProperty("os.name").toLowerCase(); + String arch = System.getProperty("os.arch").toLowerCase(); + boolean is64 = is64Bit(arch); + if (os.contains("windows")) { + return is64 ? Platform.Windows64 : Platform.Windows32; + } else if (os.contains("linux") || os.contains("freebsd") || os.contains("sunos")) { + return is64 ? Platform.Linux64 : Platform.Linux32; + } else if (os.contains("mac os x") || os.contains("darwin")) { + if (arch.startsWith("ppc")) { + return is64 ? Platform.MacOSX_PPC64 : Platform.MacOSX_PPC32; + } else { + return is64 ? Platform.MacOSX64 : Platform.MacOSX32; + } + } else { + throw new UnsupportedOperationException("The specified platform: " + os + " is not supported."); + } + } + + public abstract JmeContext newContext(AppSettings settings, JmeContext.Type contextType); + + public abstract AudioRenderer newAudioRenderer(AppSettings settings); + + public abstract void initialize(AppSettings settings); + + public abstract ImageRaster createImageRaster(Image image, int slice); +} diff --git a/jme3-core/src/main/java/com/jme3/system/JmeVersion.java b/jme3-core/src/main/java/com/jme3/system/JmeVersion.java new file mode 100644 index 000000000..98cfbc354 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/JmeVersion.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +public class JmeVersion { + private static final String FULL_NAME = "jMonkeyEngine 3.x"; + + public static String getFullName() { + return FULL_NAME; + } +} diff --git a/jme3-core/src/main/java/com/jme3/system/NanoTimer.java b/jme3-core/src/main/java/com/jme3/system/NanoTimer.java new file mode 100644 index 000000000..e0a4d8b46 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/NanoTimer.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +/** + * 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/jme3-core/src/main/java/com/jme3/system/NullContext.java b/jme3-core/src/main/java/com/jme3/system/NullContext.java new file mode 100644 index 000000000..f7d4b3d5a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/NullContext.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; +import com.jme3.input.dummy.DummyKeyInput; +import com.jme3.input.dummy.DummyMouseInput; +import com.jme3.renderer.Renderer; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class NullContext implements JmeContext, Runnable { + + protected static final Logger logger = Logger.getLogger(NullContext.class.getName()); + + protected AtomicBoolean created = new AtomicBoolean(false); + protected AtomicBoolean needClose = new AtomicBoolean(false); + protected final Object createdLock = new Object(); + + protected int frameRate; + protected AppSettings settings = new AppSettings(true); + protected Timer timer; + protected SystemListener listener; + protected NullRenderer renderer; + + public Type getType() { + return Type.Headless; + } + + public void setSystemListener(SystemListener listener){ + this.listener = listener; + } + + protected void initInThread(){ + logger.fine("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.fine("NullContext destroyed."); + } + + public void destroy(boolean waitFor){ + needClose.set(true); + if (waitFor) + waitFor(false); + } + + public void create(boolean waitFor){ + if (created.get()){ + logger.warning("create() called when NullContext is already created!"); + return; + } + + new Thread(this, "Headless Application Thread").start(); + if (waitFor) + waitFor(true); + } + + public void restart() { + } + + public void setAutoFlushFrames(boolean enabled){ + } + + public MouseInput getMouseInput() { + return new DummyMouseInput(); + } + + public KeyInput getKeyInput() { + return new DummyKeyInput(); + } + + public JoyInput getJoyInput() { + return null; + } + + public TouchInput getTouchInput() { + return null; + } + + public void setTitle(String title) { + } + + public void create(){ + create(false); + } + + public void destroy(){ + destroy(false); + } + + protected void waitFor(boolean createdVal){ + synchronized (createdLock){ + while (created.get() != createdVal){ + try { + createdLock.wait(); + } catch (InterruptedException ex) { + } + } + } + } + + public boolean isCreated(){ + return created.get(); + } + + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + frameRate = settings.getFrameRate(); + if (frameRate <= 0) + frameRate = 60; // use default update rate. + } + + public AppSettings getSettings(){ + return settings; + } + + public Renderer getRenderer() { + return renderer; + } + + public Timer getTimer() { + return timer; + } + + public boolean isRenderable() { + return true; // Doesn't really matter if true or false. Either way + // RenderManager won't render anything. + } +} diff --git a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java new file mode 100644 index 000000000..3bd21b17d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import java.nio.ByteBuffer; +import java.util.EnumSet; + +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; + +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 invalidateState(){ + } + + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + } + + public void setBackgroundColor(ColorRGBA color) { + } + + public void applyRenderState(RenderState state) { + } + + public void setDepthRange(float start, float end) { + } + + public void onFrame() { + } + + public void setWorldMatrix(Matrix4f worldMatrix) { + } + + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { + } + + public void setViewPort(int x, int y, int width, int height) { + } + + public void setClipRect(int x, int y, int width, int height) { + } + + public void clearClipRect() { + } + + public void setLighting(LightList lights) { + } + + public void setShader(Shader shader) { + } + + public void deleteShader(Shader shader) { + } + + public void deleteShaderSource(ShaderSource source) { + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { + } + + public void setMainFrameBufferOverride(FrameBuffer fb) { + } + + public void setFrameBuffer(FrameBuffer fb) { + } + + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { + } + + public void deleteFrameBuffer(FrameBuffer fb) { + } + + public void setTexture(int unit, Texture tex) { + } + + public void modifyTexture(Texture tex, Image pixels, int x, int y) { + } + + 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/jme3-core/src/main/java/com/jme3/system/Platform.java b/jme3-core/src/main/java/com/jme3/system/Platform.java new file mode 100644 index 000000000..b964b5a50 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/Platform.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +public enum Platform { + + /** + * Microsoft Windows 32 bit + */ + Windows32, + + /** + * Microsoft Windows 64 bit + */ + Windows64, + + /** + * Linux 32 bit + */ + Linux32, + + /** + * Linux 64 bit + */ + Linux64, + + /** + * Apple Mac OS X 32 bit + */ + MacOSX32, + + /** + * Apple Mac OS X 64 bit + */ + MacOSX64, + + /** + * Apple Mac OS X 32 bit PowerPC + */ + MacOSX_PPC32, + + /** + * Apple Mac OS X 64 bit PowerPC + */ + MacOSX_PPC64, + + /** + * Android ARM5 + */ + Android_ARM5, + + /** + * Android ARM6 + */ + Android_ARM6, + + /** + * Android ARM7 + */ + Android_ARM7, + + /** + * Android x86 + */ + Android_X86, + + iOS_X86, + + iOS_ARM; + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/system/SystemListener.java b/jme3-core/src/main/java/com/jme3/system/SystemListener.java new file mode 100644 index 000000000..a54817382 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/SystemListener.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +/** + * 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/jme3-core/src/main/java/com/jme3/system/Timer.java b/jme3-core/src/main/java/com/jme3/system/Timer.java new file mode 100644 index 000000000..554399522 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/Timer.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java new file mode 100644 index 000000000..bb5317b34 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture; + +import com.jme3.renderer.Caps; +import com.jme3.renderer.Renderer; +import com.jme3.texture.Image.Format; +import com.jme3.util.NativeObject; +import java.util.ArrayList; + +/** + *

      + * FrameBuffers are rendering surfaces allowing + * off-screen rendering and render-to-texture functionality. + * Instead of the scene rendering to the screen, it is rendered into the + * FrameBuffer, the result can be either a texture or a buffer. + *

      + * A FrameBuffer supports two methods of rendering, + * using a {@link Texture} or using a buffer. + * When using a texture, the result of the rendering will be rendered + * onto the texture, after which the texture can be placed on an object + * and rendered as if the texture was uploaded from disk. + * When using a buffer, the result is rendered onto + * a buffer located on the GPU, the data of this buffer is not accessible + * to the user. buffers are useful if one + * wishes to retrieve only the color content of the scene, but still desires + * depth testing (which requires a depth buffer). + * Buffers can be copied to other framebuffers + * including the main screen, by using + * {@link Renderer#copyFrameBuffer(com.jme3.texture.FrameBuffer, com.jme3.texture.FrameBuffer) }. + * The content of a {@link RenderBuffer} can be retrieved by using + * {@link Renderer#readFrameBuffer(com.jme3.texture.FrameBuffer, java.nio.ByteBuffer) }. + *

      + * FrameBuffers have several attachment points, there are + * several color attachment points and a single depth + * attachment point. + * The color attachment points support image formats such as + * {@link Format#RGBA8}, allowing rendering the color content of the scene. + * The depth attachment point requires a depth image format. + * + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + * + * @author Kirill Vainer + */ +public class FrameBuffer extends NativeObject { + + private int width = 0; + private int height = 0; + private int samples = 1; + private ArrayList colorBufs = new ArrayList(); + private RenderBuffer depthBuf = null; + private int colorBufIndex = 0; + + /** + * RenderBuffer represents either a texture or a + * buffer that will be rendered to. RenderBuffers + * are attached to an attachment slot on a FrameBuffer. + */ + public class RenderBuffer { + + Texture tex; + Image.Format format; + int id = -1; + int slot = -1; + int face = -1; + + /** + * @return The image format of the render buffer. + */ + public Format getFormat() { + return format; + } + + /** + * @return The texture to render to for this RenderBuffer + * or null if content should be rendered into a buffer. + */ + public Texture getTexture(){ + return tex; + } + + /** + * Do not use. + */ + public int getId() { + return id; + } + + /** + * Do not use. + */ + public void setId(int id){ + this.id = id; + } + + /** + * Do not use. + */ + public int getSlot() { + return slot; + } + + public int getFace() { + return face; + } + + 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 + "]"; + } + } + } + + /** + *

      + * Creates a new FrameBuffer with the given width, height, and number + * of samples. If any textures are attached to this FrameBuffer, then + * they must have the same number of samples as given in this constructor. + *

      + * Note that if the {@link Renderer} does not expose the + * {@link Caps#NonPowerOfTwoTextures}, then an exception will be thrown + * if the width and height arguments are not power of two. + * + * @param width The width to use + * @param height The height to use + * @param samples The number of samples to use for a multisampled + * framebuffer, or 1 if the framebuffer should be singlesampled. + * + * @throws IllegalArgumentException If width or height are not positive. + */ + public FrameBuffer(int width, int height, int samples){ + super(); + 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(src.id); + /* + for (RenderBuffer renderBuf : src.colorBufs){ + RenderBuffer clone = renderBuf.createDestructableClone(); + if (clone != null) + this.colorBufs.add(clone); + } + + this.depthBuf = src.depthBuf.createDestructableClone(); + */ + } + + /** + * Enables the use of a depth buffer for this FrameBuffer. + * + * @param format The format to use for the depth buffer. + * @throws IllegalArgumentException If format is not a depth format. + */ + public void setDepthBuffer(Image.Format format){ + if (id != -1) + throw new UnsupportedOperationException("FrameBuffer already initialized."); + + if (!format.isDepthFormat()) + throw new IllegalArgumentException("Depth buffer format must be depth."); + + depthBuf = new RenderBuffer(); + depthBuf.slot = -100; // -100 == special slot for DEPTH_BUFFER + depthBuf.format = format; + } + + /** + * Enables the use of a color buffer for this FrameBuffer. + * + * @param format The format to use for the color buffer. + * @throws IllegalArgumentException If format is not a color format. + */ + public void setColorBuffer(Image.Format format){ + if (id != -1) + throw new UnsupportedOperationException("FrameBuffer already initialized."); + + if (format.isDepthFormat()) + throw new IllegalArgumentException("Color buffer format must be color/luminance."); + + RenderBuffer colorBuf = new RenderBuffer(); + colorBuf.slot = 0; + colorBuf.format = format; + + colorBufs.clear(); + colorBufs.add(colorBuf); + } + + private void checkSetTexture(Texture tex, boolean depth){ + Image img = tex.getImage(); + if (img == null) + throw new IllegalArgumentException("Texture not initialized with RTT."); + + if (depth && !img.getFormat().isDepthFormat()) + throw new IllegalArgumentException("Texture image format must be depth."); + else if (!depth && img.getFormat().isDepthFormat()) + throw new IllegalArgumentException("Texture image format must be color/luminance."); + + // check that resolution matches texture resolution + if (width != img.getWidth() || height != img.getHeight()) + throw new IllegalArgumentException("Texture image resolution " + + "must match FB resolution"); + + if (samples != tex.getImage().getMultiSamples()) + throw new IllegalStateException("Texture samples must match framebuffer samples"); + } + + /** + * If enabled, any shaders rendering into this FrameBuffer + * will be able to write several results into the renderbuffers + * by using the gl_FragData array. Every slot in that + * array maps into a color buffer attached to this framebuffer. + * + * @param enabled True to enable MRT (multiple rendering targets). + */ + public void setMultiTarget(boolean enabled){ + if (enabled) colorBufIndex = -1; + else colorBufIndex = 0; + } + + /** + * @return True if MRT (multiple rendering targets) is enabled. + * @see FrameBuffer#setMultiTarget(boolean) + */ + public boolean isMultiTarget(){ + return colorBufIndex == -1; + } + + /** + * If MRT is not enabled ({@link FrameBuffer#setMultiTarget(boolean) } is false) + * then this specifies the color target to which the scene should be rendered. + *

      + * By default the value is 0. + * + * @param index The color attachment index. + * @throws IllegalArgumentException If index is negative or doesn't map + * to any attachment on this framebuffer. + */ + public void setTargetIndex(int index){ + if (index < 0 || index >= 16) + throw new IllegalArgumentException("Target index must be between 0 and 16"); + + if (colorBufs.size() < index) + throw new IllegalArgumentException("The target at " + index + " is not set!"); + + colorBufIndex = index; + setUpdateNeeded(); + } + + /** + * @return The color target to which the scene should be rendered. + * + * @see FrameBuffer#setTargetIndex(int) + */ + public int getTargetIndex(){ + return colorBufIndex; + } + + /** + * Set the color texture to use for this framebuffer. + * This automatically clears all existing textures added previously + * with {@link FrameBuffer#addColorTexture } and adds this texture as the + * only target. + * + * @param tex The color texture to set. + */ + public void setColorTexture(Texture2D tex){ + clearColorTargets(); + addColorTexture(tex); + } + + /** + * Set the color texture to use for this framebuffer. + * This automatically clears all existing textures added previously + * with {@link FrameBuffer#addColorTexture } and adds this texture as the + * only target. + * + * @param tex The cube-map texture to set. + * @param face The face of the cube-map to render to. + */ + public void setColorTexture(TextureCubeMap tex, TextureCubeMap.Face face) { + clearColorTargets(); + addColorTexture(tex, face); + } + + /** + * Clears all color targets that were set or added previously. + */ + public void clearColorTargets(){ + colorBufs.clear(); + } + + /** + * Add a color texture to use for this framebuffer. + * If MRT is enabled, then each subsequently added texture can be + * rendered to through a shader that writes to the array gl_FragData. + * If MRT is not enabled, then the index set with {@link FrameBuffer#setTargetIndex(int) } + * is rendered to by the shader. + * + * @param tex The texture to add. + */ + public void addColorTexture(Texture2D tex) { + if (id != -1) + throw new UnsupportedOperationException("FrameBuffer already initialized."); + + Image img = tex.getImage(); + checkSetTexture(tex, false); + + RenderBuffer colorBuf = new RenderBuffer(); + colorBuf.slot = colorBufs.size(); + colorBuf.tex = tex; + colorBuf.format = img.getFormat(); + + colorBufs.add(colorBuf); + } + + /** + * Add a color texture to use for this framebuffer. + * If MRT is enabled, then each subsequently added texture can be + * rendered to through a shader that writes to the array gl_FragData. + * If MRT is not enabled, then the index set with {@link FrameBuffer#setTargetIndex(int) } + * is rendered to by the shader. + * + * @param tex The cube-map texture to add. + * @param face The face of the cube-map to render to. + */ + public void addColorTexture(TextureCubeMap tex, TextureCubeMap.Face face) { + 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(); + colorBuf.face = face.ordinal(); + + colorBufs.add(colorBuf); + } + + /** + * Set the depth texture to use for this framebuffer. + * + * @param tex The color texture to set. + */ + public void setDepthTexture(Texture2D tex){ + if (id != -1) + throw new UnsupportedOperationException("FrameBuffer already initialized."); + + Image img = tex.getImage(); + checkSetTexture(tex, true); + + depthBuf = new RenderBuffer(); + depthBuf.slot = -100; // indicates GL_DEPTH_ATTACHMENT + depthBuf.tex = tex; + depthBuf.format = img.getFormat(); + } + + /** + * @return The number of color buffers attached to this texture. + */ + public int getNumColorBuffers(){ + return colorBufs.size(); + } + + /** + * @param index + * @return The color buffer at the given index. + */ + public RenderBuffer getColorBuffer(int index){ + return colorBufs.get(index); + } + + /** + * @return The first color buffer attached to this FrameBuffer, or null + * if no color buffers are attached. + */ + public RenderBuffer getColorBuffer() { + if (colorBufs.isEmpty()) + return null; + + return colorBufs.get(0); + } + + /** + * @return The depth buffer attached to this FrameBuffer, or null + * if no depth buffer is attached + */ + public RenderBuffer getDepthBuffer() { + return depthBuf; + } + + /** + * @return The height in pixels of this framebuffer. + */ + public int getHeight() { + return height; + } + + /** + * @return The width in pixels of this framebuffer. + */ + public int getWidth() { + return width; + } + + /** + * @return The number of samples when using a multisample framebuffer, or + * 1 if this is a singlesampled framebuffer. + */ + public int getSamples() { + return samples; + } + + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + String mrtStr = colorBufIndex >= 0 ? "" + colorBufIndex : "mrt"; + sb.append("FrameBuffer[format=").append(width).append("x").append(height) + .append("x").append(samples).append(", drawBuf=").append(mrtStr).append("]\n"); + if (depthBuf != null) + sb.append("Depth => ").append(depthBuf).append("\n"); + for (RenderBuffer colorBuf : colorBufs){ + sb.append("Color(").append(colorBuf.slot) + .append(") => ").append(colorBuf).append("\n"); + } + return sb.toString(); + } + + @Override + public void resetObject() { + this.id = -1; + + for (int i = 0; i < colorBufs.size(); i++) { + colorBufs.get(i).resetObject(); + } + + if (depthBuf != null) + depthBuf.resetObject(); + + setUpdateNeeded(); + } + + @Override + public void deleteObject(Object rendererObject) { + ((Renderer)rendererObject).deleteFrameBuffer(this); + } + + public NativeObject createDestructableClone(){ + return new FrameBuffer(this); + } + + @Override + public long getUniqueId() { + return ((long)OBJTYPE_FRAMEBUFFER << 32) | ((long)id); + } +} diff --git a/jme3-core/src/main/java/com/jme3/texture/Image.java b/jme3-core/src/main/java/com/jme3/texture/Image.java new file mode 100644 index 000000000..855ab392b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -0,0 +1,874 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture; + +import com.jme3.export.*; +import com.jme3.renderer.Caps; +import com.jme3.renderer.Renderer; +import com.jme3.util.BufferUtils; +import com.jme3.util.NativeObject; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 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 NativeObject implements Savable /*, Cloneable*/ { + + public enum Format { + /** + * 8-bit alpha + */ + Alpha8(8), + + /** + * 16-bit alpha + */ + Alpha16(16), + + /** + * 8-bit grayscale/luminance. + */ + Luminance8(8), + + /** + * 16-bit grayscale/luminance. + */ + Luminance16(16), + + /** + * half-precision floating-point grayscale/luminance. + */ + Luminance16F(16,true), + + /** + * single-precision floating-point grayscale/luminance. + */ + Luminance32F(32,true), + + /** + * 8-bit luminance/grayscale and 8-bit alpha. + */ + Luminance8Alpha8(16), + + /** + * 16-bit luminance/grayscale and 16-bit alpha. + */ + Luminance16Alpha16(32), + + /** + * half-precision floating-point grayscale/luminance and alpha. + */ + Luminance16FAlpha16F(32,true), + + @Deprecated + Intensity8(8), + + @Deprecated + Intensity16(16), + + /** + * 8-bit blue, green, and red. + */ + BGR8(24), // BGR and ABGR formats are often used on windows systems + + /** + * 8-bit red, green, and blue. + */ + RGB8(24), + + @Deprecated + RGB10(30), + + /** + * 16-bit red, green, and blue. + */ + RGB16(48), + + /** + * 5-bit red, 6-bit green, and 5-bit blue. + */ + RGB565(16), + + /** + * 4-bit alpha, red, green, and blue. Used on Android only. + */ + ARGB4444(16), + + /** + * 5-bit red, green, and blue with 1-bit alpha. + */ + RGB5A1(16), + + /** + * 8-bit red, green, blue, and alpha. + */ + RGBA8(32), + + /** + * 8-bit alpha, blue, green, and red. + */ + ABGR8(32), + + /** + * 16-bit red, green, blue and alpha + */ + RGBA16(64), + + /** + * S3TC compression DXT1. + * Called BC1 in DirectX10. + */ + DXT1(4,false,true, false), + + /** + * S3TC compression DXT1 with 1-bit alpha. + */ + DXT1A(4,false,true, false), + + /** + * S3TC compression DXT3 with 4-bit alpha. + * Called BC2 in DirectX10. + */ + DXT3(8,false,true, false), + + /** + * S3TC compression DXT5 with interpolated 8-bit alpha. + * Called BC3 in DirectX10. + */ + DXT5(8,false,true, false), + + /** + * Luminance-Alpha Texture Compression. + * Called BC5 in DirectX10. + */ + LATC(8, false, true, false), + + /** + * Arbitrary depth format. The precision is chosen by the video + * hardware. + */ + Depth(0,true,false,false), + + /** + * 16-bit depth. + */ + Depth16(16,true,false,false), + + /** + * 24-bit depth. + */ + Depth24(24,true,false,false), + + /** + * 32-bit depth. + */ + Depth32(32,true,false,false), + + /** + * single-precision floating point depth. + */ + Depth32F(32,true,false,true), + + /** + * Texture data is stored as {@link Format#RGB16F} in system memory, + * but will be converted to {@link Format#RGB111110F} when sent + * to the video hardware. + */ + RGB16F_to_RGB111110F(48,true), + + /** + * unsigned floating-point red, green and blue that uses 32 bits. + */ + RGB111110F(32,true), + + /** + * Texture data is stored as {@link Format#RGB16F} in system memory, + * but will be converted to {@link Format#RGB9E5} when sent + * to the video hardware. + */ + RGB16F_to_RGB9E5(48,true), + + /** + * 9-bit red, green and blue with 5-bit exponent. + */ + RGB9E5(32,true), + + /** + * half-precision floating point red, green, and blue. + */ + RGB16F(48,true), + + /** + * half-precision floating point red, green, blue, and alpha. + */ + RGBA16F(64,true), + + /** + * single-precision floating point red, green, and blue. + */ + RGB32F(96,true), + + /** + * single-precision floating point red, green, blue and alpha. + */ + RGBA32F(128,true), + + /** + * Luminance/grayscale texture compression. + * Called BC4 in DirectX10. + */ + LTC(4, false, true, false), + + /** + * 24-bit depth with 8-bit stencil. + * Check the cap {@link Caps#PackedDepthStencilBuffer}. + */ + Depth24Stencil8(32, true, false, false); + + private int bpp; + private boolean isDepth; + private boolean isCompressed; + private boolean isFloatingPoint; + + private Format(int bpp){ + this.bpp = bpp; + } + + private Format(int bpp, boolean isFP){ + this(bpp); + this.isFloatingPoint = isFP; + } + + private Format(int bpp, boolean isDepth, boolean isCompressed, boolean isFP){ + this(bpp, isFP); + this.isDepth = isDepth; + this.isCompressed = isCompressed; + } + + /** + * @return bits per pixel. + */ + public int getBitsPerPixel(){ + return bpp; + } + + /** + * @return True if this format is a depth format, false otherwise. + */ + public boolean isDepthFormat(){ + return isDepth; + } + + /** + * @return True if this is a compressed image format, false if + * uncompressed. + */ + public boolean isCompressed() { + return isCompressed; + } + + /** + * @return True if this image format is in floating point, + * false if it is an integer format. + */ + public boolean isFloatingPont(){ + return isFloatingPoint; + } + + } + + // image attributes + protected Format format; + protected int width, height, depth; + protected int[] mipMapSizes; + protected ArrayList data; + protected transient Object efficientData; + protected int multiSamples = 1; +// protected int mipOffset = 0; + + // attributes relating to GL object + protected boolean mipsWereGenerated = false; + protected boolean needGeneratedMips = false; + + /** + * Internal use only. + * The renderer marks which images have generated mipmaps in VRAM + * and which do not, so it can generate them as needed. + * + * @param generated If mipmaps were generated or not. + */ + public void setMipmapsGenerated(boolean generated) { + this.mipsWereGenerated = generated; + } + + /** + * Internal use only. + * Check if the renderer has generated mipmaps for this image in VRAM + * or not. + * + * @return If mipmaps were generated already. + */ + public boolean isMipmapsGenerated() { + return mipsWereGenerated; + } + + /** + * (Package private) Called by {@link Texture} when + * {@link #isMipmapsGenerated() } is false in order to generate + * mipmaps for this image. + */ + void setNeedGeneratedMipmaps() { + needGeneratedMips = true; + } + + /** + * @return True if the image needs to have mipmaps generated + * for it (as requested by the texture). + */ + public boolean isGeneratedMipmapsRequired() { + return needGeneratedMips; + } + + @Override + public void resetObject() { + this.id = -1; + this.mipsWereGenerated = false; + setUpdateNeeded(); + } + + @Override + protected void deleteNativeBuffers() { + for (ByteBuffer buf : data) { + BufferUtils.destroyDirectBuffer(buf); + } + } + + @Override + public void deleteObject(Object rendererObject) { + ((Renderer)rendererObject).deleteImage(this); + } + + @Override + public NativeObject createDestructableClone() { + return new Image(id); + } + + @Override + public long getUniqueId() { + return ((long)OBJTYPE_TEXTURE << 32) | ((long)id); + } + + /** + * @return A shallow clone of this image. The data is not cloned. + */ + @Override + public Image clone(){ + Image clone = (Image) super.clone(); + clone.mipMapSizes = mipMapSizes != null ? mipMapSizes.clone() : null; + clone.data = data != null ? new ArrayList(data) : null; + clone.setUpdateNeeded(); + return clone; + } + + /** + * Constructor instantiates a new Image object. All values + * are undefined. + */ + public Image() { + super(); + data = new ArrayList(1); + } + + protected Image(int id){ + super(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; + } else { + needGeneratedMips = false; + mipsWereGenerated = true; + } + + 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; + } else { + needGeneratedMips = false; + mipsWereGenerated = true; + } + + 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 format + * the data format of the image. + * @param width + * the width of the image. + * @param height + * the height of the image. + * @param data + * the image data. + */ + public Image(Format format, int width, int height, int depth, ArrayList data) { + this(format, width, height, depth, data, null); + } + + /** + * 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. + */ + 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 efficient data representation of this image. + *

      + * Some system implementations are more efficient at operating + * on data other than ByteBuffers, in that case, this method can be used. + * + * @param efficientData + */ + public void setEfficentData(Object efficientData){ + this.efficientData = efficientData; + setUpdateNeeded(); + } + + /** + * @return The efficient data representation of this image. + * @see Image#setEfficentData(java.lang.Object) + */ + public Object getEfficentData(){ + return efficientData; + } + + /** + * Sets the mipmap sizes stored in this image's data buffer. Mipmaps are + * stored sequentially, and the first mipmap is the main image data. To + * specify no mipmaps, pass null and this will automatically be expanded + * into a single mipmap of the full + * + * @param mipMapSizes + * the mipmap sizes array, or null for a single image map. + */ + public void setMipMapSizes(int[] mipMapSizes) { + if (mipMapSizes != null && mipMapSizes.length <= 1) + mipMapSizes = null; + + this.mipMapSizes = mipMapSizes; + + if (mipMapSizes != null) { + needGeneratedMips = false; + mipsWereGenerated = false; + } else { + needGeneratedMips = false; + mipsWereGenerated = true; + } + + 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; + } + + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + sb.append(getClass().getSimpleName()); + sb.append("[size=").append(width).append("x").append(height); + + if (depth > 1) + sb.append("x").append(depth); + + sb.append(", format=").append(format.name()); + + if (hasMipmaps()) + sb.append(", mips"); + + if (getId() >= 0) + sb.append(", id=").append(id); + + sb.append("]"); + + return sb.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof Image)) { + return false; + } + Image that = (Image) other; + if (this.getFormat() != that.getFormat()) + return false; + if (this.getWidth() != that.getWidth()) + return false; + if (this.getHeight() != that.getHeight()) + return false; + if (this.getData() != null && !this.getData().equals(that.getData())) + return false; + if (this.getData() == null && that.getData() != null) + return false; + if (this.getMipMapSizes() != null + && !Arrays.equals(this.getMipMapSizes(), that.getMipMapSizes())) + return false; + if (this.getMipMapSizes() == null && that.getMipMapSizes() != null) + return false; + if (this.getMultiSamples() != that.getMultiSamples()) + return false; + + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 97 * hash + (this.format != null ? this.format.hashCode() : 0); + hash = 97 * hash + this.width; + hash = 97 * hash + this.height; + hash = 97 * hash + this.depth; + hash = 97 * hash + Arrays.hashCode(this.mipMapSizes); + hash = 97 * hash + (this.data != null ? this.data.hashCode() : 0); + hash = 97 * hash + this.multiSamples; + return hash; + } + + public void write(JmeExporter e) throws IOException { + OutputCapsule capsule = e.getCapsule(this); + capsule.write(format, "format", Format.RGBA8); + capsule.write(width, "width", 0); + capsule.write(height, "height", 0); + capsule.write(depth, "depth", 0); + capsule.write(mipMapSizes, "mipMapSizes", null); + capsule.write(multiSamples, "multiSamples", 1); + capsule.writeByteBufferArrayList(data, "data", null); + } + + public void read(JmeImporter e) throws IOException { + InputCapsule capsule = e.getCapsule(this); + format = capsule.readEnum("format", Format.class, Format.RGBA8); + width = capsule.readInt("width", 0); + height = capsule.readInt("height", 0); + depth = capsule.readInt("depth", 0); + mipMapSizes = capsule.readIntArray("mipMapSizes", null); + multiSamples = capsule.readInt("multiSamples", 1); + data = (ArrayList) capsule.readByteBufferArrayList("data", null); + + if (mipMapSizes != null) { + needGeneratedMips = false; + mipsWereGenerated = true; + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/texture/Texture.java b/jme3-core/src/main/java/com/jme3/texture/Texture.java new file mode 100644 index 000000000..925f49df7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/Texture.java @@ -0,0 +1,634 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture; + +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.asset.CloneableSmartAsset; +import com.jme3.asset.TextureKey; +import com.jme3.export.*; +import com.jme3.util.PlaceholderAssets; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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, environment - None. + * + * @see com.jme3.texture.Image + * @author Mark Powell + * @author Joshua Slack + * @version $Id: Texture.java 4131 2009-03-19 20:15:28Z blaine.dev $ + */ +public abstract class Texture implements CloneableSmartAsset, 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 boolean needCompareModeUpdate = false; + private int anisotropicFilter; + + /** + * @return A cloned Texture object. + */ + @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; + if (minificationFilter.usesMipMapLevels() && image != null && !image.isGeneratedMipmapsRequired()) { + image.setNeedGeneratedMipmaps(); + } + } + + /** + * @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; + needCompareModeUpdate = true; + } + + /** + * 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; + + // Test if mipmap generation required. + setMinFilter(getMinFilter()); + } + + /** + * @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; + } + + /** + * 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() { + StringBuilder sb = new StringBuilder(); + sb.append(getClass().getSimpleName()); + sb.append("[name=").append(name); + if (image != null) { + sb.append(", image=").append(image.toString()); + } + + sb.append("]"); + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Texture other = (Texture) obj; + + // NOTE: Since images are generally considered unique assets in jME3, + // using the image's equals() implementation is not neccessary here + // (would be too slow) + if (this.image != other.image) { + return false; + } + if (this.minificationFilter != other.minificationFilter) { + return false; + } + if (this.magnificationFilter != other.magnificationFilter) { + return false; + } + if (this.shadowCompareMode != other.shadowCompareMode) { + return false; + } + if (this.anisotropicFilter != other.anisotropicFilter) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 5; + // NOTE: Since images are generally considered unique assets in jME3, + // using the image's hashCode() implementation is not neccessary here + // (would be too slow) + hash = 67 * hash + (this.image != null ? System.identityHashCode(this.image) : 0); + hash = 67 * hash + (this.minificationFilter != null ? this.minificationFilter.hashCode() : 0); + hash = 67 * hash + (this.magnificationFilter != null ? this.magnificationFilter.hashCode() : 0); + hash = 67 * hash + (this.shadowCompareMode != null ? this.shadowCompareMode.hashCode() : 0); + hash = 67 * hash + this.anisotropicFilter; + return hash; + } + + /** Retrieve a basic clone of this Texture (ie, clone everything but the + * image data, which is shared) + * + * @return Texture + * + * @deprecated Use {@link Texture#clone()} instead. + */ + @Deprecated + public Texture createSimpleClone(Texture rVal) { + rVal.setMinFilter(minificationFilter); + rVal.setMagFilter(magnificationFilter); + rVal.setShadowCompareMode(shadowCompareMode); + rVal.setAnisotropicFilter(anisotropicFilter); + rVal.setImage(image); // NOT CLONED. + rVal.setKey(key); + rVal.setName(name); + return rVal; + } + + /** + * @deprecated Use {@link Texture#clone()} instead. + */ + @Deprecated + public abstract Texture createSimpleClone(); + + public void write(JmeExporter e) throws IOException { + OutputCapsule capsule = e.getCapsule(this); + capsule.write(name, "name", null); + + if (key == null){ + // no texture key is set, try to save image instead then + capsule.write(image, "image", null); + }else{ + capsule.write(key, "key", null); + } + + capsule.write(anisotropicFilter, "anisotropicFilter", 1); + capsule.write(minificationFilter, "minificationFilter", + MinFilter.BilinearNoMipMaps); + capsule.write(magnificationFilter, "magnificationFilter", + MagFilter.Bilinear); + } + + public void read(JmeImporter e) throws IOException { + InputCapsule capsule = e.getCapsule(this); + name = capsule.readString("name", null); + key = (TextureKey) capsule.readSavable("key", null); + + // load texture from key, if available + if (key != null) { + // key is available, so try the texture from there. + try { + Texture loadedTex = e.getAssetManager().loadTexture(key); + image = loadedTex.getImage(); + } catch (AssetNotFoundException ex){ + Logger.getLogger(Texture.class.getName()).log(Level.SEVERE, "Cannot locate texture {0}", key); + image = PlaceholderAssets.getPlaceholderImage(); + } + }else{ + // no key is set on the texture. Attempt to load an embedded image + image = (Image) capsule.readSavable("image", null); + if (image == null){ + // TODO: what to print out here? the texture has no key or data, there's no useful information .. + // assume texture.name is set even though the key is null + Logger.getLogger(Texture.class.getName()).log(Level.SEVERE, "Cannot load embedded image {0}", toString() ); + } + } + + anisotropicFilter = capsule.readInt("anisotropicFilter", 1); + minificationFilter = capsule.readEnum("minificationFilter", + MinFilter.class, + MinFilter.BilinearNoMipMaps); + magnificationFilter = capsule.readEnum("magnificationFilter", + MagFilter.class, MagFilter.Bilinear); + } + + public boolean isNeedCompareModeUpdate() { + return needCompareModeUpdate; + } + + public void compareModeUpdated() { + this.needCompareModeUpdate = false; + } + + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/texture/Texture2D.java b/jme3-core/src/main/java/com/jme3/texture/Texture2D.java new file mode 100644 index 000000000..2533d2ce0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/Texture2D.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import java.io.IOException; + +/** + * @author Joshua Slack + */ +public class Texture2D extends Texture { + + private WrapMode wrapS = WrapMode.EdgeClamp; + private WrapMode wrapT = WrapMode.EdgeClamp; + + /** + * Creates a new two-dimensional texture with default attributes. + */ + public Texture2D(){ + super(); + } + + /** + * Creates a new two-dimensional texture using the given image. + * @param img The image to use. + */ + public Texture2D(Image img){ + super(); + setImage(img); + if (img.getFormat().isDepthFormat()){ + setMagFilter(MagFilter.Nearest); + setMinFilter(MinFilter.NearestNoMipMaps); + } + } + + /** + * Creates a new two-dimensional texture for the purpose of offscreen + * rendering. + * + * @see com.jme3.texture.FrameBuffer + * + * @param width + * @param height + * @param format + */ + public Texture2D(int width, int height, Image.Format format){ + this(new Image(format, width, height, null)); + } + + /** + * Creates a new two-dimensional texture for the purpose of offscreen + * rendering. + * + * @see com.jme3.texture.FrameBuffer + * + * @param width + * @param height + * @param format + * @param numSamples + */ + public Texture2D(int width, int height, int numSamples, Image.Format format){ + this(new Image(format, width, height, null)); + getImage().setMultiSamples(numSamples); + } + + @Override + public Texture createSimpleClone() { + Texture2D clone = new Texture2D(); + createSimpleClone(clone); + return clone; + } + + @Override + public Texture createSimpleClone(Texture rVal) { + rVal.setWrap(WrapAxis.S, wrapS); + rVal.setWrap(WrapAxis.T, wrapT); + return super.createSimpleClone(rVal); + } + + /** + * 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; + default: + throw new IllegalArgumentException("Not applicable for 2D textures"); + } + } + + /** + * 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; + default: + throw new IllegalArgumentException("invalid WrapAxis: " + axis); + } + } + + @Override + public Type getType() { + return Type.TwoDimensional; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Texture2D)) { + return false; + } + Texture2D that = (Texture2D) other; + if (this.getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) + return false; + if (this.getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T)) + return false; + return super.equals(other); + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 79 * hash + (this.wrapS != null ? this.wrapS.hashCode() : 0); + hash = 79 * hash + (this.wrapT != null ? this.wrapT.hashCode() : 0); + return hash; + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(wrapS, "wrapS", WrapMode.EdgeClamp); + capsule.write(wrapT, "wrapT", WrapMode.EdgeClamp); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp); + wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/texture/Texture3D.java b/jme3-core/src/main/java/com/jme3/texture/Texture3D.java new file mode 100644 index 000000000..106351ece --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/Texture3D.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import java.io.IOException; + +/** + * @author Maarten Steur + */ +public class Texture3D extends Texture { + + private WrapMode wrapS = WrapMode.EdgeClamp; + private WrapMode wrapT = WrapMode.EdgeClamp; + private WrapMode wrapR = WrapMode.EdgeClamp; + + /** + * Creates a new two-dimensional texture with default attributes. + */ + public Texture3D() { + super(); + } + + /** + * Creates a new three-dimensional texture using the given image. + * @param img The image to use. + */ + public Texture3D(Image img) { + super(); + setImage(img); + if (img.getFormat().isDepthFormat()) { + setMagFilter(MagFilter.Nearest); + setMinFilter(MinFilter.NearestNoMipMaps); + } + } + + /** + * Creates a new three-dimensional texture for the purpose of offscreen + * rendering. + * + * @see com.jme3.texture.FrameBuffer + * + * @param width + * @param height + * @param depth + * @param format + */ + public Texture3D(int width, int height, int depth, Image.Format format) { + this(new Image(format, width, height, depth, null)); + } + + /** + * Creates a new three-dimensional texture for the purpose of offscreen + * rendering. + * + * @see com.jme3.texture.FrameBuffer + * + * @param width + * @param height + * @param format + * @param numSamples + */ + public Texture3D(int width, int height, int depth, int numSamples, Image.Format format) { + this(new Image(format, width, height, depth, null)); + getImage().setMultiSamples(numSamples); + } + + @Override + public Texture createSimpleClone() { + Texture3D clone = new Texture3D(); + createSimpleClone(clone); + return clone; + } + + @Override + public Texture createSimpleClone(Texture rVal) { + rVal.setWrap(WrapAxis.S, wrapS); + rVal.setWrap(WrapAxis.T, wrapT); + rVal.setWrap(WrapAxis.R, wrapR); + return super.createSimpleClone(rVal); + } + + /** + * 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.ThreeDimensional; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Texture3D)) { + return false; + } + Texture3D that = (Texture3D) other; + if (this.getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) { + return false; + } + if (this.getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T)) { + return false; + } + if (this.getWrap(WrapAxis.R) != that.getWrap(WrapAxis.R)) { + return false; + } + return super.equals(other); + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(wrapS, "wrapS", WrapMode.EdgeClamp); + capsule.write(wrapT, "wrapT", WrapMode.EdgeClamp); + capsule.write(wrapR, "wrapR", WrapMode.EdgeClamp); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp); + wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp); + wrapR = capsule.readEnum("wrapR", WrapMode.class, WrapMode.EdgeClamp); + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/texture/TextureArray.java b/jme3-core/src/main/java/com/jme3/texture/TextureArray.java new file mode 100644 index 000000000..32cf376dd --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/TextureArray.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture; + +import com.jme3.texture.Image.Format; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class implements a Texture array + * warning, this feature is only supported on opengl 3.0 version. + * To check if a hardware supports TextureArray check : + * renderManager.getRenderer().getCaps().contains(Caps.TextureArray) + * @author phate666 + */ +public class TextureArray extends Texture { + + private WrapMode wrapS = WrapMode.EdgeClamp; + private WrapMode wrapT = WrapMode.EdgeClamp; + + /** + * Construct a TextureArray + * warning, this feature is only supported on opengl 3.0 version. + * To check if a hardware supports TextureArray check : + * renderManager.getRenderer().getCaps().contains(Caps.TextureArray) + */ + public TextureArray() { + super(); + } + + /** + * Construct a TextureArray from the given list of images. + * To check if a hardware supports TextureArray check : + * renderManager.getRenderer().getCaps().contains(Caps.TextureArray) + * @param images + */ + public TextureArray(List images) { + super(); + + int width = images.get(0).getWidth(); + int height = images.get(0).getHeight(); + Format format = images.get(0).getFormat(); + Image arrayImage = new Image(format, width, height, null); + + for (Image img : images) { + if (img.getHeight() != height || img.getWidth() != width) { + throw new IllegalArgumentException("Images in texture array must have same dimensions"); + } + if (img.getFormat() != format) { + throw new IllegalArgumentException("Images in texture array must have same format"); + } + + arrayImage.addData(img.getData(0)); + } + setImage(arrayImage); + } + + @Override + public Texture createSimpleClone() { + TextureArray clone = new TextureArray(); + createSimpleClone(clone); + return clone; + } + + @Override + public Texture createSimpleClone(Texture rVal) { + rVal.setWrap(WrapAxis.S, wrapS); + rVal.setWrap(WrapAxis.T, wrapT); + return super.createSimpleClone(rVal); + } + + @Override + public Type getType() { + return Type.TwoDimensionalArray; + } + + @Override + public WrapMode getWrap(WrapAxis axis) { + switch (axis) { + case S: + return wrapS; + case T: + return wrapT; + default: + throw new IllegalArgumentException("invalid WrapAxis: " + axis); + } + } + + @Override + public void setWrap(WrapAxis axis, WrapMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode can not be null."); + } else if (axis == null) { + throw new IllegalArgumentException("axis can not be null."); + } + switch (axis) { + case S: + this.wrapS = mode; + break; + case T: + this.wrapT = mode; + break; + default: + throw new IllegalArgumentException("Not applicable for 2D textures"); + } + } + + @Override + public void setWrap(WrapMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode can not be null."); + } + this.wrapS = mode; + this.wrapT = mode; + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/texture/TextureCubeMap.java b/jme3-core/src/main/java/com/jme3/texture/TextureCubeMap.java new file mode 100644 index 000000000..22debeeb1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/TextureCubeMap.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * 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 TextureCubeMap(int width, int height, Image.Format format){ + this(createEmptyLayeredImage(width, height, 6, format)); + } + + private static Image createEmptyLayeredImage(int width, int height, + int layerCount, Image.Format format) { + ArrayList layers = new ArrayList(); + for(int i = 0; i < layerCount; i++) { + layers.add(null); + } + Image image = new Image(format, width, height, 0, layers); + return image; + } + + 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 int hashCode() { + int hash = super.hashCode(); + hash = 53 * hash + (this.wrapS != null ? this.wrapS.hashCode() : 0); + hash = 53 * hash + (this.wrapT != null ? this.wrapT.hashCode() : 0); + hash = 53 * hash + (this.wrapR != null ? this.wrapR.hashCode() : 0); + return hash; + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(wrapS, "wrapS", WrapMode.EdgeClamp); + capsule.write(wrapT, "wrapT", WrapMode.EdgeClamp); + capsule.write(wrapR, "wrapR", WrapMode.EdgeClamp); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp); + wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp); + wrapR = capsule.readEnum("wrapR", WrapMode.class, WrapMode.EdgeClamp); + } +} diff --git a/jme3-core/src/main/java/com/jme3/texture/TextureProcessor.java b/jme3-core/src/main/java/com/jme3/texture/TextureProcessor.java new file mode 100644 index 000000000..0ade0f72b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/TextureProcessor.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture; + +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetProcessor; +import com.jme3.asset.TextureKey; +import java.nio.ByteBuffer; + +public class TextureProcessor implements AssetProcessor { + + public Object postProcess(AssetKey key, Object obj) { + TextureKey texKey = (TextureKey) key; + Image img = (Image) obj; + if (img == null) { + return null; + } + + Texture tex; + if (texKey.isAsCube()) { + if (texKey.isFlipY()) { + // also flip -y and +y image in cubemap + ByteBuffer pos_y = img.getData(2); + img.setData(2, img.getData(3)); + img.setData(3, pos_y); + } + tex = new TextureCubeMap(); + } else if (texKey.isAsTexture3D()) { + tex = new Texture3D(); + } else { + tex = new Texture2D(); + } + + // enable mipmaps if image has them + // or generate them if requested by user + if (img.hasMipmaps() || texKey.isGenerateMips()) { + tex.setMinFilter(Texture.MinFilter.Trilinear); + } + + tex.setAnisotropicFilter(texKey.getAnisotropy()); + tex.setName(texKey.getName()); + tex.setImage(img); + return tex; + } + + public Object createClone(Object obj) { + Texture tex = (Texture) obj; + return tex.clone(); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/texture/image/BitMaskImageCodec.java b/jme3-core/src/main/java/com/jme3/texture/image/BitMaskImageCodec.java new file mode 100644 index 000000000..a888dfa41 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/image/BitMaskImageCodec.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.image; + +import java.nio.ByteBuffer; + +class BitMaskImageCodec extends ImageCodec { + + // Shifts + final int as, rs, gs, bs; + boolean be = false; + + public BitMaskImageCodec(int bpp, int flags, int ac, int rc, int gc, int bc, int as, int rs, int gs, int bs) { + super(bpp, flags, + (int) (((long) 1 << ac) - 1), + (int) (((long) 1 << rc) - 1), + (int) (((long) 1 << gc) - 1), + (int) (((long) 1 << bc) - 1)); + + if (bpp > 4) { + throw new UnsupportedOperationException("Use ByteAlignedImageCodec for codecs with pixel sizes larger than 4 bytes"); + } + + this.as = as; + this.rs = rs; + this.gs = gs; + this.bs = bs; + } + + private static int readPixelRaw(ByteBuffer buf, int idx, int bpp) { + //idx += bpp; + //int original = buf.get(--idx) & 0xff; + //while ((--bpp) > 0) { + // original = (original << 8) | (buf.get(--idx) & 0xff); + //} + //return original; + //return buf.getInt(idx) & (0xFFFFFFFF >>> (32 - bpp)); + int pixel = 0; + buf.position(idx); + for (int i = 0; i < bpp; i++) { + pixel = pixel | (buf.get() & 0xff) << (i * 8); + } + return pixel; + } + + private void writePixelRaw(ByteBuffer buf, int idx, int pixel, int bpp){ +// buf.position(idx); +// if (!be){ + // This works: +// while ((--bpp) >= 0){ +// byte bt = (byte) ((pixel >> (bpp * 8)) & 0xff); +// buf.put(idx + bpp, bt); +// } + // == +// } else { +// for (int i = bpp - 1; i >= 0; i--) { +// byte bt = (byte) ((pixel >> (i * 8)) & 0xff); +// buf.put(idx + i, bt); +// } +// } + + buf.position(idx); + for (int i = 0; i < bpp; i++) { + buf.put( (byte)((pixel >> (8 * i)) & 0xff) ); + } + } + + @Override + public void readComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp) { + int inputPixel = readPixelRaw(buf, (x + y * width) * bpp, bpp); + components[0] = (inputPixel >> as) & maxAlpha; + components[1] = (inputPixel >> rs) & maxRed; + components[2] = (inputPixel >> gs) & maxGreen; + components[3] = (inputPixel >> bs) & maxBlue; + } + + public void writeComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp) { + // Shift components then mask them + // Map all components into a single bitspace + int outputPixel = ((components[0] & maxAlpha) << as) + | ((components[1] & maxRed) << rs) + | ((components[2] & maxGreen) << gs) + | ((components[3] & maxBlue) << bs); + + // Find index in image where to write pixel. + // Write the resultant bitspace into the pixel. + writePixelRaw(buf, (x + y * width) * bpp, outputPixel, bpp); + } +} diff --git a/jme3-core/src/main/java/com/jme3/texture/image/ByteAlignedImageCodec.java b/jme3-core/src/main/java/com/jme3/texture/image/ByteAlignedImageCodec.java new file mode 100644 index 000000000..490a44014 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/image/ByteAlignedImageCodec.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.image; + +import java.nio.ByteBuffer; + +class ByteAlignedImageCodec extends ImageCodec { + + private final int ap, az, rp, rz, gp, gz, bp, bz; + boolean be; + + public ByteAlignedImageCodec(int bpp, int flags, int az, int rz, int gz, int bz, int ap, int rp, int gp, int bp) { + // Cast to long to compute max vals, since some components could be as high as 32 bits. + super(bpp, flags, + (int)(((long)1 << (az << 3)) - 1), + (int)(((long)1 << (rz << 3)) - 1), + (int)(((long)1 << (gz << 3)) - 1), + (int)(((long)1 << (bz << 3)) - 1)); + + this.ap = ap; + this.az = az; + this.rp = rp; + this.rz = rz; + + this.gp = gp; + this.gz = gz; + this.bp = bp; + this.bz = bz; + } + + private static void readPixelRaw(ByteBuffer buf, int idx, int bpp, byte[] result) { + buf.position(idx); + buf.get(result, 0, bpp); + } + + private static void writePixelRaw(ByteBuffer buf, int idx, byte[] pixel, int bpp) { +// try { + buf.position(idx); + buf.put(pixel, 0, bpp); +// } catch (IndexOutOfBoundsException ex) { +// System.out.println("!"); +// } + } + + private static int readComponent(byte[] encoded, int position, int size) { +// int component = encoded[position] & 0xff; +// while ((--size) > 0){ +// component = (component << 8) | (encoded[++position] & 0xff); +// } +// return component; + try { + int component = 0; + for (int i = size - 1; i >= 0; i--) { + component = (component << 8) | (encoded[position + i] & 0xff); + } + return component; +// position += size - 1; +// +// while ((--size) >= 0) { +// component = (component << 8) | (encoded[position--] & 0xff); +// } +// return component; + } catch (ArrayIndexOutOfBoundsException ex){ + ex.printStackTrace(); + return 0; + } + } + + private void writeComponent(int component, int position, int size, byte[] result) { +// if (!be) { +// while ((--size) >= 0){ +// byte bt = (byte) ((component >> (size * 8)) & 0xff); +// result[position++] = bt; +// } +// } else { + for (int i = 0; i < size; i++) { + byte bt = (byte) ((component >> (i * 8)) & 0xff); + result[position++] = bt; + } +// } + } + + public void readComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp) { + readPixelRaw(buf, (x + y * width) * bpp, bpp, tmp); + components[0] = readComponent(tmp, ap, az); + components[1] = readComponent(tmp, rp, rz); + components[2] = readComponent(tmp, gp, gz); + components[3] = readComponent(tmp, bp, bz); + } + + public void writeComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp) { + writeComponent(components[0], ap, az, tmp); + writeComponent(components[1], rp, rz, tmp); + writeComponent(components[2], gp, gz, tmp); + writeComponent(components[3], bp, bz, tmp); + writePixelRaw(buf, (x + y * width) * bpp, tmp, bpp); + } +} diff --git a/jme3-core/src/main/java/com/jme3/texture/image/ByteOffsetImageCodec.java b/jme3-core/src/main/java/com/jme3/texture/image/ByteOffsetImageCodec.java new file mode 100644 index 000000000..908bcdf0b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/image/ByteOffsetImageCodec.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.image; + +import java.nio.ByteBuffer; + +public class ByteOffsetImageCodec extends ImageCodec { + + private int redPos, greenPos, bluePos, alphaPos; + + public ByteOffsetImageCodec(int bpp, int flags, int alphaPos, int redPos, int greenPos, int bluePos) { + super(bpp, flags, alphaPos != -1 ? 255 : 0, + redPos != -1 ? 255 : 0, + greenPos != -1 ? 255 : 0, + bluePos != -1 ? 255 : 0); + this.alphaPos = alphaPos; + this.redPos = redPos; + this.greenPos = greenPos; + this.bluePos = bluePos; + } + + @Override + public void readComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp) { + int i = (y * width + x) * bpp; + buf.position(i); + buf.get(tmp, 0, bpp); + if (alphaPos != -1) { + components[0] = tmp[alphaPos] & 0xff; + } + if (redPos != -1) { + components[1] = tmp[redPos] & 0xff; + } + if (greenPos != -1) { + components[2] = tmp[greenPos] & 0xff; + } + if (bluePos != -1) { + components[3] = tmp[bluePos] & 0xff; + } + } + + @Override + public void writeComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp) { + int i = (y * width + x) * bpp; + if (alphaPos != -1) { + tmp[alphaPos] = (byte) components[0]; + } + if (redPos != -1) { + tmp[redPos] = (byte) components[1]; + } + if (greenPos != -1) { + tmp[greenPos] = (byte) components[2]; + } + if (bluePos != -1) { + tmp[bluePos] = (byte) components[3]; + } + buf.position(i); + buf.put(tmp, 0, bpp); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java b/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java new file mode 100644 index 000000000..32fc99eda --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.image; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.texture.Image; +import java.nio.ByteBuffer; + +public class DefaultImageRaster extends ImageRaster { + + private final int[] components = new int[4]; + private ByteBuffer buffer; + private final Image image; + private final ImageCodec codec; + private final int width; + private final int height; + private final byte[] temp; + private int slice; + + private void rangeCheck(int x, int y) { + if (x < 0 || y < 0 || x >= width || y >= height) { + throw new IllegalArgumentException("x and y must be inside the image dimensions"); + } + } + + public DefaultImageRaster(Image image, int slice) { + this.image = image; + this.slice = slice; + this.buffer = image.getData(slice); + this.codec = ImageCodec.lookup(image.getFormat()); + this.width = image.getWidth(); + this.height = image.getHeight(); + if (codec instanceof ByteAlignedImageCodec || codec instanceof ByteOffsetImageCodec) { + this.temp = new byte[codec.bpp]; + } else { + this.temp = null; + } + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public void setPixel(int x, int y, ColorRGBA color) { + rangeCheck(x, y); + + // Check flags for grayscale + if (codec.isGray) { + float gray = color.r * 0.27f + color.g * 0.67f + color.b * 0.06f; + color = new ColorRGBA(gray, gray, gray, color.a); + } + + switch (codec.type) { + case ImageCodec.FLAG_F16: + components[0] = (int) FastMath.convertFloatToHalf(color.a); + components[1] = (int) FastMath.convertFloatToHalf(color.r); + components[2] = (int) FastMath.convertFloatToHalf(color.g); + components[3] = (int) FastMath.convertFloatToHalf(color.b); + break; + case ImageCodec.FLAG_F32: + components[0] = (int) Float.floatToIntBits(color.a); + components[1] = (int) Float.floatToIntBits(color.r); + components[2] = (int) Float.floatToIntBits(color.g); + components[3] = (int) Float.floatToIntBits(color.b); + break; + case 0: + // Convert color to bits by multiplying by size + components[0] = Math.min( (int) (color.a * codec.maxAlpha + 0.5f), codec.maxAlpha); + components[1] = Math.min( (int) (color.r * codec.maxRed + 0.5f), codec.maxRed); + components[2] = Math.min( (int) (color.g * codec.maxGreen + 0.5f), codec.maxGreen); + components[3] = Math.min( (int) (color.b * codec.maxBlue + 0.5f), codec.maxBlue); + break; + } + codec.writeComponents(getBuffer(), x, y, width, components, temp); + image.setUpdateNeeded(); + } + + private ByteBuffer getBuffer(){ + if(buffer == null){ + this.buffer = image.getData(slice); + } + return buffer; + } + + @Override + public ColorRGBA getPixel(int x, int y, ColorRGBA store) { + rangeCheck(x, y); + + codec.readComponents(getBuffer(), x, y, width, components, temp); + if (store == null) { + store = new ColorRGBA(); + } + switch (codec.type) { + case ImageCodec.FLAG_F16: + store.set(FastMath.convertHalfToFloat((short)components[1]), + FastMath.convertHalfToFloat((short)components[2]), + FastMath.convertHalfToFloat((short)components[3]), + FastMath.convertHalfToFloat((short)components[0])); + break; + case ImageCodec.FLAG_F32: + store.set(Float.intBitsToFloat((int)components[1]), + Float.intBitsToFloat((int)components[2]), + Float.intBitsToFloat((int)components[3]), + Float.intBitsToFloat((int)components[0])); + break; + case 0: + // Convert to float and divide by bitsize to get into range 0.0 - 1.0. + store.set((float)components[1] / codec.maxRed, + (float)components[2] / codec.maxGreen, + (float)components[3] / codec.maxBlue, + (float)components[0] / codec.maxAlpha); + break; + } + if (codec.isGray) { + store.g = store.b = store.r; + } else { + if (codec.maxRed == 0) { + store.r = 1; + } + if (codec.maxGreen == 0) { + store.g = 1; + } + if (codec.maxBlue == 0) { + store.b = 1; + } + if (codec.maxAlpha == 0) { + store.a = 1; + } + } + return store; + } +} diff --git a/jme3-core/src/main/java/com/jme3/texture/image/ImageCodec.java b/jme3-core/src/main/java/com/jme3/texture/image/ImageCodec.java new file mode 100644 index 000000000..504e2be39 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/image/ImageCodec.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.image; + +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import java.nio.ByteBuffer; +import java.util.EnumMap; + +abstract class ImageCodec { + + public static final int FLAG_F16 = 1, FLAG_F32 = 2, FLAG_GRAY = 4; //, FLAG_ALPHAONLY = 8, FLAG_SHAREDEXP = 16; + private static final EnumMap params = new EnumMap(Image.Format.class); + + protected final int bpp, type, maxAlpha, maxRed, maxGreen, maxBlue; + protected final boolean isGray; + + public ImageCodec(int bpp, int flags, int maxAlpha, int maxRed, int maxGreen, int maxBlue) { + this.bpp = bpp; + this.isGray = (flags & FLAG_GRAY) != 0; + this.type = flags & ~FLAG_GRAY; + this.maxAlpha = maxAlpha; + this.maxRed = maxRed; + this.maxGreen = maxGreen; + this.maxBlue = maxBlue; + } + + static { + // == ALPHA == +// params.put(Format.Alpha8, new BitMaskImageCodec(1, 0, 8, 0, 0, 0, +// 0, 0, 0, 0)); + + params.put(Format.Alpha8, new ByteOffsetImageCodec(1, 0, 0, -1, -1, -1)); + + params.put(Format.Alpha16, new BitMaskImageCodec(2, 0, 16, 0, 0, 0, + 0, 0, 0, 0)); + + // == LUMINANCE == +// params.put(Format.Luminance8, new BitMaskImageCodec(1, FLAG_GRAY, 0, 8, 0, 0, +// 0, 0, 0, 0)); + + params.put(Format.Luminance8, new ByteOffsetImageCodec(1, FLAG_GRAY, -1, 0, -1, -1)); + + params.put(Format.Luminance16, new BitMaskImageCodec(2, FLAG_GRAY, 0, 16, 0, 0, + 0, 0, 0, 0)); + params.put(Format.Luminance16F, new BitMaskImageCodec(2, FLAG_GRAY | FLAG_F16, 0, 16, 0, 0, + 0, 0, 0, 0)); + params.put(Format.Luminance32F, new BitMaskImageCodec(4, FLAG_GRAY | FLAG_F32, 0, 32, 0, 0, + 0, 0, 0, 0)); + + // == INTENSITY == + // ?? + + // == LUMINANCA ALPHA == +// params.put(Format.Luminance8Alpha8, new BitMaskImageCodec(2, FLAG_GRAY, +// 8, 8, 0, 0, +// 8, 0, 0, 0)); + + params.put(Format.Luminance8Alpha8, new ByteOffsetImageCodec(2, FLAG_GRAY, 1, 0, -1, -1)); + + params.put(Format.Luminance16Alpha16, new BitMaskImageCodec(4, FLAG_GRAY, + 16, 16, 0, 0, + 16, 0, 0, 0)); + + params.put(Format.Luminance16FAlpha16F, new BitMaskImageCodec(4, FLAG_GRAY | FLAG_F16, + 16, 16, 0, 0, + 16, 0, 0, 0)); + + // == RGB == +// params.put(Format.BGR8, new BitMaskImageCodec(3, 0, +// 0, 8, 8, 8, +// 0, 16, 8, 0)); +// + params.put(Format.BGR8, new ByteOffsetImageCodec(3, 0, -1, 2, 1, 0)); + + params.put(Format.RGB565, new BitMaskImageCodec(2, 0, + 0, 5, 6, 5, + 0, 11, 5, 0)); +// +// params.put(Format.RGB8, new BitMaskImageCodec(3, 0, +// 0, 8, 8, 8, +// 0, 0, 8, 16)); + + params.put(Format.RGB8, new ByteOffsetImageCodec(3, 0, -1, 0, 1, 2)); + + params.put(Format.RGB16, new ByteAlignedImageCodec(6, 0, + 0, 2, 2, 2, + 0, 0, 2, 4)); + + params.put(Format.RGB32F, new ByteAlignedImageCodec(12, FLAG_F32, + 0, 4, 4, 4, + 0, 0, 4, 8)); + + ByteAlignedImageCodec rgb16f = new ByteAlignedImageCodec(6, FLAG_F16, + 0, 2, 2, 2, + 0, 0, 2, 4); + params.put(Format.RGB16F, rgb16f); + params.put(Format.RGB16F_to_RGB111110F, rgb16f); + params.put(Format.RGB16F_to_RGB9E5, rgb16f); + + // == RGBA == +// params.put(Format.ABGR8, new BitMaskImageCodec(4, 0, +// 0, 8, 8, 8, +// 0, 24, 16, 8)); + + params.put(Format.ABGR8, new ByteOffsetImageCodec(4, 0, 0, 3, 2, 1)); + + params.put(Format.ARGB4444, new BitMaskImageCodec(2, 0, + 4, 4, 4, 4, + 12, 0, 4, 8)); + + params.put(Format.RGB5A1, new BitMaskImageCodec(2, 0, + 1, 5, 5, 5, + 0, 11, 6, 1)); + ((BitMaskImageCodec)params.get(Format.RGB5A1)).be = true; + +// params.put(Format.RGBA8, new ByteAlignedImageCodec(4, 0, +// 0, 1, 1, 1, +// 0, 0, 1, 2)); + + //new BitMaskImageCodec(4, 0, + // 8, 8, 8, 8, + // 24, 0, 8, 16)); + + params.put(Format.RGBA8, new ByteOffsetImageCodec(4, 0, 3, 0, 1, 2)); + + params.put(Format.RGBA16, new ByteAlignedImageCodec(8, 0, + 2, 2, 2, 2, + 6, 0, 2, 4)); + + params.put(Format.RGBA16F, new ByteAlignedImageCodec(8, FLAG_F16, + 2, 2, 2, 2, + 6, 0, 2, 4)); + + params.put(Format.RGBA32F, new ByteAlignedImageCodec(16, FLAG_F32, + 4, 4, 4, 4, + 12, 0, 4, 8)); + } + + public abstract void readComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp); + + public abstract void writeComponents(ByteBuffer buf, int x, int y, int width, int[] components, byte[] tmp); + + /** + * Looks up the format in the codec registry. + * The codec will be able to decode the given format. + * + * @param format The format to lookup. + * @return The codec capable of decoding it, or null if not found. + */ + public static ImageCodec lookup(Format format) { + ImageCodec codec = params.get(format); + if (codec == null) { + throw new UnsupportedOperationException("The format " + format + " is not supported"); + } + return codec; + } +} diff --git a/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java b/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java new file mode 100644 index 000000000..d16129708 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.image; + +import com.jme3.math.ColorRGBA; +import com.jme3.system.JmeSystem; +import com.jme3.texture.Image; + +/** + * Utility class for reading and writing from jME3 {@link Image images}. + *
      + * Allows directly manipulating pixels of the image by writing and + * reading {@link ColorRGBA colors} at any coordinate, without + * regard to the underlying {@link Image.Format format} of the image. + * NOTE: compressed and depth formats are not supported. + * Special RGB formats like RGB111110F and RGB9E5 are not supported + * at the moment, but may be added later on. For now + * use RGB16F_to_RGB111110F and RGB16F_to_RGB9E5 to handle + * the conversion on the GPU. + *

      + * If direct manipulations are done to the image, such as replacing + * the image data, or changing the width, height, or format, then + * all current instances of ImageReadWrite become invalid, and + * new instances must be created in order to properly access + * the image data. + * + * Usage example:
      + * + * Image myImage = ... + * ImageRaster raster = ImageRaster.create(myImage); + * raster.setPixel(1, 5, ColorRGBA.Green); + * System.out.println( raster.getPixel(1, 5) ); // Will print [0.0, 1.0, 0.0, 1.0]. + * + * + * @author Kirill Vainer + */ +public abstract class ImageRaster { + + /** + * Create new image reader / writer. + * + * @param image The image to read / write to. + * @param slice Which slice to use. Only applies to 3D images, 2D image + * arrays or cubemaps. + */ + public static ImageRaster create(Image image, int slices) { + return JmeSystem.createImageRaster(image, slices); + } + + /** + * Create new image reader / writer for 2D images. + * + * @param image The image to read / write to. + */ + public static ImageRaster create(Image image) { + if (image.getData().size() > 1) { + throw new IllegalStateException("Use constructor that takes slices argument to read from multislice image"); + } + return JmeSystem.createImageRaster(image, 0); + } + + public ImageRaster() { + } + + /** + * Returns the pixel width of the underlying image. + * + * @return the pixel width of the underlying image. + */ + public abstract int getWidth(); + + /** + * Returns the pixel height of the underlying image. + * + * @return the pixel height of the underlying image. + */ + public abstract int getHeight(); + + /** + * Sets the pixel at the given coordinate to the given color. + *

      + * For all integer based formats (those not ending in "F"), the + * color is first clamped to 0.0 - 1.0 before converting it to + * an integer to avoid overflow. For floating point based formats, + * components larger than 1.0 can be represented, but components + * lower than 0.0 are still not allowed (as all formats are unsigned). + *

      + * If the underlying format is grayscale (e.g. one of the luminance formats, + * such as {@link Image.Format#Luminance8}) then a color to grayscale + * conversion is done first, before writing the result into the image. + *

      + * If the image does not have some of the components in the color (such + * as alpha, or any of the color components), then these components + * will be ignored. The only exception to this is luminance formats + * for which the color is converted to luminance first (see above). + *

      + * After writing the color, the image shall be marked as requiring an + * update. The next time it is used for rendering, all pixel changes + * will be reflected when the image is rendered. + * + * @param x The x coordinate, from 0 to width - 1. + * @param y The y coordinate, from 0 to height - 1. + * @param color The color to write. + * @throws IllegalArgumentException If x or y are outside the image dimensions. + */ + public abstract void setPixel(int x, int y, ColorRGBA color); + + /** + * Retrieve the color at the given coordinate. + *

      + * Any components that are not defined in the image format + * will be set to 1.0 in the returned color. For example, + * reading from an {@link Image.Format#Alpha8} format will + * return a ColorRGBA with the R, G, and B components set to 1.0, and + * the A component set to the alpha in the image. + *

      + * For grayscale or luminance formats, the luminance value is replicated + * in the R, G, and B components. + *

      + * Integer formats are converted to the range 0.0 - 1.0, based + * on the maximum possible integer value that can be represented + * by the number of bits the component has. + * For example, the {@link Image.Format#RGB5A1} format can + * contain the integer values 0 - 31, a conversion to floating point + * is done by diving the integer value by 31 (done with floating point + * precision). + * + * @param x The x coordinate, from 0 to width - 1. + * @param y The y coordinate, from 0 to height - 1. + * @param store Storage location for the read color, if null, + * then a new ColorRGBA is created and returned with the read color. + * @return The store parameter, if it is null, then a new ColorRGBA + * with the read color. + * @throws IllegalArgumentException If x or y are outside the image dimensions. + */ + public abstract ColorRGBA getPixel(int x, int y, ColorRGBA store); + + /** + * Retrieve the color at the given coordinate. + *

      + * Convenience method that does not take a store argument. Equivalent + * to calling getPixel(x, y, null). + * See {@link #getPixel(int, int, com.jme3.math.ColorRGBA) } for + * more information. + * + * @param x The x coordinate, from 0 to width - 1. + * @param y The y coordinate, from 0 to height - 1. + * @return A new ColorRGBA with the read color. + * @throws IllegalArgumentException If x or y are outside the image dimensions + */ + public ColorRGBA getPixel(int x, int y) { + return getPixel(x, y, null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/ui/Picture.java b/jme3-core/src/main/java/com/jme3/ui/Picture.java new file mode 100644 index 000000000..ad7bc8799 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/ui/Picture.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.ui; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.material.Material; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture2D; + +/** + * A 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 = 1f; + private float height = 1f; + + /** + * Create a named picture. + * + * By default a picture's width and height are 1 + * and its position is 0, 0. + * + * @param name the name of the picture in the scene graph + * @param flipY If true, the Y coordinates of the texture will be flipped. + */ + public Picture(String name, boolean flipY){ + super(name, new Quad(1, 1, flipY)); + setQueueBucket(Bucket.Gui); + setCullHint(CullHint.Never); + } + + /** + * Creates a named picture. + * By default a picture's width and height are 1 + * and its position is 0, 0. + * The image texture coordinates will not be flipped. + * + * @param name the name of the picture in the scene graph + */ + public Picture(String name){ + this(name, false); + } + + /* + * Serialization only. Do not use. + */ + public Picture(){ + } + + /** + * Set the width in pixels of the picture, if the width + * does not match the texture's width, then the texture will + * be scaled to fit the picture. + * + * @param width the width to set. + */ + public void setWidth(float width){ + this.width = width; + setLocalScale(new Vector3f(width, height, 1f)); + } + + /** + * Set the height in pixels of the picture, if the height + * does not match the texture's height, then the texture will + * be scaled to fit the picture. + * + * @param height the height to set. + */ + public void setHeight(float height){ + this.height = height; + setLocalScale(new Vector3f(width, height, 1f)); + } + + /** + * Set the position of the picture in pixels. + * The origin (0, 0) is at the bottom-left of the screen. + * + * @param x The x coordinate + * @param y The y coordinate + */ + public void setPosition(float x, float y){ + float z = getLocalTranslation().getZ(); + setLocalTranslation(x, y, z); + } + + /** + * Set the image to put on the picture. + * + * @param assetManager The {@link AssetManager} to use to load the image. + * @param imgName The image name. + * @param useAlpha If true, the picture will appear transparent and allow + * objects behind it to appear through. If false, the transparent + * portions will be the image's color at that pixel. + */ + public void setImage(AssetManager assetManager, String imgName, boolean useAlpha){ + TextureKey key = new TextureKey(imgName, true); + Texture2D tex = (Texture2D) assetManager.loadTexture(key); + setTexture(assetManager, tex, useAlpha); + } + + /** + * Set the texture to put on the picture. + * + * @param assetManager The {@link AssetManager} to use to load the material. + * @param tex The texture + * @param useAlpha If true, the picture will appear transparent and allow + * objects behind it to appear through. If false, the transparent + * portions will be the image's color at that pixel. + */ + public void setTexture(AssetManager assetManager, Texture2D tex, boolean useAlpha){ + if (getMaterial() == null){ + Material mat = new Material(assetManager, "Common/MatDefs/Gui/Gui.j3md"); + mat.setColor("Color", ColorRGBA.White); + setMaterial(mat); + } + material.getAdditionalRenderState().setBlendMode(useAlpha ? BlendMode.Alpha : BlendMode.Off); + material.setTexture("Texture", tex); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/util/BufferUtils.java b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java new file mode 100644 index 000000000..dcc6ab34d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java @@ -0,0 +1,1381 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +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.LongBuffer; +import java.nio.ShortBuffer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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 { + + private static boolean trackDirectMemory = false; + private static ReferenceQueue removeCollected = new ReferenceQueue(); + private static ConcurrentHashMap trackedBuffers = new ConcurrentHashMap(); + static ClearReferences cleanupthread; + + /** + * Set it to true if you want to enable direct memory tracking for debugging purpose. + * Default is false. + * To print direct memory usage use BufferUtils.printCurrentDirectMemory(StringBuilder store); + * @param enabled + */ + public static void setTrackDirectMemoryEnabled(boolean enabled) { + trackDirectMemory = enabled; + } + + /** + * Creates a clone of the given buffer. The clone's capacity is + * equal to the given buffer's limit. + * + * @param buf The buffer to clone + * @return The cloned buffer + */ + public static Buffer clone(Buffer buf) { + if (buf instanceof FloatBuffer) { + return clone((FloatBuffer) buf); + } else if (buf instanceof ShortBuffer) { + return clone((ShortBuffer) buf); + } else if (buf instanceof ByteBuffer) { + return clone((ByteBuffer) buf); + } else if (buf instanceof IntBuffer) { + return clone((IntBuffer) buf); + } else if (buf instanceof DoubleBuffer) { + return clone((DoubleBuffer) buf); + } else { + throw new UnsupportedOperationException(); + } + } + + private static void onBufferAllocated(Buffer buffer) { + /** + * StackTraceElement[] stackTrace = new Throwable().getStackTrace(); int + * initialIndex = 0; + * + * for (int i = 0; i < stackTrace.length; i++){ if + * (!stackTrace[i].getClassName().equals(BufferUtils.class.getName())){ + * initialIndex = i; break; } } + * + * int allocated = buffer.capacity(); int size = 0; + * + * if (buffer instanceof FloatBuffer){ size = 4; }else if (buffer + * instanceof ShortBuffer){ size = 2; }else if (buffer instanceof + * ByteBuffer){ size = 1; }else if (buffer instanceof IntBuffer){ size = + * 4; }else if (buffer instanceof DoubleBuffer){ size = 8; } + * + * allocated *= size; + * + * for (int i = initialIndex; i < stackTrace.length; i++){ + * StackTraceElement element = stackTrace[i]; if + * (element.getClassName().startsWith("java")){ break; } + * + * try { Class clazz = Class.forName(element.getClassName()); if (i == + * initialIndex){ + * System.out.println(clazz.getSimpleName()+"."+element.getMethodName + * ()+"():" + element.getLineNumber() + " allocated " + allocated); + * }else{ System.out.println(" at " + + * clazz.getSimpleName()+"."+element.getMethodName()+"()"); } } catch + * (ClassNotFoundException ex) { } } + */ + if (BufferUtils.trackDirectMemory) { + + if (BufferUtils.cleanupthread == null) { + BufferUtils.cleanupthread = new ClearReferences(); + BufferUtils.cleanupthread.start(); + } + if (buffer instanceof ByteBuffer) { + BufferInfo info = new BufferInfo(ByteBuffer.class, buffer.capacity(), buffer, BufferUtils.removeCollected); + BufferUtils.trackedBuffers.put(info, info); + } else if (buffer instanceof FloatBuffer) { + BufferInfo info = new BufferInfo(FloatBuffer.class, buffer.capacity() * 4, buffer, BufferUtils.removeCollected); + BufferUtils.trackedBuffers.put(info, info); + } else if (buffer instanceof IntBuffer) { + BufferInfo info = new BufferInfo(IntBuffer.class, buffer.capacity() * 4, buffer, BufferUtils.removeCollected); + BufferUtils.trackedBuffers.put(info, info); + } else if (buffer instanceof ShortBuffer) { + BufferInfo info = new BufferInfo(ShortBuffer.class, buffer.capacity() * 2, buffer, BufferUtils.removeCollected); + BufferUtils.trackedBuffers.put(info, info); + } else if (buffer instanceof DoubleBuffer) { + BufferInfo info = new BufferInfo(DoubleBuffer.class, buffer.capacity() * 8, buffer, BufferUtils.removeCollected); + BufferUtils.trackedBuffers.put(info, info); + } + + } + } + + /** + * 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 (Vector3f element : data) { + if (element != null) { + buff.put(element.x).put(element.y).put(element.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 (Quaternion element : data) { + if (element != null) { + buff.put(element.getX()).put(element.getY()).put(element.getZ()).put(element.getW()); + } else { + buff.put(0).put(0).put(0).put(0); + } + } + buff.flip(); + return buff; + } + + /** + * Generate a new FloatBuffer using the given array of Vector4 objects. + * The FloatBuffer will be 4 * data.length long and contain the vector data. + * + * @param data array of Vector4 objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(Vector4f... 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).put(0); + } + } + buff.flip(); + return buff; + } + + /** + * Generate a new FloatBuffer using the given array of float primitives. + * @param data array of float primitives to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(float... data) { + if (data == null) { + return null; + } + FloatBuffer buff = createFloatBuffer(data.length); + buff.clear(); + buff.put(data); + buff.flip(); + return buff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified + * number of Vector3f object data. + * + * @param vertices + * number of vertices that need to be held by the newly created + * buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector3Buffer(int vertices) { + FloatBuffer vBuff = createFloatBuffer(3 * vertices); + return vBuff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified + * number of Vector3f object data only if the given buffer if not already + * the right size. + * + * @param buf + * the buffer to first check and rewind + * @param vertices + * number of vertices that need to be held by the newly created + * buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector3Buffer(FloatBuffer buf, int vertices) { + if (buf != null && buf.limit() == 3 * vertices) { + buf.rewind(); + return buf; + } + + return createFloatBuffer(3 * vertices); + } + + /** + * Sets the data contained in the given color into the FloatBuffer at the + * specified index. + * + * @param color + * the data to insert + * @param buf + * the buffer to insert into + * @param index + * the postion to place the data; in terms of colors not floats + */ + public static void setInBuffer(ColorRGBA color, FloatBuffer buf, + int index) { + buf.position(index * 4); + buf.put(color.r); + buf.put(color.g); + buf.put(color.b); + buf.put(color.a); + } + + /** + * Sets the data contained in the given quaternion into the FloatBuffer at the + * specified index. + * + * @param quat + * the {@link Quaternion} to insert + * @param buf + * the buffer to insert into + * @param index + * the postion to place the data; in terms of quaternions not floats + */ + public static void setInBuffer(Quaternion quat, FloatBuffer buf, + int index) { + buf.position(index * 4); + buf.put(quat.getX()); + buf.put(quat.getY()); + buf.put(quat.getZ()); + buf.put(quat.getW()); + } + + /** + * Sets the data contained in the given vector4 into the FloatBuffer at the + * specified index. + * + * @param vec + * the {@link Vector4f} to insert + * @param buf + * the buffer to insert into + * @param index + * the postion to place the data; in terms of vector4 not floats + */ + public static void setInBuffer(Vector4f vec, FloatBuffer buf, + int index) { + buf.position(index * 4); + buf.put(vec.getX()); + buf.put(vec.getY()); + buf.put(vec.getZ()); + buf.put(vec.getW()); + } + + /** + * Sets the data contained in the given Vector3F into the FloatBuffer at the + * specified index. + * + * @param vector + * the data to insert + * @param buf + * the buffer to insert into + * @param index + * the postion to place the data; in terms of vectors not floats + */ + public static void setInBuffer(Vector3f vector, FloatBuffer buf, int index) { + if (buf == null) { + return; + } + if (vector == null) { + buf.put(index * 3, 0); + buf.put((index * 3) + 1, 0); + buf.put((index * 3) + 2, 0); + } else { + buf.put(index * 3, vector.x); + buf.put((index * 3) + 1, vector.y); + buf.put((index * 3) + 2, vector.z); + } + } + + /** + * Updates the values of the given vector from the specified buffer at the + * index provided. + * + * @param vector + * the vector to set data on + * @param buf + * the buffer to read from + * @param index + * the position (in terms of vectors, not floats) to read from + * the buf + */ + public static void populateFromBuffer(Vector3f vector, FloatBuffer buf, int index) { + vector.x = buf.get(index * 3); + vector.y = buf.get(index * 3 + 1); + vector.z = buf.get(index * 3 + 2); + } + + /** + * Generates a Vector3f array from the given FloatBuffer. + * + * @param buff + * the FloatBuffer to read from + * @return a newly generated array of Vector3f objects + */ + public static Vector3f[] getVector3Array(FloatBuffer buff) { + buff.clear(); + Vector3f[] verts = new Vector3f[buff.limit() / 3]; + for (int x = 0; x < verts.length; x++) { + Vector3f v = new Vector3f(buff.get(), buff.get(), buff.get()); + verts[x] = v; + } + return verts; + } + + /** + * Copies a Vector3f from one position in the buffer to another. The index + * values are in terms of vector number (eg, vector number 0 is postions 0-2 + * in the FloatBuffer.) + * + * @param buf + * the buffer to copy from/to + * @param fromPos + * the index of the vector to copy + * @param toPos + * the index to copy the vector to + */ + public static void copyInternalVector3(FloatBuffer buf, int fromPos, int toPos) { + copyInternal(buf, fromPos * 3, toPos * 3, 3); + } + + /** + * Normalize a Vector3f in-buffer. + * + * @param buf + * the buffer to find the Vector3f within + * @param index + * the position (in terms of vectors, not floats) of the vector + * to normalize + */ + public static void normalizeVector3(FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector3f tempVec3 = vars.vect1; + populateFromBuffer(tempVec3, buf, index); + tempVec3.normalizeLocal(); + setInBuffer(tempVec3, buf, index); + vars.release(); + } + + /** + * Add to a Vector3f in-buffer. + * + * @param toAdd + * the vector to add from + * @param buf + * the buffer to find the Vector3f within + * @param index + * the position (in terms of vectors, not floats) of the vector + * to add to + */ + public static void addInBuffer(Vector3f toAdd, FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector3f tempVec3 = vars.vect1; + populateFromBuffer(tempVec3, buf, index); + tempVec3.addLocal(toAdd); + setInBuffer(tempVec3, buf, index); + vars.release(); + } + + /** + * Multiply and store a Vector3f in-buffer. + * + * @param toMult + * the vector to multiply against + * @param buf + * the buffer to find the Vector3f within + * @param index + * the position (in terms of vectors, not floats) of the vector + * to multiply + */ + public static void multInBuffer(Vector3f toMult, FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector3f tempVec3 = vars.vect1; + populateFromBuffer(tempVec3, buf, index); + tempVec3.multLocal(toMult); + setInBuffer(tempVec3, buf, index); + vars.release(); + } + + /** + * Checks to see if the given Vector3f is equals to the data stored in the + * buffer at the given data index. + * + * @param check + * the vector to check against - null will return false. + * @param buf + * the buffer to compare data with + * @param index + * the position (in terms of vectors, not floats) of the vector + * in the buffer to check against + * @return true if the data is equivalent, otherwise false. + */ + public static boolean equals(Vector3f check, FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector3f tempVec3 = vars.vect1; + populateFromBuffer(tempVec3, buf, index); + boolean eq = tempVec3.equals(check); + vars.release(); + return eq; + } + + // // -- VECTOR2F METHODS -- //// + /** + * Generate a new FloatBuffer using the given array of Vector2f objects. + * The FloatBuffer will be 2 * data.length long and contain the vector data + * as data[0].x, data[0].y, data[1].x... etc. + * + * @param data array of Vector2f objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(Vector2f... data) { + if (data == null) { + return null; + } + FloatBuffer buff = createFloatBuffer(2 * data.length); + for (Vector2f element : data) { + if (element != null) { + buff.put(element.x).put(element.y); + } else { + buff.put(0).put(0); + } + } + buff.flip(); + return buff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified + * number of Vector2f object data. + * + * @param vertices + * number of vertices that need to be held by the newly created + * buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector2Buffer(int vertices) { + FloatBuffer vBuff = createFloatBuffer(2 * vertices); + return vBuff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified + * number of Vector2f object data only if the given buffer if not already + * the right size. + * + * @param buf + * the buffer to first check and rewind + * @param vertices + * number of vertices that need to be held by the newly created + * buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector2Buffer(FloatBuffer buf, int vertices) { + if (buf != null && buf.limit() == 2 * vertices) { + buf.rewind(); + return buf; + } + + return createFloatBuffer(2 * vertices); + } + + /** + * Sets the data contained in the given Vector2F into the FloatBuffer at the + * specified index. + * + * @param vector + * the data to insert + * @param buf + * the buffer to insert into + * @param index + * the postion to place the data; in terms of vectors not floats + */ + public static void setInBuffer(Vector2f vector, FloatBuffer buf, int index) { + buf.put(index * 2, vector.x); + buf.put((index * 2) + 1, vector.y); + } + + /** + * Updates the values of the given vector from the specified buffer at the + * index provided. + * + * @param vector + * the vector to set data on + * @param buf + * the buffer to read from + * @param index + * the position (in terms of vectors, not floats) to read from + * the buf + */ + public static void populateFromBuffer(Vector2f vector, FloatBuffer buf, int index) { + vector.x = buf.get(index * 2); + vector.y = buf.get(index * 2 + 1); + } + + /** + * Generates a Vector2f array from the given FloatBuffer. + * + * @param buff + * the FloatBuffer to read from + * @return a newly generated array of Vector2f objects + */ + public static Vector2f[] getVector2Array(FloatBuffer buff) { + buff.clear(); + Vector2f[] verts = new Vector2f[buff.limit() / 2]; + for (int x = 0; x < verts.length; x++) { + Vector2f v = new Vector2f(buff.get(), buff.get()); + verts[x] = v; + } + return verts; + } + + /** + * Copies a Vector2f from one position in the buffer to another. The index + * values are in terms of vector number (eg, vector number 0 is postions 0-1 + * in the FloatBuffer.) + * + * @param buf + * the buffer to copy from/to + * @param fromPos + * the index of the vector to copy + * @param toPos + * the index to copy the vector to + */ + public static void copyInternalVector2(FloatBuffer buf, int fromPos, int toPos) { + copyInternal(buf, fromPos * 2, toPos * 2, 2); + } + + /** + * Normalize a Vector2f in-buffer. + * + * @param buf + * the buffer to find the Vector2f within + * @param index + * the position (in terms of vectors, not floats) of the vector + * to normalize + */ + public static void normalizeVector2(FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector2f tempVec2 = vars.vect2d; + populateFromBuffer(tempVec2, buf, index); + tempVec2.normalizeLocal(); + setInBuffer(tempVec2, buf, index); + vars.release(); + } + + /** + * Add to a Vector2f in-buffer. + * + * @param toAdd + * the vector to add from + * @param buf + * the buffer to find the Vector2f within + * @param index + * the position (in terms of vectors, not floats) of the vector + * to add to + */ + public static void addInBuffer(Vector2f toAdd, FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector2f tempVec2 = vars.vect2d; + populateFromBuffer(tempVec2, buf, index); + tempVec2.addLocal(toAdd); + setInBuffer(tempVec2, buf, index); + vars.release(); + } + + /** + * Multiply and store a Vector2f in-buffer. + * + * @param toMult + * the vector to multiply against + * @param buf + * the buffer to find the Vector2f within + * @param index + * the position (in terms of vectors, not floats) of the vector + * to multiply + */ + public static void multInBuffer(Vector2f toMult, FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector2f tempVec2 = vars.vect2d; + populateFromBuffer(tempVec2, buf, index); + tempVec2.multLocal(toMult); + setInBuffer(tempVec2, buf, index); + vars.release(); + } + + /** + * Checks to see if the given Vector2f is equals to the data stored in the + * buffer at the given data index. + * + * @param check + * the vector to check against - null will return false. + * @param buf + * the buffer to compare data with + * @param index + * the position (in terms of vectors, not floats) of the vector + * in the buffer to check against + * @return true if the data is equivalent, otherwise false. + */ + public static boolean equals(Vector2f check, FloatBuffer buf, int index) { + TempVars vars = TempVars.get(); + Vector2f tempVec2 = vars.vect2d; + populateFromBuffer(tempVec2, buf, index); + boolean eq = tempVec2.equals(check); + vars.release(); + return eq; + } + + //// -- INT METHODS -- //// + /** + * Generate a new IntBuffer using the given array of ints. The IntBuffer + * will be data.length long and contain the int data as data[0], data[1]... + * etc. + * + * @param data + * array of ints to place into a new IntBuffer + */ + public static IntBuffer createIntBuffer(int... data) { + if (data == null) { + return null; + } + IntBuffer buff = createIntBuffer(data.length); + buff.clear(); + buff.put(data); + buff.flip(); + return buff; + } + + /** + * Create a new int[] array and populate it with the given IntBuffer's + * contents. + * + * @param buff + * the IntBuffer to read from + * @return a new int array populated from the IntBuffer + */ + public static int[] getIntArray(IntBuffer buff) { + if (buff == null) { + return null; + } + buff.clear(); + int[] inds = new int[buff.limit()]; + for (int x = 0; x < inds.length; x++) { + inds[x] = buff.get(); + } + return inds; + } + + /** + * Create a new float[] array and populate it with the given FloatBuffer's + * contents. + * + * @param buff + * the FloatBuffer to read from + * @return a new float array populated from the FloatBuffer + */ + public static float[] getFloatArray(FloatBuffer buff) { + if (buff == null) { + return null; + } + buff.clear(); + float[] inds = new float[buff.limit()]; + for (int x = 0; x < inds.length; x++) { + inds[x] = buff.get(); + } + return inds; + } + + //// -- GENERAL DOUBLE ROUTINES -- //// + /** + * Create a new DoubleBuffer of the specified size. + * + * @param size + * required number of double to store. + * @return the new DoubleBuffer + */ + public static DoubleBuffer createDoubleBuffer(int size) { + DoubleBuffer buf = ByteBuffer.allocateDirect(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer(); + buf.clear(); + onBufferAllocated(buf); + return buf; + } + + /** + * Create a new DoubleBuffer of an appropriate size to hold the specified + * number of doubles only if the given buffer if not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of doubles that need to be held by the newly created + * buffer + * @return the requested new DoubleBuffer + */ + public static DoubleBuffer createDoubleBuffer(DoubleBuffer buf, int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createDoubleBuffer(size); + return buf; + } + + /** + * Creates a new DoubleBuffer with the same contents as the given + * DoubleBuffer. The new DoubleBuffer is seperate from the old one and + * changes are not reflected across. If you want to reflect changes, + * consider using Buffer.duplicate(). + * + * @param buf + * the DoubleBuffer to copy + * @return the copy + */ + public static DoubleBuffer clone(DoubleBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + DoubleBuffer copy; + if (isDirect(buf)) { + copy = createDoubleBuffer(buf.limit()); + } else { + copy = DoubleBuffer.allocate(buf.limit()); + } + copy.put(buf); + + return copy; + } + + //// -- GENERAL FLOAT ROUTINES -- //// + /** + * Create a new FloatBuffer of the specified size. + * + * @param size + * required number of floats to store. + * @return the new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(int size) { + FloatBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer(); + buf.clear(); + onBufferAllocated(buf); + return buf; + } + + /** + * Copies floats from one position in the buffer to another. + * + * @param buf + * the buffer to copy from/to + * @param fromPos + * the starting point to copy from + * @param toPos + * the starting point to copy to + * @param length + * the number of floats to copy + */ + public static void copyInternal(FloatBuffer buf, int fromPos, int toPos, int length) { + float[] data = new float[length]; + buf.position(fromPos); + buf.get(data); + buf.position(toPos); + buf.put(data); + } + + /** + * Creates a new FloatBuffer with the same contents as the given + * FloatBuffer. The new FloatBuffer is seperate from the old one and changes + * are not reflected across. If you want to reflect changes, consider using + * Buffer.duplicate(). + * + * @param buf + * the FloatBuffer to copy + * @return the copy + */ + public static FloatBuffer clone(FloatBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + FloatBuffer copy; + if (isDirect(buf)) { + copy = createFloatBuffer(buf.limit()); + } else { + copy = FloatBuffer.allocate(buf.limit()); + } + copy.put(buf); + + return copy; + } + + //// -- GENERAL INT ROUTINES -- //// + /** + * Create a new IntBuffer of the specified size. + * + * @param size + * required number of ints to store. + * @return the new IntBuffer + */ + public static IntBuffer createIntBuffer(int size) { + IntBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer(); + buf.clear(); + onBufferAllocated(buf); + return buf; + } + + /** + * Create a new IntBuffer of an appropriate size to hold the specified + * number of ints only if the given buffer if not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of ints that need to be held by the newly created + * buffer + * @return the requested new IntBuffer + */ + public static IntBuffer createIntBuffer(IntBuffer buf, int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createIntBuffer(size); + return buf; + } + + /** + * Creates a new IntBuffer with the same contents as the given IntBuffer. + * The new IntBuffer is seperate from the old one and changes are not + * reflected across. If you want to reflect changes, consider using + * Buffer.duplicate(). + * + * @param buf + * the IntBuffer to copy + * @return the copy + */ + public static IntBuffer clone(IntBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + IntBuffer copy; + if (isDirect(buf)) { + copy = createIntBuffer(buf.limit()); + } else { + copy = IntBuffer.allocate(buf.limit()); + } + copy.put(buf); + + return copy; + } + + //// -- GENERAL BYTE ROUTINES -- //// + /** + * Create a new ByteBuffer of the specified size. + * + * @param size + * required number of ints to store. + * @return the new IntBuffer + */ + public static ByteBuffer createByteBuffer(int size) { + ByteBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); + buf.clear(); + onBufferAllocated(buf); + return buf; + } + + /** + * Create a new ByteBuffer of an appropriate size to hold the specified + * number of ints only if the given buffer if not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of bytes that need to be held by the newly created + * buffer + * @return the requested new IntBuffer + */ + public static ByteBuffer createByteBuffer(ByteBuffer buf, int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createByteBuffer(size); + return buf; + } + + public static ByteBuffer createByteBuffer(byte... data) { + ByteBuffer bb = createByteBuffer(data.length); + bb.put(data); + bb.flip(); + return bb; + } + + public static ByteBuffer createByteBuffer(String data) { + byte[] bytes = data.getBytes(); + ByteBuffer bb = createByteBuffer(bytes.length); + bb.put(bytes); + bb.flip(); + return bb; + } + + /** + * Creates a new ByteBuffer with the same contents as the given ByteBuffer. + * The new ByteBuffer is seperate from the old one and changes are not + * reflected across. If you want to reflect changes, consider using + * Buffer.duplicate(). + * + * @param buf + * the ByteBuffer to copy + * @return the copy + */ + public static ByteBuffer clone(ByteBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + ByteBuffer copy; + if (isDirect(buf)) { + copy = createByteBuffer(buf.limit()); + } else { + copy = ByteBuffer.allocate(buf.limit()); + } + copy.put(buf); + + return copy; + } + + //// -- GENERAL SHORT ROUTINES -- //// + /** + * Create a new ShortBuffer of the specified size. + * + * @param size + * required number of shorts to store. + * @return the new ShortBuffer + */ + public static ShortBuffer createShortBuffer(int size) { + ShortBuffer buf = ByteBuffer.allocateDirect(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer(); + buf.clear(); + onBufferAllocated(buf); + return buf; + } + + /** + * Create a new ShortBuffer of an appropriate size to hold the specified + * number of shorts only if the given buffer if not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of shorts that need to be held by the newly created + * buffer + * @return the requested new ShortBuffer + */ + public static ShortBuffer createShortBuffer(ShortBuffer buf, int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createShortBuffer(size); + return buf; + } + + public static ShortBuffer createShortBuffer(short... data) { + if (data == null) { + return null; + } + ShortBuffer buff = createShortBuffer(data.length); + buff.clear(); + buff.put(data); + buff.flip(); + return buff; + } + + /** + * Creates a new ShortBuffer with the same contents as the given ShortBuffer. + * The new ShortBuffer is seperate from the old one and changes are not + * reflected across. If you want to reflect changes, consider using + * Buffer.duplicate(). + * + * @param buf + * the ShortBuffer to copy + * @return the copy + */ + public static ShortBuffer clone(ShortBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + ShortBuffer copy; + if (isDirect(buf)) { + 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.limit(buffer.capacity()); + } + if (buffer == null || (buffer.remaining() < required)) { + int position = (buffer != null ? buffer.position() : 0); + FloatBuffer newVerts = createFloatBuffer(position + required); + if (buffer != null) { + buffer.flip(); + newVerts.put(buffer); + newVerts.position(position); + } + buffer = newVerts; + } + return buffer; + } + + public static IntBuffer ensureLargeEnough(IntBuffer buffer, int required) { + if (buffer != null) { + buffer.limit(buffer.capacity()); + } + if (buffer == null || (buffer.remaining() < required)) { + int position = (buffer != null ? buffer.position() : 0); + IntBuffer newVerts = createIntBuffer(position + required); + if (buffer != null) { + buffer.flip(); + newVerts.put(buffer); + newVerts.position(position); + } + buffer = newVerts; + } + return buffer; + } + + + public static ShortBuffer ensureLargeEnough(ShortBuffer buffer, int required) { + if (buffer != null) { + buffer.limit(buffer.capacity()); + } + if (buffer == null || (buffer.remaining() < required)) { + int position = (buffer != null ? buffer.position() : 0); + ShortBuffer newVerts = createShortBuffer(position + required); + if (buffer != null) { + buffer.flip(); + newVerts.put(buffer); + newVerts.position(position); + } + buffer = newVerts; + } + return buffer; + } + + public static ByteBuffer ensureLargeEnough(ByteBuffer buffer, int required) { + if (buffer != null) { + buffer.limit(buffer.capacity()); + } + if (buffer == null || (buffer.remaining() < required)) { + int position = (buffer != null ? buffer.position() : 0); + ByteBuffer newVerts = createByteBuffer(position + required); + if (buffer != null) { + buffer.flip(); + newVerts.put(buffer); + newVerts.position(position); + } + buffer = newVerts; + } + return buffer; + } + + public static void printCurrentDirectMemory(StringBuilder store) { + long totalHeld = 0; + long heapMem = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + + boolean printStout = store == null; + if (store == null) { + store = new StringBuilder(); + } + if (trackDirectMemory) { + // make a new set to hold the keys to prevent concurrency issues. + int fBufs = 0, bBufs = 0, iBufs = 0, sBufs = 0, dBufs = 0; + int fBufsM = 0, bBufsM = 0, iBufsM = 0, sBufsM = 0, dBufsM = 0; + for (BufferInfo b : BufferUtils.trackedBuffers.values()) { + if (b.type == ByteBuffer.class) { + totalHeld += b.size; + bBufsM += b.size; + bBufs++; + } else if (b.type == FloatBuffer.class) { + totalHeld += b.size; + fBufsM += b.size; + fBufs++; + } else if (b.type == IntBuffer.class) { + totalHeld += b.size; + iBufsM += b.size; + iBufs++; + } else if (b.type == ShortBuffer.class) { + totalHeld += b.size; + sBufsM += b.size; + sBufs++; + } else if (b.type == DoubleBuffer.class) { + totalHeld += b.size; + dBufsM += b.size; + dBufs++; + } + } + + store.append("Existing buffers: ").append(BufferUtils.trackedBuffers.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"); + } else { + store.append("Total heap memory held: ").append(heapMem / 1024).append("kb\n"); + store.append("Only heap memory available, if you want to monitor direct memory use BufferUtils.setTrackDirectMemoryEnabled(true) during initialization.").append("\n"); + } + if (printStout) { + System.out.println(store.toString()); + } + } + private static final AtomicBoolean loadedMethods = new AtomicBoolean(false); + private static Method cleanerMethod = null; + private static Method cleanMethod = null; + private static Method viewedBufferMethod = null; + private static Method freeMethod = null; + + private static Method loadMethod(String className, String methodName) { + try { + Method method = Class.forName(className).getMethod(methodName); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException ex) { + return null; // the method was not found + } catch (SecurityException ex) { + return null; // setAccessible not allowed by security policy + } catch (ClassNotFoundException ex) { + return null; // the direct buffer implementation was not found + } + } + + private static void loadCleanerMethods() { + // If its already true, exit, if not, set it to true. + if (BufferUtils.loadedMethods.getAndSet(true)) { + return; + } + // This could potentially be called many times if used from multiple + // threads + synchronized (BufferUtils.loadedMethods) { + // Oracle JRE / OpenJDK + cleanerMethod = loadMethod("sun.nio.ch.DirectBuffer", "cleaner"); + cleanMethod = loadMethod("sun.misc.Cleaner", "clean"); + viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "viewedBuffer"); + if (viewedBufferMethod == null) { + // They changed the name in Java 7 (???) + viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "attachment"); + } + + // Apache Harmony + ByteBuffer bb = BufferUtils.createByteBuffer(1); + Class clazz = bb.getClass(); + try { + freeMethod = clazz.getMethod("free"); + } catch (NoSuchMethodException ex) { + } catch (SecurityException ex) { + } + } + } + + /** + * Direct buffers are garbage collected by using a phantom reference and a + * reference queue. Every once a while, the JVM checks the reference queue and + * cleans the direct buffers. However, as this doesn't happen + * immediately after discarding all references to a direct buffer, it's + * easy to OutOfMemoryError yourself using direct buffers. This function + * explicitly calls the Cleaner method of a direct buffer. + * + * @param toBeDestroyed + * The direct buffer that will be "cleaned". Utilizes reflection. + * + */ + public static void destroyDirectBuffer(Buffer toBeDestroyed) { + if (!isDirect(toBeDestroyed)) { + return; + } + + BufferUtils.loadCleanerMethods(); + + try { + if (freeMethod != null) { + freeMethod.invoke(toBeDestroyed); + } else { + Object cleaner = cleanerMethod.invoke(toBeDestroyed); + if (cleaner != null) { + cleanMethod.invoke(cleaner); + } else { + // Try the alternate approach of getting the viewed buffer first + Object viewedBuffer = viewedBufferMethod.invoke(toBeDestroyed); + if (viewedBuffer != null) { + destroyDirectBuffer((Buffer) viewedBuffer); + } else { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "Buffer cannot be destroyed: {0}", toBeDestroyed); + } + } + } + } catch (IllegalAccessException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } catch (IllegalArgumentException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } catch (InvocationTargetException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } catch (SecurityException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } + } + + /* + * FIXME when java 1.5 supprt is dropped - replace calls to this method with Buffer.isDirect + * + * Buffer.isDirect() is only java 6. Java 5 only have this method on Buffer subclasses : + * FloatBuffer, IntBuffer, ShortBuffer, ByteBuffer,DoubleBuffer, LongBuffer. + * CharBuffer has been excluded as we don't use it. + * + */ + private static boolean isDirect(Buffer buf) { + if (buf instanceof FloatBuffer) { + return ((FloatBuffer) buf).isDirect(); + } + if (buf instanceof IntBuffer) { + return ((IntBuffer) buf).isDirect(); + } + if (buf instanceof ShortBuffer) { + return ((ShortBuffer) buf).isDirect(); + } + if (buf instanceof ByteBuffer) { + return ((ByteBuffer) buf).isDirect(); + } + if (buf instanceof DoubleBuffer) { + return ((DoubleBuffer) buf).isDirect(); + } + if (buf instanceof LongBuffer) { + return ((LongBuffer) buf).isDirect(); + } + throw new UnsupportedOperationException(" BufferUtils.isDirect was called on " + buf.getClass().getName()); + } + + private static class BufferInfo extends PhantomReference { + + private Class type; + private int size; + + public BufferInfo(Class type, int size, Buffer referent, ReferenceQueue q) { + super(referent, q); + this.type = type; + this.size = size; + } + } + + private static class ClearReferences extends Thread { + + ClearReferences() { + this.setDaemon(true); + } + + @Override + public void run() { + try { + while (true) { + Reference toclean = BufferUtils.removeCollected.remove(); + BufferUtils.trackedBuffers.remove(toclean); + } + + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/IntMap.java b/jme3-core/src/main/java/com/jme3/util/IntMap.java new file mode 100644 index 000000000..fed91be06 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/IntMap.java @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import com.jme3.util.IntMap.Entry; +import java.util.Iterator; +import java.util.Map; + +/** + * Similar to a {@link Map} except that ints are used as keys. + * + * Taken from 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("loadFactor must be greater than zero."); + } + capacity = 1; + while (capacity < initialCapacity){ + capacity <<= 1; + } + this.loadFactor = loadFactor; + this.threshold = (int) (capacity * loadFactor); + this.table = new Entry[capacity]; + this.mask = capacity - 1; + } + + @Override + public IntMap 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() { + IntMapIterator it = new IntMapIterator(); + it.beginUse(); + return it; + } + + 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() { + } + + public void beginUse(){ + cur = table[0]; + idx = 0; + el = 0; + } + + public boolean hasNext() { + return el < size; + } + + public Entry next() { + if (el >= size) + throw new IllegalStateException("No more elements!"); + + if (cur != null){ + Entry e = cur; + cur = cur.next; + el++; + return e; + } +// if (cur != null && cur.next != null){ + // if we have a current entry, continue to the next entry in the list +// cur = cur.next; +// el++; +// return cur; +// } + + do { + // either we exhausted the current entry list, or + // the entry was null. find another non-null entry. + cur = table[++idx]; + } while (cur == null); + + Entry e = cur; + cur = cur.next; + el ++; + + return e; + } + + public void remove() { + } + + } + + public static final class Entry implements Cloneable { + + final int key; + T value; + Entry next; + + Entry(int k, T v, Entry n) { + key = k; + value = v; + next = n; + } + + public int getKey(){ + return key; + } + + public T getValue(){ + return value; + } + + @Override + public String toString(){ + return key + " => " + value; + } + + @Override + public Entry clone(){ + try{ + Entry clone = (Entry) super.clone(); + clone.next = next != null ? next.clone() : null; + return clone; + }catch (CloneNotSupportedException ex){ + } + return null; + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java b/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java new file mode 100644 index 000000000..0834665d4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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) { + StringBuilder sb = new StringBuilder(); + + 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/jme3-core/src/main/java/com/jme3/util/ListMap.java b/jme3-core/src/main/java/com/jme3/util/ListMap.java new file mode 100644 index 000000000..0de4d3f75 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/ListMap.java @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.io.Serializable; +import java.util.*; +import java.util.Map.Entry; + +/** + * Implementation of a Map that favors iteration speed rather than + * get/put speed. + * + * @author Kirill Vainer + */ +public final class ListMap extends AbstractMap implements 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); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ListMapEntry other = (ListMapEntry) obj; + if (this.key != other.key && (this.key == null || !this.key.equals(other.key))) { + return false; + } + if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return (this.key != null ? this.key.hashCode() : 0) ^ + (this.value != null ? this.value.hashCode() : 0); + } + } + + private final HashMap backingMap; + private ListMapEntry[] entries; + +// private final ArrayList> entries; + + public ListMap(){ + entries = new ListMapEntry[4]; + backingMap = new HashMap(4); +// entries = new ArrayList>(); + } + + public ListMap(int initialCapacity){ + entries = new ListMapEntry[initialCapacity]; + backingMap = new HashMap(initialCapacity); +// entries = new ArrayList>(initialCapacity); + } + + public ListMap(Map map){ + entries = new ListMapEntry[map.size()]; + backingMap = new HashMap(map.size()); +// entries = new ArrayList>(map.size()); + putAll(map); + } + + @Override + public int size() { +// return entries.size(); + return backingMap.size(); + } + + public Entry getEntry(int index){ +// return entries.get(index); + return entries[index]; + } + + public V getValue(int index){ +// return entries.get(index).value; + return entries[index].value; + } + + public K getKey(int index){ +// return entries.get(index).key; + return entries[index].key; + } + + @Override + 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); +// } + + @Override + public boolean containsKey(Object key) { + return backingMap.containsKey( (K) key); +// if (key == null) +// throw new IllegalArgumentException(); +// +// for (int i = 0; i < entries.size(); i++){ +// ListMapEntry entry = entries.get(i); +// if (keyEq(entry.key, key)) +// return true; +// } +// return false; + } + + @Override + public boolean containsValue(Object value) { + return backingMap.containsValue( (V) value); +// for (int i = 0; i < entries.size(); i++){ +// if (valEq(entries.get(i).value, value)) +// return true; +// } +// return false; + } + + @Override + public V get(Object key) { + return backingMap.get( (K) key); +// if (key == null) +// throw new IllegalArgumentException(); +// +// for (int i = 0; i < entries.size(); i++){ +// ListMapEntry entry = entries.get(i); +// if (keyEq(entry.key, key)) +// return entry.value; +// } +// return null; + } + + @Override + public V put(K key, V value) { + if (backingMap.containsKey(key)){ + // set the value on the entry + int size = size(); + for (int i = 0; i < size; i++){ + ListMapEntry entry = entries[i]; + if (keyEq(entry.key, key)){ + entry.value = value; + break; + } + } + }else{ + int size = size(); + // expand list as necessary + if (size == entries.length){ + ListMapEntry[] tmpEntries = entries; + entries = new ListMapEntry[size * 2]; + System.arraycopy(tmpEntries, 0, entries, 0, size); + } + entries[size] = new ListMapEntry(key, value); + } + return backingMap.put(key, value); +// if (key == null) +// throw new IllegalArgumentException(); +// +// // check if entry exists, if yes, overwrite it with new value +// for (int i = 0; i < entries.size(); i++){ +// ListMapEntry 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; + } + + @Override + public V remove(Object key) { + V element = backingMap.remove( (K) key); + if (element != null){ + // find removed element + int size = size() + 1; // includes removed element + int removedIndex = -1; + for (int i = 0; i < size; i++){ + ListMapEntry entry = entries[i]; + if (keyEq(entry.key, key)){ + removedIndex = i; + break; + } + } + assert removedIndex >= 0; + + size --; + for (int i = removedIndex; i < size; i++){ + entries[i] = entries[i+1]; + } + } + return element; +// if (key == null) +// throw new IllegalArgumentException(); +// +// for (int i = 0; i < entries.size(); i++){ +// ListMapEntry entry = entries.get(i); +// if (keyEq(entry.key, key)){ +// return entries.remove(i).value; +// } +// } +// return null; + } + + @Override + public void putAll(Map map) { + for (Entry entry : map.entrySet()){ + put(entry.getKey(), entry.getValue()); + } + + +// 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()); +// } +// } + } + + @Override + public void clear() { + backingMap.clear(); +// entries.clear(); + } + + @Override + public ListMap clone(){ + ListMap clone = new ListMap(size()); + clone.putAll(this); + return clone; + } + + @Override + public Set keySet() { + return backingMap.keySet(); +// HashSet keys = new HashSet(); +// for (int i = 0; i < entries.size(); i++){ +// ListMapEntry entry = entries.get(i); +// keys.add(entry.key); +// } +// return keys; + } + + @Override + public Collection values() { + return backingMap.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() { + return backingMap.entrySet(); +// HashSet> entryset = new HashSet>(); +// entryset.addAll(entries); +// return entryset; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/util/LittleEndien.java b/jme3-core/src/main/java/com/jme3/util/LittleEndien.java new file mode 100644 index 000000000..ea85eccfd --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/LittleEndien.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.io.*; + +/** + * 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 extends InputStream implements DataInput { + + private BufferedInputStream in; + private BufferedReader inRead; + + /** + * Creates a new LittleEndien reader from the given input stream. The + * stream is wrapped in a BufferedReader automatically. + * @param in The input stream to read from. + */ + public LittleEndien(InputStream in) { + this.in = new BufferedInputStream(in); + inRead = new BufferedReader(new InputStreamReader(in)); + } + + public int read() throws IOException { + return in.read(); + } + + @Override + public int read(byte[] buf) throws IOException { + return in.read(buf); + } + + @Override + public int read(byte[] buf, int off, int len) throws IOException { + return in.read(buf, off, len); + } + + public int readUnsignedShort() throws IOException { + return (in.read() & 0xff) | ((in.read() & 0xff) << 8); + } + + /** + * read an unsigned int as a long + */ + public long readUInt() throws IOException { + return ((in.read() & 0xff) + | ((in.read() & 0xff) << 8) + | ((in.read() & 0xff) << 16) + | (((long) (in.read() & 0xff)) << 24)); + } + + public boolean readBoolean() throws IOException { + return (in.read() != 0); + } + + public byte readByte() throws IOException { + return (byte) in.read(); + } + + public int readUnsignedByte() throws IOException { + return in.read(); + } + + public short readShort() throws IOException { + return (short) this.readUnsignedShort(); + } + + public char readChar() throws IOException { + return (char) this.readUnsignedShort(); + } + + public int readInt() throws IOException { + return ((in.read() & 0xff) + | ((in.read() & 0xff) << 8) + | ((in.read() & 0xff) << 16) + | ((in.read() & 0xff) << 24)); + } + + public long readLong() throws IOException { + return ((in.read() & 0xff) + | ((long) (in.read() & 0xff) << 8) + | ((long) (in.read() & 0xff) << 16) + | ((long) (in.read() & 0xff) << 24) + | ((long) (in.read() & 0xff) << 32) + | ((long) (in.read() & 0xff) << 40) + | ((long) (in.read() & 0xff) << 48) + | ((long) (in.read() & 0xff) << 56)); + } + + public float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + public double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + public void readFully(byte b[]) throws IOException { + in.read(b, 0, b.length); + } + + public void readFully(byte b[], int off, int len) throws IOException { + in.read(b, off, len); + } + + public int skipBytes(int n) throws IOException { + return (int) in.skip(n); + } + + public String readLine() throws IOException { + return inRead.readLine(); + } + + public String readUTF() throws IOException { + throw new IOException("Unsupported operation"); + } + + @Override + public void close() throws IOException { + in.close(); + } + + @Override + public int available() throws IOException { + return in.available(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/MemoryUtils.java b/jme3-core/src/main/java/com/jme3/util/MemoryUtils.java new file mode 100644 index 000000000..25c5294fc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/MemoryUtils.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.lang.management.ManagementFactory; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.management.JMException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +/** + * See thread http://jmonkeyengine.org/forum/topic/monitor-direct-memory-usage-in-your-app/#post-205999 + * @author Paul Speed + */ +public class MemoryUtils { + private static MBeanServer mbeans = ManagementFactory.getPlatformMBeanServer(); + private static ObjectName directPool; + static { + try { + // Create the name reference for the direct buffer pool’s MBean + directPool = new ObjectName("java.nio:type=BufferPool,name=direct"); + } catch (MalformedObjectNameException ex) { + Logger.getLogger(MemoryUtils.class.getName()).log(Level.SEVERE, "Error creating direct pool ObjectName", ex); + } + } + + /** + * + * @return the direct memory used in byte. + */ + public static long getDirectMemoryUsage() { + try { + Long value = (Long)mbeans.getAttribute(directPool, "MemoryUsed"); + return value == null ? -1 : value; + } catch (JMException ex) { + Logger.getLogger(MemoryUtils.class.getName()).log(Level.SEVERE, "Error retrieving ‘MemoryUsed’", ex); + return -1; + } + } + + /** + * + * @return the number of direct buffer used + */ + public static long getDirectMemoryCount() { + try { + Long value = (Long)mbeans.getAttribute(directPool, "Count"); + return value == null ? -1 : value; + } catch (JMException ex) { + Logger.getLogger(MemoryUtils.class.getName()).log(Level.SEVERE, "Error retrieving ‘Count’", ex); + return -1; + } + } + + /** + * + * @return Should return the total direct memory available, result seem off + * see post http://jmonkeyengine.org/forum/topic/monitor-direct-memory-usage-in-your-app/#post-205999 + */ + public static long getDirectMemoryTotalCapacity() { + try { + Long value = (Long)mbeans.getAttribute(directPool, "TotalCapacity"); + return value == null ? -1 : value; + } catch (JMException ex) { + Logger.getLogger(MemoryUtils.class.getName()).log(Level.SEVERE, "Error retrieving ‘TotalCapacity’", ex); + return -1; + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/NativeObject.java b/jme3-core/src/main/java/com/jme3/util/NativeObject.java new file mode 100644 index 000000000..508e6623d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/NativeObject.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.nio.Buffer; + +/** + * Describes a native object. An encapsulation of a certain object + * on the native side of the graphics or audio library. + * + * This class is used to track when OpenGL and OpenAL native objects are + * collected by the garbage collector, and then invoke the proper destructor + * on the OpenGL library to delete it from memory. + */ +public abstract class NativeObject implements Cloneable { + + public static final int INVALID_ID = -1; + + protected static final int OBJTYPE_VERTEXBUFFER = 1, + OBJTYPE_TEXTURE = 2, + OBJTYPE_FRAMEBUFFER = 3, + OBJTYPE_SHADER = 4, + OBJTYPE_SHADERSOURCE = 5, + OBJTYPE_AUDIOBUFFER = 6, + OBJTYPE_AUDIOSTREAM = 7, + OBJTYPE_FILTER = 8; + + /** + * The object manager to which this NativeObject is registered to. + */ + protected NativeObjectManager objectManager = null; + + /** + * The ID of the object, usually depends on its type. + * Typically returned from calls like glGenTextures, glGenBuffers, etc. + */ + protected int id = INVALID_ID; + + /** + * 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; + + /** + * Creates a new GLObject with the given type. Should be + * called by the subclasses. + * + * @param type The type that the subclass represents. + */ + public NativeObject(){ + this.handleRef = new Object(); + } + + /** + * Protected constructor that doesn't allocate handle ref. + * This is used in subclasses for the createDestructableClone(). + */ + protected NativeObject(int id){ + this.id = id; + } + + void setNativeObjectManager(NativeObjectManager objectManager) { + this.objectManager = objectManager; + } + + /** + * Sets the ID of the NativeObject. 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 != INVALID_ID) { + throw new IllegalStateException("ID has already been set for this GL object."); + } + this.id = id; + } + + /** + * @return The ID of the object. Should not be used by user code in most + * cases. + */ + public int getId(){ + return id; + } + + /** + * Internal use only. Indicates that the object has changed + * and its state needs to be updated. + */ + public void setUpdateNeeded(){ + updateNeeded = true; + } + + /** + * Internal use only. Indicates that the state changes were applied. + */ + public void clearUpdateNeeded(){ + updateNeeded = false; + } + + /** + * Internal use only. Check if {@link #setUpdateNeeded()} was called before. + */ + public boolean isUpdateNeeded(){ + return updateNeeded; + } + + @Override + public String toString(){ + return "Native" + getClass().getSimpleName() + " " + id; + } + + /** + * This should create a deep clone. For a shallow clone, use + * createDestructableClone(). + */ + @Override + protected NativeObject clone() { + try { + NativeObject obj = (NativeObject) super.clone(); + obj.handleRef = new Object(); + obj.objectManager = null; + obj.id = INVALID_ID; + obj.updateNeeded = true; + return obj; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * Deletes any associated native {@link Buffer buffers}. + * This is necessary because it is unlikely that native buffers + * will be garbage collected naturally (due to how GC works), therefore + * the collection must be handled manually. + * + * Only implementations that manage native buffers need to override + * this method. Note that the behavior that occurs when a + * deleted native buffer is used is not defined, therefore this + * method is protected + */ + protected void deleteNativeBuffers() { + } + + /** + * Package-private version of {@link #deleteNativeBuffers() }, to be used + * from the {@link NativeObjectManager}. + */ + void deleteNativeBuffersInternal() { + deleteNativeBuffers(); + } + + /** + * Called when the GL context is restarted to reset all IDs. Prevents + * "white textures" on display restart. + */ + public abstract void resetObject(); + + /** + * Deletes the GL object from the GPU when it is no longer used. Called + * automatically by the GL object manager. + * + * @param rendererObject The renderer to be used to delete the object + */ + public abstract void deleteObject(Object rendererObject); + + /** + * Creates a shallow clone of this GL Object. The deleteObject method + * should be functional for this object. + */ + public abstract NativeObject createDestructableClone(); + + /** + * Returns a unique ID for this NativeObject. No other NativeObject shall + * have the same ID. + * + * @return unique ID for this NativeObject. + */ + public abstract long getUniqueId(); + + /** + * Reclaims native resources used by this NativeObject. + * It should be safe to call this method or even use the object + * after it has been reclaimed, unless {@link NativeObjectManager#UNSAFE} is + * set to true, in that case native buffers are also reclaimed which may + * introduce instability. + */ + public void dispose() { + if (objectManager != null) { + objectManager.enqueueUnusedObject(this); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java b/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java new file mode 100644 index 000000000..84e731962 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import com.jme3.renderer.Renderer; +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +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 NativeObjectManager { + + private static final Logger logger = Logger.getLogger(NativeObjectManager.class.getName()); + + /** + * Set to true to enable deletion of native buffers together with GL objects + * when requested. Note that usage of object after deletion could cause undefined results + * or native crashes, therefore by default this is set to false. + */ + public static boolean UNSAFE = false; + + /** + * The maximum number of objects that should be removed per frame. + * If the limit is reached, no more objects will be removed for that frame. + */ + private static final int MAX_REMOVES_PER_FRAME = 100; + + /** + * Reference queue for {@link NativeObjectRef native object references}. + */ + private ReferenceQueue refQueue = new ReferenceQueue(); + + /** + * List of currently active GLObjects. + */ + private HashMap refMap = new HashMap(); + + /** + * List of real objects requested by user for deletion. + */ + private ArrayDeque userDeletionQueue = new ArrayDeque(); + + private static class NativeObjectRef extends PhantomReference { + + private NativeObject objClone; + private WeakReference realObj; + + public NativeObjectRef(ReferenceQueue refQueue, NativeObject obj){ + super(obj.handleRef, refQueue); + assert obj.handleRef != null; + + this.realObj = new WeakReference(obj); + this.objClone = obj.createDestructableClone(); + assert objClone.getId() == obj.getId(); + } + } + + /** + * (Internal use only) Register a NativeObject with the manager. + */ + public void registerObject(NativeObject obj) { + if (obj.getId() <= 0) { + throw new IllegalArgumentException("object id must be greater than zero"); + } + + NativeObjectRef ref = new NativeObjectRef(refQueue, obj); + refMap.put(obj.getUniqueId(), ref); + + obj.setNativeObjectManager(this); + + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "Registered: {0}", new String[]{obj.toString()}); + } + } + + private void deleteNativeObject(Object rendererObject, NativeObject obj, NativeObjectRef ref, + boolean deleteGL, boolean deleteBufs) { + assert rendererObject != null; + + // "obj" is considered the real object (with buffers and everything else) + // if "ref" is null. + NativeObject realObj = ref != null ? + ref.realObj.get() : + obj; + + assert realObj == null || obj.getId() == realObj.getId(); + + if (deleteGL) { + if (obj.getId() <= 0) { + logger.log(Level.WARNING, "Object already deleted: {0}", obj.getClass().getSimpleName() + "/" + obj.getId()); + } else { + // Unregister it from cleanup list. + NativeObjectRef ref2 = refMap.remove(obj.getUniqueId()); + if (ref2 == null) { + throw new IllegalArgumentException("This NativeObject is not " + + "registered in this NativeObjectManager"); + } + + assert ref == null || ref == ref2; + + int id = obj.getId(); + + // Delete object from the GL driver + obj.deleteObject(rendererObject); + assert obj.getId() == NativeObject.INVALID_ID; + + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "Deleted: {0}", obj.getClass().getSimpleName() + "/" + id); + } + + if (realObj != null){ + // Note: make sure to reset them as well + // They may get used in a new renderer in the future + realObj.resetObject(); + } + } + } + if (deleteBufs && UNSAFE && realObj != null) { + // Only the real object has native buffers. + // The destructable clone has nothing and cannot be used in this case. + realObj.deleteNativeBuffersInternal(); + } + } + + /** + * (Internal use only) Deletes unused NativeObjects. + * Will delete at most {@link #MAX_REMOVES_PER_FRAME} objects. + * + * @param rendererObject The renderer object. + * For graphics objects, {@link Renderer} is used, for audio, {#link AudioRenderer} is used. + */ + public void deleteUnused(Object rendererObject){ + int removed = 0; + while (removed < MAX_REMOVES_PER_FRAME && !userDeletionQueue.isEmpty()) { + // Remove user requested objects. + NativeObject obj = userDeletionQueue.pop(); + deleteNativeObject(rendererObject, obj, null, true, true); + removed++; + } + while (removed < MAX_REMOVES_PER_FRAME) { + // Remove objects reclaimed by GC. + NativeObjectRef ref = (NativeObjectRef) refQueue.poll(); + if (ref == null) { + break; + } + + deleteNativeObject(rendererObject, ref.objClone, ref, true, false); + removed++; + } + if (removed >= 1) { + logger.log(Level.FINE, "NativeObjectManager: {0} native objects were removed from native", removed); + } + } + + /** + * (Internal use only) Deletes all objects. + * Must only be called when display is destroyed. + */ + public void deleteAllObjects(Object rendererObject){ + deleteUnused(rendererObject); + ArrayList refMapCopy = new ArrayList(refMap.values()); + for (NativeObjectRef ref : refMapCopy) { + deleteNativeObject(rendererObject, ref.objClone, ref, true, false); + } + assert refMap.size() == 0; + } + + /** + * Marks the given NativeObject as unused, + * to be deleted on the next frame. + * Usage of this object after deletion will cause an exception. + * Note that native buffers are only reclaimed if + * {@link #UNSAFE} is set to true. + * + * @param obj The object to mark as unused. + */ + void enqueueUnusedObject(NativeObject obj) { + userDeletionQueue.push(obj); + } + + /** + * (Internal use only) Resets all {@link NativeObject}s. + * This is typically called when the context is restarted. + */ + public void resetObjects(){ + for (NativeObjectRef ref : refMap.values()) { + // Must use the real object here, for this to be effective. + NativeObject realObj = ref.realObj.get(); + if (realObj == null) { + continue; + } + + realObj.resetObject(); + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, "Reset: {0}", realObj); + } + } + refMap.clear(); + refQueue = new ReferenceQueue(); + } + +// 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/jme3-core/src/main/java/com/jme3/util/PlaceholderAssets.java b/jme3-core/src/main/java/com/jme3/util/PlaceholderAssets.java new file mode 100644 index 000000000..8a2630e62 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/PlaceholderAssets.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import com.jme3.asset.AssetManager; +import com.jme3.audio.AudioBuffer; +import com.jme3.audio.AudioData; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import java.nio.ByteBuffer; + +public class PlaceholderAssets { + + /** + * Checkerboard of white and red squares + */ + private static final byte[] imageData = { + (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0x00, (byte)0x00, + + (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, + + (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0x00, (byte)0x00, + + (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, + }; + + public static Image getPlaceholderImage(){ + ByteBuffer tempData = BufferUtils.createByteBuffer(3 * 4 * 4); + tempData.put(imageData).flip(); + return new Image(Format.RGB8, 4, 4, tempData); + } + + public static Material getPlaceholderMaterial(AssetManager assetManager){ + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Red); + return mat; + } + + public static Spatial getPlaceholderModel(AssetManager assetManager){ + // What should be the size? Nobody knows + // the user's expected scale... + Box box = new Box(1, 1, 1); + Geometry geom = new Geometry("placeholder", box); + geom.setMaterial(getPlaceholderMaterial(assetManager)); + return geom; + } + + public static AudioData getPlaceholderAudio(){ + AudioBuffer audioBuf = new AudioBuffer(); + audioBuf.setupFormat(1, 8, 44100); + ByteBuffer bb = BufferUtils.createByteBuffer(1); + bb.put((byte)0).flip(); + audioBuf.updateData(bb); + return audioBuf; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java b/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java new file mode 100644 index 000000000..27f129f2f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.util.*; + +/** + *

      Provides a list with similar modification semantics to java.util.concurrent's + * CopyOnWriteArrayList except that it is not concurrent and also provides + * direct access to the current array. This List allows modification of the + * contents while iterating as any iterators will be looking at a snapshot of + * the list at the time they were created. Similarly, access the raw internal + * array is only presenting a snap shot and so can be safely iterated while + * the list is changing.

      + * + *

      All modifications, including set() operations will cause a copy of the + * data to be created that replaces the old version. Because this list is + * not designed for threading concurrency it further optimizes the "many modifications" + * case by buffering them as a normal ArrayList until the next time the contents + * are accessed.

      + * + *

      Normal list modification performance should be equal to ArrayList in a + * many situations and always better than CopyOnWriteArrayList. Optimum usage + * is when modifications are done infrequently or in batches... as is often the + * case in a scene graph. Read operations perform superior to all other methods + * as the array can be accessed directly.

      + * + *

      Important caveats over normal java.util.Lists:

      + *
        + *
      • Even though this class supports modifying the list, the subList() method + * returns a read-only list. This technically breaks the List contract.
      • + *
      • The ListIterators returned by this class only support the remove() + * modification method. add() and set() are not supported on the iterator. + * Even after ListIterator.remove() or Iterator.remove() is called, this change + * is not reflected in the iterator instance as it is still refering to its + * original snapshot. + *
      + * + * @version $Revision$ + * @author Paul Speed + */ +public class SafeArrayList implements List { + + // Implementing List directly to avoid accidentally acquiring + // incorrect or non-optimal behavior from AbstractList. For + // example, the default iterator() method will not work for + // this list. + + // Note: given the particular use-cases this was intended, + // it would make sense to nerf the public mutators and + // make this publicly act like a read-only list. + // SafeArrayList-specific methods could then be exposed + // for the classes like Node and Spatial to use to manage + // the list. This was the callers couldn't remove a child + // without it being detached properly, for example. + + private Class elementType; + private List buffer; + private E[] backingArray; + private int size = 0; + + public SafeArrayList(Class elementType) { + this.elementType = elementType; + } + + public SafeArrayList(Class elementType, Collection c) { + this.elementType = elementType; + addAll(c); + } + + protected final T[] createArray(Class type, int size) { + return (T[])java.lang.reflect.Array.newInstance(type, size); + } + + protected final E[] createArray(int size) { + return createArray(elementType, size); + } + + /** + * Returns a current snapshot of this List's backing array that + * is guaranteed not to change through further List manipulation. + * Changes to this array may or may not be reflected in the list and + * should be avoided. + */ + public final E[] getArray() { + if( backingArray != null ) + return backingArray; + + if( buffer == null ) { + backingArray = createArray(0); + } else { + // Only keep the array or the buffer but never both at + // the same time. 1) it saves space, 2) it keeps the rest + // of the code safer. + backingArray = buffer.toArray( createArray(buffer.size()) ); + buffer = null; + } + return backingArray; + } + + protected final List getBuffer() { + if( buffer != null ) + return buffer; + + if( backingArray == null ) { + buffer = new ArrayList(); + } else { + // Only keep the array or the buffer but never both at + // the same time. 1) it saves space, 2) it keeps the rest + // of the code safer. + buffer = new ArrayList( Arrays.asList(backingArray) ); + backingArray = null; + } + return buffer; + } + + public final int size() { + return size; + } + + public final boolean isEmpty() { + return size == 0; + } + + public boolean contains(Object o) { + return indexOf(o) >= 0; + } + + public Iterator iterator() { + return listIterator(); + } + + public Object[] toArray() { + return getArray(); + } + + public T[] toArray(T[] a) { + + E[] array = getArray(); + if (a.length < array.length) { + return (T[])Arrays.copyOf(array, array.length, a.getClass()); + } + + System.arraycopy( array, 0, a, 0, array.length ); + + if (a.length > array.length) { + a[array.length] = null; + } + + return a; + } + + public boolean add(E e) { + boolean result = getBuffer().add(e); + size = getBuffer().size(); + return result; + } + + public boolean remove(Object o) { + boolean result = getBuffer().remove(o); + size = getBuffer().size(); + return result; + } + + public boolean containsAll(Collection c) { + return Arrays.asList(getArray()).containsAll(c); + } + + public boolean addAll(Collection c) { + boolean result = getBuffer().addAll(c); + size = getBuffer().size(); + return result; + } + + public boolean addAll(int index, Collection c) { + boolean result = getBuffer().addAll(index, c); + size = getBuffer().size(); + return result; + } + + public boolean removeAll(Collection c) { + boolean result = getBuffer().removeAll(c); + size = getBuffer().size(); + return result; + } + + public boolean retainAll(Collection c) { + boolean result = getBuffer().retainAll(c); + size = getBuffer().size(); + return result; + } + + public void clear() { + getBuffer().clear(); + size = 0; + } + + public boolean equals(Object o) { + if( o == this ) + return true; + if( !(o instanceof List) ) //covers null too + return false; + List other = (List)o; + Iterator i1 = iterator(); + Iterator i2 = other.iterator(); + while( i1.hasNext() && i2.hasNext() ) { + Object o1 = i1.next(); + Object o2 = i2.next(); + if( o1 == o2 ) + continue; + if( o1 == null || !o1.equals(o2) ) + return false; + } + return !(i1.hasNext() || !i2.hasNext()); + } + + public int hashCode() { + // Exactly the hash code described in the List interface, basically + E[] array = getArray(); + int result = 1; + for( E e : array ) { + result = 31 * result + (e == null ? 0 : e.hashCode()); + } + return result; + } + + public final E get(int index) { + if( backingArray != null ) + return backingArray[index]; + if( buffer != null ) + return buffer.get(index); + throw new IndexOutOfBoundsException( "Index:" + index + ", Size:0" ); + } + + public E set(int index, E element) { + return getBuffer().set(index, element); + } + + public void add(int index, E element) { + getBuffer().add(index, element); + size = getBuffer().size(); + } + + public E remove(int index) { + E result = getBuffer().remove(index); + size = getBuffer().size(); + return result; + } + + public int indexOf(Object o) { + E[] array = getArray(); + for( int i = 0; i < array.length; i++ ) { + E element = array[i]; + if( element == o ) { + return i; + } + if( element != null && element.equals(o) ) { + return i; + } + } + return -1; + } + + public int lastIndexOf(Object o) { + E[] array = getArray(); + for( int i = array.length - 1; i >= 0; i-- ) { + E element = array[i]; + if( element == o ) { + return i; + } + if( element != null && element.equals(o) ) { + return i; + } + } + return -1; + } + + public ListIterator listIterator() { + return new ArrayIterator(getArray(), 0); + } + + public ListIterator listIterator(int index) { + return new ArrayIterator(getArray(), index); + } + + public List subList(int fromIndex, int toIndex) { + + // So far JME doesn't use subList that I can see so I'm nerfing it. + List raw = Arrays.asList(getArray()).subList(fromIndex, toIndex); + return Collections.unmodifiableList(raw); + } + + public String toString() { + + E[] array = getArray(); + if( array.length == 0 ) { + return "[]"; + } + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for( int i = 0; i < array.length; i++ ) { + if( i > 0 ) + sb.append( ", " ); + E e = array[i]; + sb.append( e == this ? "(this Collection)" : e ); + } + sb.append(']'); + return sb.toString(); + } + + protected class ArrayIterator implements ListIterator { + private E[] array; + private int next; + private int lastReturned; + + protected ArrayIterator( E[] array, int index ) { + this.array = array; + this.next = index; + this.lastReturned = -1; + } + + public boolean hasNext() { + return next != array.length; + } + + public E next() { + if( !hasNext() ) + throw new NoSuchElementException(); + lastReturned = next++; + return array[lastReturned]; + } + + public boolean hasPrevious() { + return next != 0; + } + + public E previous() { + if( !hasPrevious() ) + throw new NoSuchElementException(); + lastReturned = --next; + return array[lastReturned]; + } + + public int nextIndex() { + return next; + } + + public int previousIndex() { + return next - 1; + } + + public void remove() { + // This operation is not so easy to do but we will fake it. + // The issue is that the backing list could be completely + // different than the one this iterator is a snapshot of. + // We'll just remove(element) which in most cases will be + // correct. If the list had earlier .equals() equivalent + // elements then we'll remove one of those instead. Either + // way, none of those changes are reflected in this iterator. + SafeArrayList.this.remove( array[lastReturned] ); + } + + public void set(E e) { + throw new UnsupportedOperationException(); + } + + public void add(E e) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/SkyFactory.java b/jme3-core/src/main/java/com/jme3/util/SkyFactory.java new file mode 100644 index 000000000..9d31ebba0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/SkyFactory.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.bounding.BoundingSphere; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.TextureCubeMap; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * SkyFactory is used to create jME {@link Spatial}s that can + * be attached to the scene to display a sky image in the background. + * + * @author Kirill Vainer + */ +public class SkyFactory { + + /** + * Create a sky with radius=10 using the given cubemap or spheremap texture. + * + * For the sky to be visible, its radius must fall between the near and far + * planes of the camera's frustrum. + * + * @param assetManager from which to load materials + * @param texture to use + * @param normalScale The normal scale is multiplied by the 3D normal to get + * a texture coordinate. Use Vector3f.UNIT_XYZ to not apply and + * transformation to the normal. + * @param sphereMap determines how the texture is used:
      + *
        + *
      • true: The texture is a Texture2D with the pixels arranged for + * sphere + * mapping.
      • + *
      • false: The texture is either a TextureCubeMap or Texture2D. If it is + * a Texture2D then the image is taken from it and is inserted into a + * TextureCubeMap
      • + *
      + * @return a new spatial representing the sky, ready to be attached to the + * scene graph + */ + public static Spatial createSky(AssetManager assetManager, Texture texture, + Vector3f normalScale, boolean sphereMap) { + return createSky(assetManager, texture, normalScale, sphereMap, 10); + } + + /** + * Create a sky using the given cubemap or spheremap texture. + * + * @param assetManager from which to load materials + * @param texture to use + * @param normalScale The normal scale is multiplied by the 3D normal to get + * a texture coordinate. Use Vector3f.UNIT_XYZ to not apply and + * transformation to the normal. + * @param sphereMap determines how the texture is used:
      + *
        + *
      • true: The texture is a Texture2D with the pixels arranged for + * sphere + * mapping.
      • + *
      • false: The texture is either a TextureCubeMap or Texture2D. If it is + * a Texture2D then the image is taken from it and is inserted into a + * TextureCubeMap
      • + *
      + * @param sphereRadius the sky sphere's radius: for the sky to be visible, + * its radius must fall between the near and far planes of the camera's + * frustrum + * @return a new spatial representing the sky, ready to be attached to the + * scene graph + */ + public static Spatial createSky(AssetManager assetManager, Texture texture, + Vector3f normalScale, boolean sphereMap, int sphereRadius) { + if (texture == null) { + throw new IllegalArgumentException("texture cannot be null"); + } + final Sphere sphereMesh = new Sphere(10, 10, sphereRadius, false, true); + + Geometry sky = new Geometry("Sky", sphereMesh); + sky.setQueueBucket(Bucket.Sky); + sky.setCullHint(Spatial.CullHint.Never); + sky.setModelBound(new BoundingSphere(Float.POSITIVE_INFINITY, Vector3f.ZERO)); + + Material skyMat = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md"); + + skyMat.setVector3("NormalScale", normalScale); + if (sphereMap) { + skyMat.setBoolean("SphereMap", sphereMap); + } else if (!(texture instanceof TextureCubeMap)) { + // make sure its a cubemap + Image img = texture.getImage(); + texture = new TextureCubeMap(); + texture.setImage(img); + } + skyMat.setTexture("Texture", texture); + sky.setMaterial(skyMat); + + return sky; + } + + private static void checkImage(Image image) { +// if (image.getDepth() != 1) +// throw new IllegalArgumentException("3D/Array images not allowed"); + + if (image.getWidth() != image.getHeight()) { + throw new IllegalArgumentException("Image width and height must be the same"); + } + + if (image.getMultiSamples() != 1) { + throw new IllegalArgumentException("Multisample textures not allowed"); + } + } + + private static void checkImagesForCubeMap(Image... images) { + if (images.length == 1) { + return; + } + + Format fmt = images[0].getFormat(); + int width = images[0].getWidth(); + int height = images[0].getHeight(); + + ByteBuffer data = images[0].getData(0); + int size = data != null ? data.capacity() : 0; + + checkImage(images[0]); + + for (int i = 1; i < images.length; i++) { + Image image = images[i]; + checkImage(images[i]); + if (image.getFormat() != fmt) { + throw new IllegalArgumentException("Images must have same format"); + } + if (image.getWidth() != width || image.getHeight() != height) { + throw new IllegalArgumentException("Images must have same resolution"); + } + ByteBuffer data2 = image.getData(0); + if (data2 != null){ + if (data2.capacity() != size) { + throw new IllegalArgumentException("Images must have same size"); + } + } + } + } + + /** + * Create a cube-mapped sky with radius=10 using six textures. + * + * For the sky to be visible, its radius must fall between the near and far + * planes of the camera's frustrum. + * + * @param assetManager from which to load materials + * @param west texture for the western face of the cube + * @param east texture for the eastern face of the cube + * @param north texture for the northern face of the cube + * @param south texture for the southern face of the cube + * @param up texture for the top face of the cube + * @param down texture for the bottom face of the cube + * @param normalScale The normal scale is multiplied by the 3D normal to get + * a texture coordinate. Use Vector3f.UNIT_XYZ to not apply and + * transformation to the normal. + * @return a new spatial representing the sky, ready to be attached to the + * scene graph + */ + public static Spatial createSky(AssetManager assetManager, Texture west, + Texture east, Texture north, Texture south, Texture up, + Texture down, Vector3f normalScale) { + return createSky(assetManager, west, east, north, south, up, down, + normalScale, 10); + } + + /** + * Create a cube-mapped sky using six textures. + * + * @param assetManager from which to load materials + * @param west texture for the western face of the cube + * @param east texture for the eastern face of the cube + * @param north texture for the northern face of the cube + * @param south texture for the southern face of the cube + * @param up texture for the top face of the cube + * @param down texture for the bottom face of the cube + * @param normalScale The normal scale is multiplied by the 3D normal to get + * a texture coordinate. Use Vector3f.UNIT_XYZ to not apply and + * transformation to the normal. + * @param sphereRadius the sky sphere's radius: for the sky to be visible, + * its radius must fall between the near and far planes of the camera's + * frustrum + * @return a new spatial representing the sky, ready to be attached to the + * scene graph + */ + public static Spatial createSky(AssetManager assetManager, Texture west, + Texture east, Texture north, Texture south, Texture up, + Texture down, Vector3f normalScale, float sphereRadius) { + final Sphere sphereMesh = new Sphere(10, 10, sphereRadius, false, true); + Geometry sky = new Geometry("Sky", sphereMesh); + sky.setQueueBucket(Bucket.Sky); + sky.setCullHint(Spatial.CullHint.Never); + sky.setModelBound(new BoundingSphere(Float.POSITIVE_INFINITY, Vector3f.ZERO)); + + Image westImg = west.getImage(); + Image eastImg = east.getImage(); + Image northImg = north.getImage(); + Image southImg = south.getImage(); + Image upImg = up.getImage(); + Image downImg = down.getImage(); + + checkImagesForCubeMap(westImg, eastImg, northImg, southImg, upImg, downImg); + + Image cubeImage = new Image(westImg.getFormat(), westImg.getWidth(), westImg.getHeight(), null); + + cubeImage.addData(westImg.getData(0)); + cubeImage.addData(eastImg.getData(0)); + + cubeImage.addData(downImg.getData(0)); + cubeImage.addData(upImg.getData(0)); + + cubeImage.addData(southImg.getData(0)); + cubeImage.addData(northImg.getData(0)); + + if (westImg.getEfficentData() != null){ + // also consilidate efficient data + ArrayList efficientData = new ArrayList(6); + efficientData.add(westImg.getEfficentData()); + efficientData.add(eastImg.getEfficentData()); + efficientData.add(downImg.getEfficentData()); + efficientData.add(upImg.getEfficentData()); + efficientData.add(southImg.getEfficentData()); + efficientData.add(northImg.getEfficentData()); + cubeImage.setEfficentData(efficientData); + } + + TextureCubeMap cubeMap = new TextureCubeMap(cubeImage); + cubeMap.setAnisotropicFilter(0); + cubeMap.setMagFilter(Texture.MagFilter.Bilinear); + cubeMap.setMinFilter(Texture.MinFilter.NearestNoMipMaps); + cubeMap.setWrap(Texture.WrapMode.EdgeClamp); + + Material skyMat = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md"); + skyMat.setTexture("Texture", cubeMap); + skyMat.setVector3("NormalScale", normalScale); + sky.setMaterial(skyMat); + + return sky; + } + + public static Spatial createSky(AssetManager assetManager, Texture west, Texture east, Texture north, Texture south, Texture up, Texture down) { + return createSky(assetManager, west, east, north, south, up, down, Vector3f.UNIT_XYZ); + } + + public static Spatial createSky(AssetManager assetManager, Texture texture, boolean sphereMap) { + return createSky(assetManager, texture, Vector3f.UNIT_XYZ, sphereMap); + } + + public static Spatial createSky(AssetManager assetManager, String textureName, boolean sphereMap) { + TextureKey key = new TextureKey(textureName, true); + key.setGenerateMips(true); + key.setAsCube(!sphereMap); + Texture tex = assetManager.loadTexture(key); + return createSky(assetManager, tex, sphereMap); + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/SortUtil.java b/jme3-core/src/main/java/com/jme3/util/SortUtil.java new file mode 100644 index 000000000..51edc0adb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/SortUtil.java @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Quick and merge sort implementations that create no garbage, unlike {@link + * Arrays#sort}. The merge sort is stable, the quick sort is not. + */ +public class SortUtil { + + /** + * The size at or below which we will use insertion sort because it's + * probably faster. + */ + private static final int INSERTION_SORT_THRESHOLD = 7; + + + /** + procedure optimizedGnomeSort(a[]) + pos := 1 + last := 0 + while pos < length(a) + if (a[pos] >= a[pos-1]) + if (last != 0) + pos := last + last := 0 + end if + pos := pos + 1 + else + swap a[pos] and a[pos-1] + if (pos > 1) + if (last == 0) + last := pos + end if + pos := pos - 1 + else + pos := pos + 1 + end if + end if + end while +end procedure + */ + + public static void gsort(Object[] a, Comparator comp) { + int pos = 1; + int last = 0; + int length = a.length; + + while (pos < length){ + if ( comp.compare(a[pos], a[pos-1]) >= 0 ){ + if (last != 0){ + pos = last; + last = 0; + } + pos ++; + }else{ + Object tmp = a[pos]; + a[pos] = a[pos-1]; + a[pos-1] = tmp; + + if (pos > 1){ + if (last == 0){ + last = pos; + } + pos --; + }else{ + pos ++; + } + } + } + +// int p = 0; +// int l = a.length; +// while (p < l) { +// int pm1 = p - 1; +// if (p == 0 || comp.compare(a[p], a[pm1]) >= 0) { +// p++; +// } else { +// Object t = a[p]; +// a[p] = a[pm1]; +// a[pm1] = t; +// p--; +// } +// } + } + + private static void test(Float[] original, Float[] sorted, Comparator ic) { + long time, dt; + + time = System.nanoTime(); + for (int i = 0; i < 1000000; i++) { + System.arraycopy(original, 0, sorted, 0, original.length); + gsort(sorted, ic); + } + dt = System.nanoTime() - time; + System.out.println("GSort " + (dt/1000000.0) + " ms"); + + time = System.nanoTime(); + for (int i = 0; i < 1000000; i++) { + System.arraycopy(original, 0, sorted, 0, original.length); + qsort(sorted, ic); + } + dt = System.nanoTime() - time; + System.out.println("QSort " + (dt/1000000.0) + " ms"); + + time = System.nanoTime(); + for (int i = 0; i < 1000000; i++) { + System.arraycopy(original, 0, sorted, 0, original.length); + msort(original, sorted, ic); + } + dt = System.nanoTime() - time; + System.out.println("MSort " + (dt/1000000.0) + " ms"); + + time = System.nanoTime(); + for (int i = 0; i < 1000000; i++) { + System.arraycopy(original, 0, sorted, 0, original.length); + Arrays.sort(sorted, ic); + } + dt = System.nanoTime() - time; + System.out.println("ASort " + (dt/1000000.0) + " ms"); + } + + public static void main(String[] args) { + Comparator 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 sort + */ + public static void msort(Object[] src, Object[] dest, Comparator comp){ + msort(src, dest, 0, src.length - 1, comp); + } + + /** + * Merge sort + * + * @param src Source array + * @param dest Destination array + * @param low Index of beginning element + * @param high Index of end element + * @param comp Comparator + */ + public static void msort(Object[] src, Object[] dest, int low, int high, + Comparator comp) { + if(low < high) { + int center = (low + high) / 2; + msort(src, dest, low, center, comp); + msort(src, dest, center + 1, high, comp); + merge(src, dest, low, center + 1, high, comp); + } + } + + private static void merge(Object[] src, Object[] dest, + int low, int middle, int high, Comparator comp) { + int leftEnd = middle - 1; + int pos = low; + int numElements = high - low + 1; + + while (low <= leftEnd && middle <= high) { + if (comp.compare(src[low], src[middle]) <= 0) { + dest[pos++] = src[low++]; + } else { + dest[pos++] = src[middle++]; + } + } + + while (low <= leftEnd) { + dest[pos++] = src[low++]; + } + + while (middle <= high) { + dest[pos++] = src[middle++]; + } + + for (int i = 0; i < numElements; i++, high--) { + src[high] = dest[high]; + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java b/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java new file mode 100644 index 000000000..e982e9665 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java @@ -0,0 +1,993 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.*; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.mesh.IndexBuffer; +import static com.jme3.util.BufferUtils.*; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.DoubleBuffer; +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.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author Lex (Aleksey Nikiforov) + */ +public class TangentBinormalGenerator { + + private static final float ZERO_TOLERANCE = 0.0000001f; + private static final Logger log = Logger.getLogger( + TangentBinormalGenerator.class.getName()); + private static float toleranceDot; + public static boolean debug = false; + + static { + setToleranceAngle(45); + } + + + private static class VertexInfo { + public final Vector3f position; + public final Vector3f normal; + public final Vector2f texCoord; + public final ArrayList indices = new ArrayList(); + + public VertexInfo(Vector3f position, Vector3f normal, Vector2f texCoord) { + this.position = position; + this.normal = normal; + this.texCoord = texCoord; + } + } + + /** Collects all the triangle data for one vertex. + */ + private static class VertexData { + public final ArrayList triangles = new ArrayList(); + + public VertexData() { } + } + + /** Keeps track of tangent, binormal, and normal for one triangle. + */ + public static class TriangleData { + public final Vector3f tangent; + public final Vector3f binormal; + public final Vector3f normal; + public int[] index = new int[3]; + public int triangleOffset; + + public TriangleData(Vector3f tangent, Vector3f binormal, Vector3f normal) { + this.tangent = tangent; + this.binormal = binormal; + this.normal = normal; + } + public void setIndex(int[] index) { + for (int i = 0; i < index.length; i++) { + this.index[i] = index[i]; + } + } + } + + private static List initVertexData(int size) { + List vertices = new ArrayList(size); + for (int i = 0; i < size; i++) { + vertices.add(new VertexData()); + } + return vertices; + } + + public static void generate(Mesh mesh) { + generate(mesh, true, false); + } + + public static void generate(Spatial scene, boolean splitMirrored) { + if (scene instanceof Node) { + Node node = (Node) scene; + for (Spatial child : node.getChildren()) { + generate(child, splitMirrored); + } + } else { + Geometry geom = (Geometry) scene; + Mesh mesh = geom.getMesh(); + + // Check to ensure mesh has texcoords and normals before generating + if (mesh.getBuffer(Type.TexCoord) != null + && mesh.getBuffer(Type.Normal) != null){ + generate(geom.getMesh(),true, splitMirrored); + } + } + } + + public static void generate(Spatial scene) { + generate(scene, false); + } + + public static void generate(Mesh mesh, boolean approxTangents, boolean splitMirrored) { + int[] index = new int[3]; + Vector3f[] v = new Vector3f[3]; + Vector2f[] t = new Vector2f[3]; + for (int i = 0; i < 3; i++) { + v[i] = new Vector3f(); + t[i] = new Vector2f(); + } + + if (mesh.getBuffer(Type.Normal) == null) { + throw new IllegalArgumentException("The given mesh has no normal data!"); + } + + List vertices; + switch (mesh.getMode()) { + case Triangles: + vertices = processTriangles(mesh, index, v, t, splitMirrored); + if(splitMirrored){ + splitVertices(mesh, vertices, splitMirrored); + } + 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,splitMirrored); + + //if the mesh has a bind pose, we need to generate the bind pose for the tangent buffer + if (mesh.getBuffer(Type.BindPosePosition) != null) { + + VertexBuffer tangents = mesh.getBuffer(Type.Tangent); + if (tangents != null) { + VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent); + bindTangents.setupData(Usage.CpuOnly, + 4, + Format.Float, + BufferUtils.clone(tangents.getData())); + + if (mesh.getBuffer(Type.BindPoseTangent) != null) { + mesh.clearBuffer(Type.BindPoseTangent); + } + mesh.setBuffer(bindTangents); + tangents.setUsage(Usage.Stream); + } + } + } + + public static void generate(Mesh mesh, boolean approxTangents) { + generate(mesh, approxTangents, false); + } + + private static List processTriangles(Mesh mesh, + int[] index, Vector3f[] v, Vector2f[] t, boolean splitMirrored) { + IndexBuffer indexBuffer = mesh.getIndexBuffer(); + FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); + if (mesh.getBuffer(Type.TexCoord) == null) { + throw new IllegalArgumentException("Can only generate tangents for " + + "meshes with texture coordinates"); + } + + FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); + + List vertices = initVertexData(vertexBuffer.limit() / 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(splitMirrored){ + triData.setIndex(index); + triData.triangleOffset = i * 3 ; + } + if (triData != null) { + vertices.get(index[0]).triangles.add(triData); + vertices.get(index[1]).triangles.add(triData); + vertices.get(index[2]).triangles.add(triData); + } + } + + return vertices; + } + + //Don't remove splitmirorred boolean,It's not used right now, but i intend to + //make this method also split vertice with rotated tangent space and I'll + //add another splitRotated boolean + private static List splitVertices(Mesh mesh, List vertexData, boolean splitMirorred) { + int nbVertices = mesh.getBuffer(Type.Position).getNumElements(); + List newVertices = new ArrayList(); + Map indiceMap = new HashMap(); + FloatBuffer normalBuffer = mesh.getFloatBuffer(Type.Normal); + + for (int i = 0; i < vertexData.size(); i++) { + ArrayList triangles = vertexData.get(i).triangles; + Vector3f givenNormal = new Vector3f(); + populateFromBuffer(givenNormal, normalBuffer, i); + + ArrayList trianglesUp = new ArrayList(); + ArrayList trianglesDown = new ArrayList(); + for (int j = 0; j < triangles.size(); j++) { + TriangleData triangleData = triangles.get(j); + if(parity(givenNormal, triangleData.normal) > 0){ + trianglesUp.add(triangleData); + }else{ + trianglesDown.add(triangleData); + } + } + + //if the vertex has triangles with opposite parity it has to be split + if(!trianglesUp.isEmpty() && !trianglesDown.isEmpty()){ + log.log(Level.FINE, "Splitting vertex {0}", i); + //assigning triangle with the same parity to the original vertex + vertexData.get(i).triangles.clear(); + vertexData.get(i).triangles.addAll(trianglesUp); + + //creating a new vertex + VertexData newVert = new VertexData(); + //assigning triangles with opposite parity to it + newVert.triangles.addAll(trianglesDown); + + newVertices.add(newVert); + //keep vertex index to fix the index buffers later + indiceMap.put(nbVertices, i); + for (TriangleData tri : newVert.triangles) { + for (int j = 0; j < tri.index.length; j++) { + if(tri.index[j] == i){ + tri.index[j] = nbVertices; + } + } + } + nbVertices++; + + } + + + } + + if(!newVertices.isEmpty()){ + + //we have new vertices, we need to update the mesh's buffers. + for (Type type : VertexBuffer.Type.values()) { + //skip tangent buffer as we're gonna overwrite it later + if(type == Type.Tangent || type == Type.BindPoseTangent) continue; + VertexBuffer vb = mesh.getBuffer(type); + //Some buffer (hardware skinning ones) can be there but not + //initialized, they must be skipped. + //They'll be initialized when Hardware Skinning is engaged + if(vb==null || vb.getNumComponents() == 0) continue; + + Buffer buffer = vb.getData(); + //IndexBuffer has special treatement, only swapping the vertex indices is needed + if(type == Type.Index){ + boolean isShortBuffer = vb.getFormat() == VertexBuffer.Format.UnsignedShort; + for (VertexData vertex : newVertices) { + for (TriangleData tri : vertex.triangles) { + for (int i = 0; i < tri.index.length; i++) { + if (isShortBuffer) { + ((ShortBuffer) buffer).put(tri.triangleOffset + i, (short) tri.index[i]); + } else { + ((IntBuffer) buffer).put(tri.triangleOffset + i, tri.index[i]); + } + } + } + } + vb.setUpdateNeeded(); + }else{ + //copy the buffer in a bigger one and append nex vertices to the end + Buffer newVerts = VertexBuffer.createBuffer(vb.getFormat(), vb.getNumComponents(), nbVertices); + if (buffer != null) { + buffer.rewind(); + bulkPut(vb.getFormat(), newVerts,buffer); + + int index = vertexData.size(); + newVerts.position(vertexData.size() * vb.getNumComponents()); + for (int j = 0; j < newVertices.size(); j++) { + int oldInd = indiceMap.get(index) ; + for (int i = 0; i < vb.getNumComponents(); i++) { + putValue(vb.getFormat(), newVerts, buffer, oldInd* vb.getNumComponents() + i); + } + index++; + } + vb.updateData(newVerts); + //destroy previous buffer as it's no longer needed + destroyDirectBuffer(buffer); + } + } + } + vertexData.addAll(newVertices); + + mesh.updateCounts(); + } + + return vertexData; + } + + private static void bulkPut(VertexBuffer.Format format, Buffer buf1, Buffer buf2) { + switch (format) { + case Byte: + case Half: + case UnsignedByte: + ((ByteBuffer) buf1).put((ByteBuffer) buf2); + break; + case Short: + case UnsignedShort: + + ((ShortBuffer) buf1).put((ShortBuffer) buf2); + break; + + case Int: + case UnsignedInt: + ((IntBuffer) buf1).put((IntBuffer) buf2); + break; + case Float: + + ((FloatBuffer) buf1).put((FloatBuffer) buf2); + break; + case Double: + ((DoubleBuffer) buf1).put((DoubleBuffer) buf2); + break; + + default: + throw new UnsupportedOperationException("Unrecoginized buffer format: " + format); + } + } + + private static void putValue(VertexBuffer.Format format, Buffer buf1, Buffer buf2,int index) { + switch (format) { + case Byte: + case Half: + case UnsignedByte: + byte b = ((ByteBuffer) buf2).get(index); + ((ByteBuffer) buf1).put(b); + break; + case Short: + case UnsignedShort: + short s = ((ShortBuffer) buf2).get(index); + ((ShortBuffer) buf1).put(s); + break; + + case Int: + case UnsignedInt: + int i = ((IntBuffer) buf2).get(index); + ((IntBuffer) buf1).put(i); + break; + case Float: + float f = ((FloatBuffer) buf2).get(index); + ((FloatBuffer) buf1).put(f); + break; + case Double: + double d = ((DoubleBuffer) buf2).get(index); + ((DoubleBuffer) buf1).put(d); + break; + default: + throw new UnsupportedOperationException("Unrecoginized buffer format: " + format); + } + } + + private static List processTriangleStrip(Mesh mesh, + int[] index, Vector3f[] v, Vector2f[] t) { + IndexBuffer indexBuffer = mesh.getIndexBuffer(); + FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); + FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); + + List vertices = initVertexData(vertexBuffer.limit() / 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.get(index[0]).triangles.add(triData); + vertices.get(index[1]).triangles.add(triData); + vertices.get(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 List processTriangleFan(Mesh mesh, + int[] index, Vector3f[] v, Vector2f[] t) { + IndexBuffer indexBuffer = mesh.getIndexBuffer(); + FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); + FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); + + List vertices = initVertexData(vertexBuffer.limit() / 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.limit() / 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.get(index[0]).triangles.add(triData); + vertices.get(index[1]).triangles.add(triData); + vertices.get(index[2]).triangles.add(triData); + } + + Vector3f vTemp = v[1]; + v[1] = v[2]; + v[2] = vTemp; + + Vector2f tTemp = t[1]; + t[1] = t[2]; + t[2] = tTemp; + + index[1] = index[2]; + } + + return vertices; + } + + // check if the area is greater than zero + private static boolean isDegenerateTriangle(Vector3f a, Vector3f b, Vector3f c) { + return (a.subtract(b).cross(c.subtract(b))).lengthSquared() == 0; + } + + public static TriangleData processTriangle(int[] index, + Vector3f[] v, Vector2f[] t) { + Vector3f edge1 = new Vector3f(); + Vector3f edge2 = new Vector3f(); + Vector2f edge1uv = new Vector2f(); + Vector2f edge2uv = new Vector2f(); + + Vector3f tangent = new Vector3f(); + Vector3f binormal = new Vector3f(); + Vector3f normal = new Vector3f(); + + t[1].subtract(t[0], edge1uv); + t[2].subtract(t[0], edge2uv); + float det = edge1uv.x * edge2uv.y - edge1uv.y * edge2uv.x; + + boolean normalize = false; + if (Math.abs(det) < ZERO_TOLERANCE) { + log.log(Level.WARNING, "Colinear uv coordinates for triangle " + + "[{0}, {1}, {2}]; tex0 = [{3}, {4}], " + + "tex1 = [{5}, {6}], tex2 = [{7}, {8}]", + new Object[]{index[0], index[1], index[2], + t[0].x, t[0].y, t[1].x, t[1].y, t[2].x, t[2].y}); + det = 1; + normalize = true; + } + + v[1].subtract(v[0], edge1); + v[2].subtract(v[0], edge2); + + tangent.set(edge1); + tangent.normalizeLocal(); + binormal.set(edge2); + binormal.normalizeLocal(); + + if (Math.abs(Math.abs(tangent.dot(binormal)) - 1) + < ZERO_TOLERANCE) { + log.log(Level.WARNING, "Vertices are on the same line " + + "for triangle [{0}, {1}, {2}].", + new Object[]{index[0], index[1], index[2]}); + } + + float factor = 1 / det; + tangent.x = (edge2uv.y * edge1.x - edge1uv.y * edge2.x) * factor; + tangent.y = (edge2uv.y * edge1.y - edge1uv.y * edge2.y) * factor; + tangent.z = (edge2uv.y * edge1.z - edge1uv.y * edge2.z) * factor; + if (normalize) { + tangent.normalizeLocal(); + } + + binormal.x = (edge1uv.x * edge2.x - edge2uv.x * edge1.x) * factor; + binormal.y = (edge1uv.x * edge2.y - edge2uv.x * edge1.y) * factor; + binormal.z = (edge1uv.x * edge2.z - edge2uv.x * edge1.z) * factor; + if (normalize) { + binormal.normalizeLocal(); + } + + tangent.cross(binormal, normal); + normal.normalizeLocal(); + + return new TriangleData( + tangent, + binormal, + normal); + } + + public static void setToleranceAngle(float angle) { + if (angle < 0 || angle > 179) { + throw new IllegalArgumentException( + "The angle must be between 0 and 179 degrees."); + } + toleranceDot = FastMath.cos(angle * FastMath.DEG_TO_RAD); + } + + + private static boolean approxEqual(Vector3f u, Vector3f v) { + float tolerance = 1E-4f; + return (FastMath.abs(u.x - v.x) < tolerance) && + (FastMath.abs(u.y - v.y) < tolerance) && + (FastMath.abs(u.z - v.z) < tolerance); + } + + private static boolean approxEqual(Vector2f u, Vector2f v) { + float tolerance = 1E-4f; + return (FastMath.abs(u.x - v.x) < tolerance) && + (FastMath.abs(u.y - v.y) < tolerance); + } + + private static ArrayList linkVertices(Mesh mesh, boolean splitMirrored) { + ArrayList vertexMap = new ArrayList(); + + FloatBuffer vertexBuffer = mesh.getFloatBuffer(Type.Position); + FloatBuffer normalBuffer = mesh.getFloatBuffer(Type.Normal); + FloatBuffer texcoordBuffer = mesh.getFloatBuffer(Type.TexCoord); + + Vector3f position = new Vector3f(); + Vector3f normal = new Vector3f(); + Vector2f texCoord = new Vector2f(); + + final int size = vertexBuffer.limit() / 3; + for (int i = 0; i < size; i++) { + + populateFromBuffer(position, vertexBuffer, i); + populateFromBuffer(normal, normalBuffer, i); + populateFromBuffer(texCoord, texcoordBuffer, i); + + boolean found = false; + //Nehon 07/07/2013 + //Removed this part, joining splitted vertice to compute tangent space makes no sense to me + //separate vertice should have separate tangent space + if(!splitMirrored){ + for (int j = 0; j < vertexMap.size(); j++) { + VertexInfo vertexInfo = vertexMap.get(j); + if (approxEqual(vertexInfo.position, position) && + approxEqual(vertexInfo.normal, normal) && + approxEqual(vertexInfo.texCoord, texCoord)) + { + vertexInfo.indices.add(i); + found = true; + break; + } + } + } + if (!found) { + VertexInfo vertexInfo = new VertexInfo(position.clone(), normal.clone(), texCoord.clone()); + vertexInfo.indices.add(i); + vertexMap.add(vertexInfo); + } + } + + return vertexMap; + } + + private static void processTriangleData(Mesh mesh, List vertices, + boolean approxTangent, boolean splitMirrored) { + ArrayList vertexMap = linkVertices(mesh,splitMirrored); + + FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.size() * 4); + + ColorRGBA[] cols = null; + if (debug) { + cols = new ColorRGBA[vertices.size()]; + } + + Vector3f tangent = new Vector3f(); + Vector3f binormal = new Vector3f(); + //Vector3f normal = new Vector3f(); + Vector3f givenNormal = new Vector3f(); + + Vector3f tangentUnit = new Vector3f(); + Vector3f binormalUnit = new Vector3f(); + + for (int k = 0; k < vertexMap.size(); k++) { + float wCoord = -1; + + VertexInfo vertexInfo = vertexMap.get(k); + + givenNormal.set(vertexInfo.normal); + givenNormal.normalizeLocal(); + + TriangleData firstTriangle = vertices.get(vertexInfo.indices.get(0)).triangles.get(0); + + // check tangent and binormal consistency + tangent.set(firstTriangle.tangent); + tangent.normalizeLocal(); + binormal.set(firstTriangle.binormal); + binormal.normalizeLocal(); + + for (int i : vertexInfo.indices) { + ArrayList triangles = vertices.get(i).triangles; + + for (int j = 0; j < triangles.size(); j++) { + TriangleData triangleData = triangles.get(j); + + tangentUnit.set(triangleData.tangent); + tangentUnit.normalizeLocal(); + if (tangent.dot(tangentUnit) < toleranceDot) { + log.log(Level.WARNING, + "Angle between tangents exceeds tolerance " + + "for vertex {0}.", i); + break; + } + + if (!approxTangent) { + binormalUnit.set(triangleData.binormal); + binormalUnit.normalizeLocal(); + if (binormal.dot(binormalUnit) < toleranceDot) { + log.log(Level.WARNING, + "Angle between binormals exceeds tolerance " + + "for vertex {0}.", i); + break; + } + } + } + } + + + // find average tangent + tangent.set(0, 0, 0); + binormal.set(0, 0, 0); + + int triangleCount = 0; + for (int i : vertexInfo.indices) { + ArrayList triangles = vertices.get(i).triangles; + triangleCount += triangles.size(); + if (debug) { + cols[i] = ColorRGBA.White; + } + + for (int j = 0; j < triangles.size(); j++) { + TriangleData triangleData = triangles.get(j); + tangent.addLocal(triangleData.tangent); + binormal.addLocal(triangleData.binormal); + + } + } + + + int blameVertex = vertexInfo.indices.get(0); + + if (tangent.length() < ZERO_TOLERANCE) { + log.log(Level.WARNING, + "Shared tangent is zero for vertex {0}.", blameVertex); + // attempt to fix from binormal + if (binormal.length() >= ZERO_TOLERANCE) { + binormal.cross(givenNormal, tangent); + tangent.normalizeLocal(); + } // if all fails use the tangent from the first triangle + else { + tangent.set(firstTriangle.tangent); + } + } else { + tangent.divideLocal(triangleCount); + } + + tangentUnit.set(tangent); + tangentUnit.normalizeLocal(); + if (Math.abs(Math.abs(tangentUnit.dot(givenNormal)) - 1) + < ZERO_TOLERANCE) { + log.log(Level.WARNING, + "Normal and tangent are parallel for vertex {0}.", blameVertex); + } + + + if (!approxTangent) { + if (binormal.length() < ZERO_TOLERANCE) { + log.log(Level.WARNING, + "Shared binormal is zero for vertex {0}.", blameVertex); + // attempt to fix from tangent + if (tangent.length() >= ZERO_TOLERANCE) { + givenNormal.cross(tangent, binormal); + binormal.normalizeLocal(); + } // if all fails use the binormal from the first triangle + else { + binormal.set(firstTriangle.binormal); + } + } else { + binormal.divideLocal(triangleCount); + } + + binormalUnit.set(binormal); + binormalUnit.normalizeLocal(); + if (Math.abs(Math.abs(binormalUnit.dot(givenNormal)) - 1) + < ZERO_TOLERANCE) { + log.log(Level.WARNING, + "Normal and binormal are parallel for vertex {0}.", blameVertex); + } + + if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1) + < ZERO_TOLERANCE) { + log.log(Level.WARNING, + "Tangent and binormal are parallel for vertex {0}.", blameVertex); + } + } + + Vector3f finalTangent = new Vector3f(); + Vector3f tmp = new Vector3f(); + for (int i : vertexInfo.indices) { + if (approxTangent) { + // Gram-Schmidt orthogonalize + finalTangent.set(tangent).subtractLocal(tmp.set(givenNormal).multLocal(givenNormal.dot(tangent))); + finalTangent.normalizeLocal(); + + wCoord = tmp.set(givenNormal).crossLocal(tangent).dot(binormal) < 0f ? -1f : 1f; + + tangents.put((i * 4), finalTangent.x); + tangents.put((i * 4) + 1, finalTangent.y); + tangents.put((i * 4) + 2, finalTangent.z); + tangents.put((i * 4) + 3, wCoord); + } else { + tangents.put((i * 4), tangent.x); + tangents.put((i * 4) + 1, tangent.y); + tangents.put((i * 4) + 2, tangent.z); + tangents.put((i * 4) + 3, wCoord); + + //setInBuffer(binormal, binormals, i); + } + } + } + tangents.limit(tangents.capacity()); + // If the model already had a tangent buffer, replace it with the regenerated one + mesh.clearBuffer(Type.Tangent); + mesh.setBuffer(Type.Tangent, 4, tangents); + + + + if(mesh.isAnimated()){ + mesh.clearBuffer(Type.BindPoseNormal); + mesh.clearBuffer(Type.BindPosePosition); + mesh.clearBuffer(Type.BindPoseTangent); + mesh.generateBindPose(true); + } + + if (debug) { + writeColorBuffer( vertices, cols, mesh); + } + mesh.updateBound(); + mesh.updateCounts(); + } + + private static void writeColorBuffer(List vertices, ColorRGBA[] cols, Mesh mesh) { + FloatBuffer colors = BufferUtils.createFloatBuffer(vertices.size() * 4); + colors.rewind(); + for (ColorRGBA color : cols) { + colors.put(color.r); + colors.put(color.g); + colors.put(color.b); + colors.put(color.a); + } + mesh.clearBuffer(Type.Color); + mesh.setBuffer(Type.Color, 4, colors); + } + + private static int parity(Vector3f n1, Vector3f n) { + if (n1.dot(n) < 0) { + return -1; + } else { + return 1; + } + + } + + 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.limit() * 2); + FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.limit() / 3 * 4 * 2); + + for (int i = 0; i < vertexBuffer.limit() / 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.limit() / 3 * 6); + FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.limit() * 4); + FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.limit() / 3 * 4 * 4); + + boolean hasParity = mesh.getBuffer(Type.Tangent).getNumComponents() == 4; + float tangentW = 1; + + for (int i = 0; i < vertexBuffer.limit() / 3; i++) { + populateFromBuffer(origin, vertexBuffer, i); + populateFromBuffer(normal, normalBuffer, i); + + if (hasParity) { + tangent.x = tangentBuffer.get(i * 4); + tangent.y = tangentBuffer.get(i * 4 + 1); + tangent.z = tangentBuffer.get(i * 4 + 2); + tangentW = tangentBuffer.get(i * 4 + 3); + } else { + populateFromBuffer(tangent, tangentBuffer, i); + } + + int index = i * 4; + + int id = i * 6; + lineIndex.put(id, index); + lineIndex.put(id + 1, index + 1); + lineIndex.put(id + 2, index); + lineIndex.put(id + 3, index + 2); + lineIndex.put(id + 4, index); + lineIndex.put(id + 5, index + 3); + + setInBuffer(origin, lineVertex, index); + setInBuffer(originColor, lineColor, index); + + point.set(tangent); + point.multLocal(scale); + point.addLocal(origin); + setInBuffer(point, lineVertex, index + 1); + setInBuffer(tangentColor, lineColor, index + 1); + + // wvBinormal = cross(wvNormal, wvTangent) * -inTangent.w + + if (binormalBuffer == null) { + normal.cross(tangent, point); + point.multLocal(-tangentW); + point.normalizeLocal(); + } else { + populateFromBuffer(point, binormalBuffer, i); + } + + point.multLocal(scale); + point.addLocal(origin); + setInBuffer(point, lineVertex, index + 2); + setInBuffer(binormalColor, lineColor, index + 2); + + point.set(normal); + point.multLocal(scale); + point.addLocal(origin); + setInBuffer(point, lineVertex, index + 3); + setInBuffer(normalColor, lineColor, index + 3); + } + + lineMesh.setBuffer(Type.Index, 1, lineIndex); + lineMesh.setBuffer(Type.Position, 3, lineVertex); + lineMesh.setBuffer(Type.Color, 4, lineColor); + + lineMesh.setStatic(); + //lineMesh.setInterleaved(); + return lineMesh; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/TempVars.java b/jme3-core/src/main/java/com/jme3/util/TempVars.java new file mode 100644 index 000000000..3f0f7596d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/TempVars.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import com.jme3.collision.bih.BIHNode.BIHStackData; +import com.jme3.math.*; +import com.jme3.scene.Spatial; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; + +/** + * Temporary variables assigned to each thread. Engine classes may access + * these temp variables with TempVars.get(), all retrieved TempVars + * instances must be returned via TempVars.release(). + * This returns an available instance of the TempVar class ensuring this + * particular instance is never used elsewhere in the mean time. + */ +public class TempVars { + + /** + * Allow X instances of TempVars in a single thread. + */ + private static final int STACK_SIZE = 5; + + /** + * TempVarsStack contains a stack of TempVars. + * Every time TempVars.get() is called, a new entry is added to the stack, + * and the index incremented. + * When TempVars.release() is called, the entry is checked against + * the current instance and then the index is decremented. + */ + private static class TempVarsStack { + + int index = 0; + TempVars[] tempVars = new TempVars[STACK_SIZE]; + } + /** + * ThreadLocal to store a TempVarsStack for each thread. + * This ensures each thread has a single TempVarsStack that is + * used only in method calls in that thread. + */ + private static final ThreadLocal varsLocal = new ThreadLocal() { + + @Override + public TempVarsStack initialValue() { + return new TempVarsStack(); + } + }; + /** + * This instance of TempVars has been retrieved but not released yet. + */ + private boolean isUsed = false; + + private TempVars() { + } + + /** + * Acquire an instance of the TempVar class. + * You have to release the instance after use by calling the + * release() method. + * If more than STACK_SIZE (currently 5) instances are requested + * in a single thread then an ArrayIndexOutOfBoundsException will be thrown. + * + * @return A TempVar instance + */ + public static TempVars get() { + TempVarsStack stack = varsLocal.get(); + + TempVars instance = stack.tempVars[stack.index]; + + if (instance == null) { + // Create new + instance = new TempVars(); + + // Put it in there + stack.tempVars[stack.index] = instance; + } + + stack.index++; + + instance.isUsed = true; + + return instance; + } + + /** + * Releases this instance of TempVars. + * Once released, the contents of the TempVars are undefined. + * The TempVars must be released in the opposite order that they are retrieved, + * e.g. Acquiring vars1, then acquiring vars2, vars2 MUST be released + * first otherwise an exception will be thrown. + */ + public void release() { + if (!isUsed) { + throw new IllegalStateException("This instance of TempVars was already released!"); + } + + isUsed = false; + + TempVarsStack stack = varsLocal.get(); + + // Return it to the stack + stack.index--; + + // Check if it is actually there + if (stack.tempVars[stack.index] != this) { + throw new IllegalStateException("An instance of TempVars has not been released in a called method!"); + } + } + /** + * For interfacing with OpenGL in Renderer. + */ + public final IntBuffer intBuffer1 = BufferUtils.createIntBuffer(1); + public final IntBuffer intBuffer16 = BufferUtils.createIntBuffer(16); + public final FloatBuffer floatBuffer16 = BufferUtils.createFloatBuffer(16); + /** + * Skinning buffers + */ + public final float[] skinPositions = new float[512 * 3]; + public final float[] skinNormals = new float[512 * 3]; + //tangent buffer as 4 components by elements + public final float[] skinTangents = new float[512 * 4]; + /** + * Fetching triangle from mesh + */ + public final Triangle triangle = new Triangle(); + /** + * Color + */ + public final ColorRGBA color = new ColorRGBA(); + /** + * General vectors. + */ + public final Vector3f vect1 = new Vector3f(); + public final Vector3f vect2 = new Vector3f(); + public final Vector3f vect3 = new Vector3f(); + public final Vector3f vect4 = new Vector3f(); + public final Vector3f vect5 = new Vector3f(); + public final Vector3f vect6 = new Vector3f(); + public final Vector3f vect7 = new Vector3f(); + //seems the maximum number of vector used is 7 in com.jme3.bounding.java + public final Vector3f vect8 = new Vector3f(); + public final Vector3f vect9 = new Vector3f(); + public final Vector3f vect10 = new Vector3f(); + public final Vector4f vect4f = new Vector4f(); + public final Vector3f[] tri = {new Vector3f(), + new Vector3f(), + new Vector3f()}; + /** + * 2D vector + */ + public final Vector2f vect2d = new Vector2f(); + public final Vector2f vect2d2 = new Vector2f(); + /** + * General matrices. + */ + public final Matrix3f tempMat3 = new Matrix3f(); + public final Matrix4f tempMat4 = new Matrix4f(); + public final Matrix4f tempMat42 = new Matrix4f(); + /** + * General quaternions. + */ + public final Quaternion quat1 = new Quaternion(); + public final Quaternion quat2 = new Quaternion(); + /** + * Eigen + */ + public final Eigen3f eigen = new Eigen3f(); + /** + * Plane + */ + public final Plane plane = new Plane(); + /** + * BoundingBox ray collision + */ + public final float[] fWdU = new float[3]; + public final float[] fAWdU = new float[3]; + public final float[] fDdU = new float[3]; + public final float[] fADdU = new float[3]; + public final float[] fAWxDdU = new float[3]; + /** + * Maximum tree depth .. 32 levels?? + */ + public final Spatial[] spatialStack = new Spatial[32]; + public final float[] matrixWrite = new float[16]; + /** + * BIHTree + */ + public final float[] bihSwapTmp = new float[9]; + public final ArrayList bihStack = new ArrayList(); +} diff --git a/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java b/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java new file mode 100644 index 000000000..16968aad2 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.blockparser; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +public class BlockLanguageParser { + + private Reader reader; + private ArrayList statementStack = new ArrayList(); + private Statement lastStatement; + private int lineNumber = 1; + + private BlockLanguageParser(){ + } + + private void reset(){ + statementStack.clear(); + statementStack.add(new Statement(0, "")); + lastStatement = null; + lineNumber = 1; + } + + private void pushStatement(StringBuilder buffer){ + String content = buffer.toString().trim(); + if (content.length() > 0){ + // push last statement onto the list + lastStatement = new Statement(lineNumber, content); + + Statement parent = statementStack.get(statementStack.size()-1); + parent.addStatement(lastStatement); + + buffer.setLength(0); + } + } + + private void load(InputStream in) throws IOException{ + reset(); + + reader = new InputStreamReader(in); + + StringBuilder buffer = new StringBuilder(); + boolean insideComment = false; + char lastChar = '\0'; + + while (true){ + int ci = reader.read(); + char c = (char) ci; + if (c == '\r'){ + continue; + } + if (insideComment && c == '\n'){ + insideComment = false; + }else if (c == '/' && lastChar == '/'){ + buffer.deleteCharAt(buffer.length()-1); + insideComment = true; + pushStatement(buffer); + lastChar = '\0'; + lineNumber++; + }else if (!insideComment){ + if (ci == -1 || c == '{' || c == '}' || c == '\n' || c == ';'){ + pushStatement(buffer); + lastChar = '\0'; + if (c == '{'){ + // push last statement onto the stack + statementStack.add(lastStatement); + continue; + }else if (c == '}'){ + // pop statement from stack + statementStack.remove(statementStack.size()-1); + continue; + }else if (c == '\n'){ + lineNumber++; + }else if (ci == -1){ + break; + } + }else{ + buffer.append(c); + lastChar = c; + } + } + } + } + + public static List parse(InputStream in) throws IOException { + BlockLanguageParser parser = new BlockLanguageParser(); + parser.load(in); + return parser.statementStack.get(0).getContents(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/blockparser/Statement.java b/jme3-core/src/main/java/com/jme3/util/blockparser/Statement.java new file mode 100644 index 000000000..bf6bfcd3d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/blockparser/Statement.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.blockparser; + +import java.util.ArrayList; +import java.util.List; + +public class Statement { + + protected int lineNumber; + protected String line; + protected List contents = new ArrayList(); + + protected Statement(int lineNumber, String line) { + this.lineNumber = lineNumber; + this.line = line; + } + + protected void addStatement(Statement statement) { + contents.add(statement); + } + + protected void addStatement(int index, Statement statement) { + contents.add(index, statement); + } + + public int getLineNumber() { + return lineNumber; + } + + public String getLine() { + return line; + } + + public List getContents() { + return contents; + } + + protected String getIndent(int indent) { + return " ".substring(0, indent); + } + + protected String toString(int indent) { + StringBuilder sb = new StringBuilder(); + sb.append(getIndent(indent)); + sb.append(line); + if (contents != null) { + sb.append(" {\n"); + for (Statement statement : contents) { + sb.append(statement.toString(indent + 4)); + sb.append("\n"); + } + sb.append(getIndent(indent)); + sb.append("}"); + } + return sb.toString(); + } + + @Override + public String toString() { + return toString(0); + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/xml/SAXUtil.java b/jme3-core/src/main/java/com/jme3/util/xml/SAXUtil.java new file mode 100644 index 000000000..2bcf82167 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/xml/SAXUtil.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.xml; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +/** + * Utility methods for parsing XML data using SAX. + */ +public final class SAXUtil { + + /** + * Parses an integer from a string, if the string is null returns + * def. + * + * @param i The string to parse + * @param def The default value if the string is null + * @return + * @throws SAXException + */ + public static int parseInt(String i, int def) throws SAXException{ + if (i == null) + return def; + else{ + try { + return Integer.parseInt(i); + } catch (NumberFormatException ex){ + throw new SAXException("Expected an integer, got '"+i+"'"); + } + } + } + + public static int parseInt(String i) throws SAXException{ + if (i == null) + throw new SAXException("Expected an integer"); + else{ + try { + return Integer.parseInt(i); + } catch (NumberFormatException ex){ + throw new SAXException("Expected an integer, got '"+i+"'"); + } + } + } + + public static float parseFloat(String f, float def) throws SAXException{ + if (f == null) + return def; + else{ + try { + return Float.parseFloat(f); + } catch (NumberFormatException ex){ + throw new SAXException("Expected a decimal, got '"+f+"'"); + } + } + } + + public static float parseFloat(String f) throws SAXException{ + if (f == null) + throw new SAXException("Expected a decimal"); + else{ + try { + return Float.parseFloat(f); + } catch (NumberFormatException ex){ + throw new SAXException("Expected a decimal, got '"+f+"'"); + } + } + } + + public static boolean parseBool(String bool, boolean def) throws SAXException{ + if (bool == null || bool.equals("")) + return def; + else + return Boolean.valueOf(bool); + //else + //else + // throw new SAXException("Expected a boolean, got'"+bool+"'"); + } + + public static String parseString(String str, String def){ + if (str == null) + return def; + else + return str; + } + + public static String parseString(String str) throws SAXException{ + if (str == null) + throw new SAXException("Expected a string"); + else + return str; + } + + public static Vector3f parseVector3(Attributes attribs) throws SAXException{ + float x = parseFloat(attribs.getValue("x")); + float y = parseFloat(attribs.getValue("y")); + float z = parseFloat(attribs.getValue("z")); + return new Vector3f(x,y,z); + } + + public static ColorRGBA parseColor(Attributes attribs) throws SAXException{ + float r = parseFloat(attribs.getValue("r")); + float g = parseFloat(attribs.getValue("g")); + float b = parseFloat(attribs.getValue("b")); + return new ColorRGBA(r, g, b, 1f); + } + +} diff --git a/jme3-core/src/main/resources/10000.monkeyz b/jme3-core/src/main/resources/10000.monkeyz new file mode 100644 index 000000000..bf6a5c014 --- /dev/null +++ b/jme3-core/src/main/resources/10000.monkeyz @@ -0,0 +1,112 @@ + +. ...... ...... . ........... ............................. .................................. ........................................................ +................ . ... .. .... ... .. . ........... ....... ... ....... ........... ....... ............ ........... ....... ............ ...... +................ .... . ... .. ....... ... ........................................................................................................................... +................ .... .................. ............................................. .................................. .................................. ............... +................ ...... . ... .. .... .. .... .............::................................................. .................................. ............... +......... .. . .. ... ..... ......... ... .... ..............ID=............... ............................... ......... ..... ......... ... ......... .. +....... .... .. .. . ..............................................ZDN.. ... .......:Z ... ............................................................................. +....... .... .. .. . ...............................................MOM, .,M$. ......NM ................................................................................. +.......... ........................................................... . ..=M8ZN. ..MNMD87:,MDM+?DM8 . .......................................................................... +....... ..... ... ........... ... ...N,...... .~ZMMMMNDZZOOON7$8MOZZ$7$$$$77$DMMMMMO............... ........................................................ +......... ......... .. ......................... ... ..:NM~.. ..=$NNO8DOO8DOOOZZZZZZZ$$Z$$$$$$777$NNI~~~:,,:::~+=?77 ... .................................. ............... +....... . ........ ... ..MDOMN8Z8MMM8O8DNDDDN8OOO8OZZZZZO8$$$$$$$7777I77ODNNNMD8$ONNO, ........................................................... +....... .. .. .... ... ....... ...........?M888OOOOOO8DDDDDDDDDO8DDOZZZO888ZZ$$$$$$$7777777777777OMO?:,, ... ............................. ... ............... +......... .. ... ..............:8+... ... .=MOND8DNNNNNNDDDDDDDDDDDD88DDDDDOZZZZ$$$$$$777777777777I78MD+~:. .......................................................... +......... .. ...=I=:. ........... .~N?..... =M8DNNDNNNNNNNDDDDDDDDDDDDDDDDDOZZZZZZ$$$$$$777777777777I7I7I$NNI........................................................... +....... .. .. .. . .,DM8~. .......=, DMO,..=M88NNNNNNNNNNNDDDDDDDDDDDDDDD8OOZZZZZZ$$$$$$$777777777777IIIIII?ZNMN+,......,......................... ... ............... +......... ... ..ZMNI .... :MI.DDMMMM8ONNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDD88Z$Z$$$$$$$77777777777IIIIIIIIII+I7ZO8MM7:. ... ... .... .... ... ......... .. +......... .. ... ............... .IMMM+ . .ONM$M8NNNDDNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDD8888O$$$$$$$777777777777IIIIIIIIDODMM$=., .......................... ... ............... +....... .. ...=DMMN~.=DNND8DNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDOZZZZZZZZZZ$$$$$$$777777777777IIIIIIII?$ND+,.. ................................................ +....... .. .. .. ...=MMMMM8DNNNNNNNNNNNNNNNNNNNNNNDDDNDDDDDDDDDDD8ZZZZZZ$$$$$$$$$$77777777777IIIIIIIIIIII7M+ ......... ........... .... ... ........... .. + ......................... ... ..~=ZNNNNDNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDD888Z$$$$$$$777777777777IIIII?+IIIII?DZ.. + ......... .. OMMMMDDNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDD8ZZZ$Z$$$$$$777777777777IIIII+=+?III?$N... .. . .. + ......... .. .. =DMNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDD8OOOOZZ$$$$$$$777777777777IIIIIIIII++III?N~ .. . .. + ........... .. .. .. ~$MMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDD888888OZZ$$$$$7777777777777IIIIIIIIII????DM:....,~: + ....... .. .. .+?7NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDD88OZ$Z$$$$$$$7777777777777IIIIIIIIIIIIIII?ONNNMM?. +................... ....... .,OMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDOZZZZZZ$$$$$$$777777777777I77IIII+++++?III?8MMD?~. ................. + ......... ..................?ZDMMMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDD8888OOOOOOOOOZ$777777777777IIIIIIIIIIIIIIII?$88N8ZI.,=+7Z8DDND8DD8O$I?:... . +. ........................ ......?MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDD8888888888Z$$$$7777777777777IIIIIIIIIIIIIIII??7ZDDNDDZ$I????????++++?7OMMMNZ~.... +................ .... .. .=MMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDD8888O$$$$$$$$$7777777777777IIIIIIIIIIIIIIII??+++======+II?+78MMN87=~,.....:?O~ .. +................ .... .. ..=MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDD8O$ZZZZZZ$$$$$$$$7777777777777IIIIIIIIIIIII+=========?II??ZDMMN+........... ... ... +... ............. .. .. .ZMMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDDDOZZZZZ$$$$$$$$777777777777777IIIIIIIIIII======+?II??7NNZ~...... + ........ ........ ........$MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDD88DD8Z$Z$$$$$$$$$777777777777777IIIIIIIII?==++?II??IDMZ:.. .. + ?NMMZ: . .. .........ZMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDDD888888Z$$$$$$$$$$777777777777777IIIIIII++?III???INN=. + .....NM8++OMD?,. .........+MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDDD8888888Z$$$$$$$$$777777777777777IIIIIIIIIIIIIII8M8... + ..IM~.,...:$MN=........+MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDDD88D888888$$$$$$$$$777777777777777IIIII+++++?IIIMO7~:,:~,::, .. .. .. + ....N:.,.......7MD.. . .:MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDDDDD8888888O$$$$$$$777777777777777IIIIIIIIIIIIII??$DMMM8Z7?~+ . .. . + ...ZN,,,.......,,DM+. ,MMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDOOZZZZZZZ$$ZOO8Z$$$$$7777777777777777IIIIIIIIIIIIIINMMNO+. . .. . ... . ::~ +....M7:,,,,,,,...,.IM8. NMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDD8OZZZZZZZZ$$$$$$$$$$7777777777777777IIIIIII+?IIII?OM~..:7M+.. .......,?ZD8M? . .. + .M=:,,,,,,,,...,,,8M7MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNND888OOOOOOOOOOO88DDDDDDDDDDDDDDDDDDD8OZZZZZZZ$$$$$$$$$$777777777777777IIIIIII?+?IIII?ZMD$+::,.,,~=I7$8D8O7IODI. + ~M~::::,,,,,,,,.,,,=MMNNNNNNNNNNNNNNNNNNNNNNNNNND888888DNNO8OOOOOOOOOOOOOOOOOO8DDDDDDDDOZZZZZZZZZZZZZ$$$$$$$$$$777777777777777IIIIIIIIIIIIIII????I777$I?I????I7DN$~.. . .. .. + . +D~:~+::::::,,,,,,,,:MMNNNNNNNNNNNNNNNNNNNNNNDNN88888888O8D8O8OOOOOOOOOOOOOOOOOZZODDDDDD8ZZZZZZZZZZZZ$$$$$$$$$$7777777777777777IIIIIII??++=+++????????+??$NNO+,... + 7Z~::+=::::::::,,,,,:,8MNNNNNNNNNNNNNNNNNNNN8ON888888O88O8O88MMMMNMNN8OOOOOOOOOZZZZZO8DDDDOZZZZZZZZZZZ$$$$$$$$$$777777777777777IIIIIII?=++?I?I?I78NMMD?=,... . + ..7$=:::O?:::::::::,,,:::OMNNNNNNNNNNNNNNNNNN88ON8888OO8NMN8ND8D+++===~+IO8NNDZZZZZZZZZZZOD8DOZZZZZZZZZZ$$$$$$$$$$7777777777777777IIIIII++==+?I78NDD8OZZZ8DMMMO7$O$ +. I$=~:::O7:::::::::,,,:::ZMNNNNNNNNNNNNNNNNO88O8OO8DMOI????++$DNO:~:~~~~=====+IONDZZZZZZZZO8D8ZZZZZZZZZZ$$$$$$$$$$7777777777777777IIIIIIII??++=++?78Z,,. ..,,,... . +. I$+:~~:D87:::::::::,,,:::8NNNNNNNNNNNNNNN888888ON8I?I???+==++++Z$~~~~~~::::::~==~$MNZZZZZZZ8D8ZZZZZZZZZ$$$$$$$$$$777777777777777III?IIIIIIIIIII??=??DM=......... . + ?$+~~~:DIZ8::::::::::,:::,NNNNNNNNNNNNNNO8888ONDIIIII+=~=~~~~~~~~~~~~~:::::::::::::~?OM8ZZZZZ88ZZZZZZZZ$$$$$$$$$$$$77777777777777I++++IIIIII7$Z7I?????ZM7. . + .~Z+:~~:D7+ZM=~~~::::::::::~DNNNNNNNNNNN8OOO8OD$DIII??+?+++==~~~~~~~::::::::::::::::::::~I8ZZZZZ8ZZZZZZZ$$$$$$$$$$$$77777777777777III?++?IIII?MO8NMMD87??$MI. . + ,D+~~~:D7+++M==~~~~:::::::::MNNNNNNNNN8888ONNIIIIIIIIIIII????????++==~::::::,,,,,,,:::,,:.$DZZZZZZZZZZZZ$$$$$$$$$$$77777777777777IIIII?=+?IIIIM~....~7MM7+ZM... + .M?+~~:8I++++N+~~~~~~~:::::~ONNNNNNNNN88OODMIIIIIIIIIIIIII??????+++++++===~::::::,,..,,,,,,=N8DZZZZZZ$ZZZ$$$$$$$$$$77777777777777IIIIIII?=?II??M7. . .OM8?M,.. . + .M7+~~~O?++++=87~~~~~~~:::~:=MNNNNNNNNOOOOMIIIIIIIIIIIIIIIII???7O8O8NND8O$?~==~:::::,..,,,,,,MN7D$ZZD8$Z$$$$ZZ$$7$$77$777$$ODNMMMMDZIIIIII?+II??88,.. ...$MO8:. + .+8++~~I$++?~=+Z$:~~~~~~::~~:INNNNNNNN8OONIIIIIIIIIIIIIIIII?+=~~~~~~~~~:::~IZN8$+~::,,,..,,,,,DD~8O$ZNND$$$$$O8+7O88878MZ?+=~~~~~~~=8M$IIIII??IZN7NO. ... .7MD+.. + ..8??=~~D???===IMO~~~~~~~:~~~~NNNNNNDNDODOIIIIIIIIIIIIII?====~=~~~~~~~~:::,,,,::::~=?:,,,.,,,,.8I:7MZZ8=7NO$$$7ONZ,,,.~?OD7,.,,,,,~~~~+D8IIII?+IIN7DMD=.. . =M?. + ..?7?+~~D???=+=M77D==~~~~:~~~~NNNNNNOODO8DIIIIIIIIIIII======~~~~~:::::::::::,,,,,,:::=Z~,,.,,,,.D,,,MZDD~:+NN$$$$$8M~.,,.,,,.,,,,~~=~~=~=7M7?II??IO$. ~, ..OI + .M??==7???++$N??$8~=~~~~~~~~NNNNNNOOOOONIIIIIIIIIIIII+=====~~~~~~~~~~:::::::::,,,,:,:ID,,,.,,,.,,,.ZZDZ,::~8MO$7$$OM+.,,,,,,,.,,,,,,~==~~$MIIIIIIOD~... ?. . + .?Z??==???++MI??+O7~===~~~~~NNNNNDOOOOODIIIIIIIIIIIIIII?+++===~~~~~::::::~:::::::,,,,,~8=,,,,,,,,,,,NOM,,,,,,+DMDZ77OM7..,,,,,,,,.....,~==~MD+I?II$M:.. . + ..MZ?==+?I?+M?I?++M?===~=~~~DNNNNNOOOOOMOIIIIIIIIIIIIIIIIII???????++=~::::::~++~:::,.,,,78.,,,,,,,,,,88+,,,,,,,..,INDO$NZ,.,,,,,,,,....,=Z~NDNMNZ+?IM+... + .. ~D+?=+?I??MII??$7D========NNNNNNOOOOO8D7IIIIIIIIIIIIIIII?????????++++++=::::::==:::,,,:~M=,,,,,,,,,,7$,,,,,,,,,,,,,.=Z8MN:..,,,,,,.....,IOM,O~=MD7IM+.. ... . .. + . 7Z?+++??+M?I??M?N7====~~?NNNNNN8OOOODIIIIIIIIIIIIIIIIII???????++++++++=++=~::::~:::,,.,~MZ,,,,,,,,,.N+,,,,,,,,,,,,,,,,.~$D=............,ZM~.DI.~8N7M?. + ...M7?++?I?OI??+MI7M=====~ZNNNNNN8OOOONIIIIIIIIIIIIIIIIII??$888ZZZ$$Z8DD8$?====~:::::::,.,:?D7,,,,,,,,~$.,,,,,,,,,,,,,...,:.,,,,,,,,,,,....~8. 7: :DDMI. + ...:MI+++??IOI?7MII8++===?NNNNNNNDOOOONIIIIIIIIIIIIIIII?$D7:,.............,OMDI===~:::~::,.,:,,,.Z8,,,,,,,,,,,,,,,.,,,,,,,:~~~~~~~===:,,,,,,$8..,....+MM? .. + .O8?++???OIIZ78IZI+===8NNNNNNNDOOOO8$IIIIIIIIIIIII?Z8:.. .........,8NOOZZ$8N7==~::~:,,.,,,,,,,.,,,,,,,,,,,,,,,,,,:~~7OOI?~:.,~====+=,,,.II.... .,NM: . + ..MII+????7II7DI7Z====NNNNNNNNNOOOOONIIIIIIIIIIIIID:,. ........ .Z8OOOZZZZ$$$MO:=::~~,,.,,,,,,,,,,,,,,,,,,,,,,,~=:,...,:~=========++++~,,M=. ZM. + ........$NI+???IIIIOIDI$+++ZNNNNNNDOOOOOOODZIIIIIIIIIIII,... .... ...+8OZZ$ZI?I$Z$$7IN7~=:~=:,,,,,,,,,,,,,,,,,,,,,,,OZ,,,~==========++==++++++:7N... .?D + .. .....N7?????I7III$8?++ZONNNNNOOOOOOOOOONIIIIIIIIII?,. ..........?OZI?II~...+$ZZ$7I7N7=~~=:,,,,,,,,,,,,,,,,,,,,:+,,,:~~~~~?ODNDNDD$++++++++=~N. ,= + ..II?+??O?I77IID=+=OODDDOOOOOOO8DD$IIIII7IIIIIII..... ....ODZ7..,++~,~7ZOZZ$77I7N+==~:,,,,,,,,,,,,,,,,,,~,,.,~~~~O$OZ$$$7?.,:I+++++?=ZMZ. ... + .......=7????N?I777IZ++IOON8OOOOODN8II7?~~~~~~~~=+II?M:. ........ NDZ$7IZZOOOOO88OZZ$7II7M?==~,,,,,,,,,,,,,,,,,I:..:~~:ZI+8Z77+++I8..=+++=INMD... . + .......$I?+??D+?O777?++ZOOOOOOODNO77I=~~~~~~~~~~~~~=IID=..........?DDZ7Z$7?Z8DDD888OZ$$77I+M+==~:,,,,,,,,,,,,,,,...,~~=N~IOZI+7??:?O..=++OMMD~.... . + .. ..M??+??ZI+N77I+++OOOOOODM$77?~~~~~~~~::::::::~~~=8O,........D8D$?7?7+?ODDDDD88OZ$77II?M===:,,,,,,,,,,,,,....:~~IO.=OI,?I$$$.~I+.++?MD... .. . + .. ..NII++??N+MI7$++?OOOOOM877?~~~~~~~:::::~~:::::::~~+M+. ....,M887~=?...+$DNDDD8OOZ$7II?+N~==:,,,,,,,,,,,,,,:~~~:N..MZ$77ZOZ$=~?I.=+7M8 .. . + .. ..IOII+++?N?M$8+++IOOONM777=~~~~~:::~~~~~~~~~~~:::::::IN:.....N88$77+...=7ONNDDD8OZI?I?+=N+===:,,,,,,,,,,,,,,~~~O...DZIDO88OZ~I8I8++$MO. + .. ....N7II++++?NM7++++$OOON7777~~~~::~~~~~~~~~~~~~~~~::,,:::8N,.. ?88$++I=,+7ONDNNDD88ZIII?+?IN~~=~~,,,,,,,,,,,,,~~:$..,8O8DZ888$?IMD=++7M$. . . + .. .M7I?++++7?++++++ZONM7777I=~~~~~~~~~~~~~~~~~~~~~~:::,,:::ON:..NOZZ$ZZ$IDOOZN888O$7ZI7I?M.N~==~~~,,,,,,,,,,,::~:,...NZO88OOZ$$O$==++++I8=. +... ....M$II+++7++++++==Z8877777?=~~~~~~~~~~~~~~~~~~:::::::::,,:::IOD8N8OZ8OOO$$ZZZZZZ$$$7I$7N+.I===~~~~:,,,,,,,,,:~~:=II.Z8Z8Z$7ZN$~=====+++=ZI. +... ....ZD?I?+II++++==+=ZM777777?=~~~~~~~~~~~~~~~~~~:::::::::::,,::~+?ZNMNOOOOO8OOOZZZZ$$ZZ7N:..====~~~~~~,,,,,,,,:~~~~~::=77OO$+~========+++++ON,. + .. .. .:MIII++D=+====+78$777777I~~~~~~~~~~~~~~~~~:::::::::::::::,,::=++=?7ONMMMMMND8OODNNNMMMN$====~==OM=~O~::,,,:=887:~~~~~~~~~~==~:,,:~+++++~?N:. .. .. .. .. + ..DOIII+===~==?77M77777777~~~~~~~~~~~~~~~+DN8ZZ?7?,:::::::::,,::~++++++=====~:~====~~~~=====+78=,,,.,=,,,,,Z..,.++~~~~~::,,..,,,,,,,,,:=++IN. + .......,M7III+===+7777M77777777==~~~~~~~~~~:~M,,,......=$I:::::::::,:::~+++++==========~~~~:::::,,,,,:=8$:,,,~+.,,$M:,.,,,,,,,,,,,,,,,,,,,,,,~++?8 . ... + ..... 7M?III++I7777IM$7777777=~~~~~~~~~~~~DZ8...........O+:::::::::,:::~=+~:::::::::::::::::,:,,,,,:IZ?,,,,.8,....,,,,,,,,,,,,.............,:+=OI... + .. .. .IM7IIIII7777MNN7777777=~~~~~~~~~~~~$,,7O,. ...:O+::::::::,,::::~==~::::::::::::::,,,,,,,,,,,,,,,,~Z,,,,,,,,,,,,,,,,,,,,,,,,,,,,...:==N=.. +... .. .....=M8IIIIII8N,=M7777777?~~~~~~~~~~~:7,...:I .......,O=~::::::::,::::::~~::::::::::::,,,,,,,,,,,,,,,,:==.,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,==D. +... .. .......OM$??ZM=...M7777777I~~~~~~~~~~~:8,.....7? .........~7,:::::::::,::::::::::::::::,,,,,,,,,,,,,,,,,,,:O:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,:~O.. + ..ZN8=., . =D7777777+~~~~~~~~~~~?~,.... +8+. ..Z7::::::::::,::::::::::,,,,,,,,,,,,,,,,,,,,,,,,~O.,,,,,,,,,,,,,,,,,,,,,,,,,,::,,,=Z... ... .. . +............ ...............M$I77777I~~~~~~~~~~~:7,... ,..O~.. ....8+,:::::::::,,:::::,,:,,,,,,,,,,,,,,,,,,,,,,,:D.,,,,,,,,,,,,,,,,,,,,,,,,:7~,,,:=N... . + .............NM8777777=~~~~~~~~~~~:$+,.......:8: . ,O+,:::::::::,,,,:,,,,,,,,,,,,,,,,,,,,,,,,,,:Z.,,,,,,,,,,,,,,,,,,,,,,=8?,,,,,,MZ .. ... .. ... .. +. .. ....................ODO8777777+~~~~~~~~~~~:?7,,..... .,Z,.I.......,87,:,:::::::::,,,,,,,,,,,,,,,,,,,,,,,,,,,,I,,,,,,,,,,,,,,,,,,,,,?MZ:,,,,,,NM:..... .................... + .. .. ... ..... ?Z.IO777777=~~~~~~~~~~~~:O=.........M8..... .$Z~:::::,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:?:,,,,,,,,,,,,,,,,,+7$N.,,,,,,:$M. ... ....... . + ............... .Z8I77777I~~~~~~~~~~~~:?Z........:.=O~... . ..:O+,,:,:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:7:,,,,,,,,,,,,,,:O$~D:,,,,,,:,NM... . .. ... .. +... .. ... ................... +N777777I~~~~~~~~~~~:::Z?..........~O$..=...... ,7$~,,:,,,,,,,,,,,,,,,,,,,,,,,,,,,,:D.,,,,,,,,,,,,7Z~,D=,,,,,,:::DN,............. ............... +... .. ... .................. ~M$7777III?~~~~~~~~~:~:~~8=...........8N,...... . ~II:,,,,,,,,,,,,,,,,,,,,,,,,,,,,=?,,,,,,,,,,?Z~.,O+,,,,,,,,,=MD . ............ ............... + ....... ....................IMDN777IIIII+~~~~:~~:::::::D7,........?+7D,,. ... ... .?N?,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,=ZI,..=$=:,,,,,,,,,8M=. .... .. .. ..... .. +. .. .............. .DM~:87I7IIIIII+:~~~~~:::::::,+D~,.........:77.,......... .::87~:,,,,,,,,,,,,,,,,,,,,.:~+7?~...:$7::,,,,,,:,:?MN:. .... .. ..... .. ... .. +. .. ... .................. .:,..NZ8IIIIIIIII?~:~::::::::::::+Z7,...........:+=..?... ....,~I$ZOZ$777?III7$Z$I+:,.....:O$~,,,,,,,,,,,~NMZ. ....... ... ..... .. +. .. ................ ~,:OND$IIIIII??+~::::::::::::::~$Z?........ .~$?I ..................+ZI,,,,,,,,,,,,,~NM= . .... .. ... .. ... +....... ....................... ..=M=I8??IIIII???~:::::::::::::::,~OO7, .. ... . ?77.?. . . ...,:?Z . ?DO:,:,,,,,,,,,,:,=NM~ .... ......... ... ........... .. + .. .. ................ . .:Z.~MI7?I?I?????=::::::::::::::::::,?7OI...... ......,~?+O..~:=~:....,:$NZ~,,,:,,,,:::,::,.IMM? . ... ....... ... .. ... .. + .. .. ................ . ,. ,MMM$III???????=:::::::::::::::::::,,?7ZNO+??~.............,~~7$$7,,,:::::::::::::::,=MMM$. ... ....... ... .. ... .. +....... . .................. .:M.88?OII???????++~:::::::::::::::::::::::::,,~======?+=~::,,,,::::::::::::::::::+DMM$,.... .... .. ... .... ... + ........... ................ . 7..$8M+OMNOI???????+=~::::::::::::::::::::::::::::::::::::::::::::::::::::::,+DMM8: .. ....................... ... ............... + ................ ...8M....7MMMD7?+???++?=~::::::::::::::::::::::::::::::::::::::::::::::::7MMM8=. ... ... ... .... .. ... .. ... .. +. ................ ,M. .. :I8MMND$I++++++++=~~::::::::::::::::::::::::::::::::::::::~7NMNZ~. .. . . .... .. .. .. . +. .. .............. = .~7DMMMM8ZI+++++++?+++==~~::::::::::::::~:::~~==?$8NMMDI,... . ........... ..... .............. ........... .. + .. ... ... ...... .... ... .,=IMMMMMNNNOIII+==+?+++++++++=++??Z8DMMMMM87~. . . .. ... ... .... .. ... .. ... .. + .. ... ... ...... .... . ...,:+$77OMMMMMMMMMMMMMMMMMNDDO7=:. .. . .. ... ... .... .. ... .. ... .. +............ .................. . .. .. .....................,..... .......... ..... ...................................................... + .................................... . .. .. ....................................................................................................................... +... .. ... .. .. . .. .. . +. .. ... .................. .. .. .. . .. .. .. . .. .. . .. ... . ....... ... .. ... + ................ . .. .. . .. .. . .... .. .. . + .. .. .... .. .. .. . .. . ... .. .. .. . .. . ........................................................ +. ........................... . .. .. .. . .. .. .. ... ... ....... .. ... .. +. ........................... . .. .. .. . .. .. .. ... ... ....... .. ... .. diff --git a/jme3-core/src/main/resources/Common/MatDefs/Blur/HGaussianBlur.frag b/jme3-core/src/main/resources/Common/MatDefs/Blur/HGaussianBlur.frag new file mode 100644 index 000000000..35ba78bb9 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Blur/HGaussianBlur.frag @@ -0,0 +1,24 @@ +uniform sampler2D m_Texture; // this should hold the texture rendered by the horizontal blur pass +uniform float m_Size; +uniform float m_Scale; + +varying vec2 texCoord; + +void main(){ + float blurSize = m_Scale/m_Size; + vec4 sum = vec4(0.0); + + // blur in x (vertical) + // take nine samples, with the distance blurSize between them + sum += texture2D(m_Texture, vec2(texCoord.x- 4.0*blurSize, texCoord.y )) * 0.05; + sum += texture2D(m_Texture, vec2(texCoord.x- 3.0*blurSize, texCoord.y )) * 0.09; + sum += texture2D(m_Texture, vec2(texCoord.x - 2.0*blurSize, texCoord.y)) * 0.12; + sum += texture2D(m_Texture, vec2(texCoord.x- blurSize, texCoord.y )) * 0.15; + sum += texture2D(m_Texture, vec2(texCoord.x, texCoord.y)) * 0.16; + sum += texture2D(m_Texture, vec2(texCoord.x+ blurSize, texCoord.y )) * 0.15; + sum += texture2D(m_Texture, vec2(texCoord.x+ 2.0*blurSize, texCoord.y )) * 0.12; + sum += texture2D(m_Texture, vec2(texCoord.x+ 3.0*blurSize, texCoord.y )) * 0.09; + sum += texture2D(m_Texture, vec2(texCoord.x+ 4.0*blurSize, texCoord.y )) * 0.05; + + gl_FragColor = sum; +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Blur/HGaussianBlur.j3md b/jme3-core/src/main/resources/Common/MatDefs/Blur/HGaussianBlur.j3md new file mode 100644 index 000000000..fb5d3808a --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Blur/HGaussianBlur.j3md @@ -0,0 +1,17 @@ +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 { + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.frag b/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.frag new file mode 100644 index 000000000..b3adfd425 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.j3md b/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.j3md new file mode 100644 index 000000000..048cd9c7f --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.j3md @@ -0,0 +1,31 @@ +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 { + } + + Defines { + RESOLVE_MS : NumSamples + } + } + + Technique { + VertexShader GLSL120: Common/MatDefs/Post/Post.vert + FragmentShader GLSL120: Common/MatDefs/Blur/RadialBlur.frag + + WorldParameters { + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur15.frag b/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur15.frag new file mode 100644 index 000000000..cc403501b --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur15.frag @@ -0,0 +1,50 @@ +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +uniform float m_SampleDist; +uniform float m_SampleStrength; +uniform float m_Samples[10]; + +in vec2 texCoord; +out vec4 outFragColor; + +void main(void) +{ + // some sample positions + //float samples[10] = float[](-0.08,-0.05,-0.03,-0.02,-0.01,0.01,0.02,0.03,0.05,0.08); + + // 0.5,0.5 is the center of the screen + // so substracting texCoord from it will result in + // a vector pointing to the middle of the screen + vec2 dir = 0.5 - texCoord; + + // calculate the distance to the center of the screen + float dist = sqrt(dir.x*dir.x + dir.y*dir.y); + + // normalize the direction (reuse the distance) + dir = dir/dist; + + // this is the original colour of this fragment + // using only this would result in a nonblurred version + vec4 colorRes = getColor(m_Texture,texCoord); + + vec4 sum = colorRes; + + // take 10 additional blur samples in the direction towards + // the center of the screen + for (int i = 0; i < 10; i++){ + sum += getColor( m_Texture, texCoord + dir * m_Samples[i] * m_SampleDist ); + } + + // we have taken eleven samples + sum *= 1.0/11.0; + + // weighten the blur effect with the distance to the + // center of the screen ( further out is blurred more) + float t = dist * m_SampleStrength; + t = clamp( t ,0.0,1.0); //0 <= t <= 1 + + //Blend the original color with the averaged pixels + outFragColor =mix( colorRes, sum, t ); + +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Blur/VGaussianBlur.frag b/jme3-core/src/main/resources/Common/MatDefs/Blur/VGaussianBlur.frag new file mode 100644 index 000000000..3e20fe56d --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Blur/VGaussianBlur.j3md b/jme3-core/src/main/resources/Common/MatDefs/Blur/VGaussianBlur.j3md new file mode 100644 index 000000000..78f0c7fa8 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Blur/VGaussianBlur.j3md @@ -0,0 +1,17 @@ +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 { + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.frag b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.frag new file mode 100644 index 000000000..619682ce0 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md new file mode 100644 index 000000000..f37f6c979 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md @@ -0,0 +1,26 @@ +MaterialDef Default GUI { + + MaterialParameters { + Texture2D Texture + Color Color (Color) + Boolean VertexColor (UseVertexColor) + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Gui/Gui.vert + FragmentShader GLSL100: Common/MatDefs/Gui/Gui.frag + + WorldParameters { + WorldViewProjectionMatrix + } + + Defines { + TEXTURE : Texture + VERTEX_COLOR : VertexColor + } + } + + Technique { + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.vert b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.vert new file mode 100644 index 000000000..7640573dc --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.vert @@ -0,0 +1,27 @@ +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() { + 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/jme3-core/src/main/resources/Common/MatDefs/Hdr/LogLum.frag b/jme3-core/src/main/resources/Common/MatDefs/Hdr/LogLum.frag new file mode 100644 index 000000000..68f08e9a6 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Hdr/LogLum.j3md b/jme3-core/src/main/resources/Common/MatDefs/Hdr/LogLum.j3md new file mode 100644 index 000000000..0c4c6c889 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Hdr/ToneMap.frag b/jme3-core/src/main/resources/Common/MatDefs/Hdr/ToneMap.frag new file mode 100644 index 000000000..f22103072 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Hdr/ToneMap.j3md b/jme3-core/src/main/resources/Common/MatDefs/Hdr/ToneMap.j3md new file mode 100644 index 000000000..24fbd04ae --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.frag new file mode 100644 index 000000000..9fc7ebb8b --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md new file mode 100644 index 000000000..8e0b8d089 --- /dev/null +++ b/jme3-core/src/main/resources/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 { + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert new file mode 100644 index 000000000..0743cc1a9 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.frag new file mode 100644 index 000000000..397062455 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert new file mode 100644 index 000000000..f4ad19963 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Light/Glow.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/Glow.frag new file mode 100644 index 000000000..a18a22809 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Glow.frag @@ -0,0 +1,32 @@ + +#if defined(NEED_TEXCOORD1) + varying vec2 texCoord1; +#else + varying vec2 texCoord; +#endif + + +#ifdef HAS_GLOWMAP + uniform sampler2D m_GlowMap; +#endif + +#ifdef HAS_GLOWCOLOR + uniform vec4 m_GlowColor; +#endif + + +void main(){ + #ifdef HAS_GLOWMAP + #if defined(NEED_TEXCOORD1) + gl_FragColor = texture2D(m_GlowMap, texCoord1); + #else + gl_FragColor = texture2D(m_GlowMap, texCoord); + #endif + #else + #ifdef HAS_GLOWCOLOR + gl_FragColor = m_GlowColor; + #else + gl_FragColor = vec4(0.0); + #endif + #endif +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag new file mode 100644 index 000000000..eac42df22 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag @@ -0,0 +1,282 @@ +#import "Common/ShaderLib/Parallax.glsllib" +#import "Common/ShaderLib/Optics.glsllib" +#define ATTENUATION +//#define HQ_ATTENUATION + +varying vec2 texCoord; +#ifdef SEPARATE_TEXCOORD + varying vec2 texCoord2; +#endif + +varying vec3 AmbientSum; +varying vec4 DiffuseSum; +varying vec3 SpecularSum; + +#ifndef VERTEX_LIGHTING + uniform vec4 g_LightDirection; + //varying vec3 vPosition; + varying vec3 vViewDir; + varying vec4 vLightDir; + varying vec3 lightVec; +#else + varying vec2 vertexLightValues; +#endif + +#ifdef DIFFUSEMAP + uniform sampler2D m_DiffuseMap; +#endif + +#ifdef SPECULARMAP + uniform sampler2D m_SpecularMap; +#endif + +#ifdef PARALLAXMAP + uniform sampler2D m_ParallaxMap; +#endif +#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) + uniform float m_ParallaxHeight; +#endif + +#ifdef LIGHTMAP + uniform sampler2D m_LightMap; +#endif + +#ifdef NORMALMAP + uniform sampler2D m_NormalMap; +#else + varying vec3 vNormal; +#endif + +#ifdef ALPHAMAP + uniform sampler2D m_AlphaMap; +#endif + +#ifdef COLORRAMP + uniform sampler2D m_ColorRamp; +#endif + +uniform float m_AlphaDiscardThreshold; + +#ifndef VERTEX_LIGHTING +uniform float m_Shininess; + +#ifdef HQ_ATTENUATION +uniform vec4 g_LightPosition; +#endif + +#ifdef USE_REFLECTION + uniform float m_ReflectionPower; + uniform float m_ReflectionIntensity; + varying vec4 refVec; + + uniform ENVMAP m_EnvMap; +#endif + +float tangDot(in vec3 v1, in vec3 v2){ + float d = dot(v1,v2); + #ifdef V_TANGENT + d = 1.0 - d*d; + return step(0.0, d) * sqrt(d); + #else + return d; + #endif +} + +float lightComputeDiffuse(in vec3 norm, in vec3 lightdir, in vec3 viewdir){ + #ifdef MINNAERT + float NdotL = max(0.0, dot(norm, lightdir)); + float NdotV = max(0.0, dot(norm, viewdir)); + return NdotL * pow(max(NdotL * NdotV, 0.1), -1.0) * 0.5; + #else + return max(0.0, dot(norm, lightdir)); + #endif +} + +float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){ + // NOTE: check for shiny <= 1 removed since shininess is now + // 1.0 by default (uses matdefs default vals) + #ifdef LOW_QUALITY + // Blinn-Phong + // Note: preferably, H should be computed in the vertex shader + vec3 H = (viewdir + lightdir) * vec3(0.5); + return pow(max(tangDot(H, norm), 0.0), shiny); + #elif defined(WARDISO) + // Isotropic Ward + vec3 halfVec = normalize(viewdir + lightdir); + float NdotH = max(0.001, tangDot(norm, halfVec)); + float NdotV = max(0.001, tangDot(norm, viewdir)); + float NdotL = max(0.001, tangDot(norm, lightdir)); + float a = tan(acos(NdotH)); + float p = max(shiny/128.0, 0.001); + return NdotL * (1.0 / (4.0*3.14159265*p*p)) * (exp(-(a*a)/(p*p)) / (sqrt(NdotV * NdotL))); + #else + // Standard Phong + vec3 R = reflect(-lightdir, norm); + return pow(max(tangDot(R, viewdir), 0.0), shiny); + #endif +} + +vec2 computeLighting(in vec3 wvNorm, in vec3 wvViewDir, in vec3 wvLightDir){ + float diffuseFactor = lightComputeDiffuse(wvNorm, wvLightDir, wvViewDir); + float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, wvLightDir, m_Shininess); + + #ifdef HQ_ATTENUATION + float att = clamp(1.0 - g_LightPosition.w * length(lightVec), 0.0, 1.0); + #else + float att = vLightDir.w; + #endif + + if (m_Shininess <= 1.0) { + specularFactor = 0.0; // should be one instruction on most cards .. + } + + specularFactor *= diffuseFactor; + + return vec2(diffuseFactor, specularFactor) * vec2(att); +} +#endif + +void main(){ + vec2 newTexCoord; + + #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) + + #ifdef STEEP_PARALLAX + #ifdef NORMALMAP_PARALLAX + //parallax map is stored in the alpha channel of the normal map + newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); + #else + //parallax map is a texture + newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); + #endif + #else + #ifdef NORMALMAP_PARALLAX + //parallax map is stored in the alpha channel of the normal map + newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); + #else + //parallax map is a texture + newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); + #endif + #endif + #else + newTexCoord = texCoord; + #endif + + #ifdef DIFFUSEMAP + vec4 diffuseColor = texture2D(m_DiffuseMap, newTexCoord); + #else + vec4 diffuseColor = vec4(1.0); + #endif + + float alpha = DiffuseSum.a * diffuseColor.a; + #ifdef ALPHAMAP + alpha = alpha * texture2D(m_AlphaMap, newTexCoord).r; + #endif + if(alpha < m_AlphaDiscardThreshold){ + discard; + } + + #ifndef VERTEX_LIGHTING + float spotFallOff = 1.0; + + #if __VERSION__ >= 110 + // allow use of control flow + if(g_LightDirection.w != 0.0){ + #endif + + vec3 L = normalize(lightVec.xyz); + vec3 spotdir = normalize(g_LightDirection.xyz); + float curAngleCos = dot(-L, spotdir); + float innerAngleCos = floor(g_LightDirection.w) * 0.001; + float outerAngleCos = fract(g_LightDirection.w); + float innerMinusOuter = innerAngleCos - outerAngleCos; + spotFallOff = (curAngleCos - outerAngleCos) / innerMinusOuter; + + #if __VERSION__ >= 110 + if(spotFallOff <= 0.0){ + gl_FragColor.rgb = AmbientSum * diffuseColor.rgb; + gl_FragColor.a = alpha; + return; + }else{ + spotFallOff = clamp(spotFallOff, 0.0, 1.0); + } + } + #else + spotFallOff = clamp(spotFallOff, step(g_LightDirection.w, 0.001), 1.0); + #endif + #endif + + // *********************** + // Read from textures + // *********************** + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + vec4 normalHeight = texture2D(m_NormalMap, newTexCoord); + vec3 normal = normalize((normalHeight.xyz * vec3(2.0) - vec3(1.0))); + #ifdef LATC + normal.z = sqrt(1.0 - (normal.x * normal.x) - (normal.y * normal.y)); + #endif + //normal.y = -normal.y; + #elif !defined(VERTEX_LIGHTING) + vec3 normal = vNormal; + #if !defined(LOW_QUALITY) && !defined(V_TANGENT) + normal = normalize(normal); + #endif + #endif + + #ifdef SPECULARMAP + vec4 specularColor = texture2D(m_SpecularMap, newTexCoord); + #else + vec4 specularColor = vec4(1.0); + #endif + + #ifdef LIGHTMAP + vec3 lightMapColor; + #ifdef SEPARATE_TEXCOORD + lightMapColor = texture2D(m_LightMap, texCoord2).rgb; + #else + lightMapColor = texture2D(m_LightMap, texCoord).rgb; + #endif + specularColor.rgb *= lightMapColor; + diffuseColor.rgb *= lightMapColor; + #endif + + #ifdef VERTEX_LIGHTING + vec2 light = vertexLightValues.xy; + #ifdef COLORRAMP + light.x = texture2D(m_ColorRamp, vec2(light.x, 0.0)).r; + light.y = texture2D(m_ColorRamp, vec2(light.y, 0.0)).r; + #endif + + gl_FragColor.rgb = AmbientSum * diffuseColor.rgb + + DiffuseSum.rgb * diffuseColor.rgb * vec3(light.x) + + SpecularSum * specularColor.rgb * vec3(light.y); + #else + vec4 lightDir = vLightDir; + lightDir.xyz = normalize(lightDir.xyz); + vec3 viewDir = normalize(vViewDir); + + vec2 light = computeLighting(normal, viewDir, lightDir.xyz) * spotFallOff; + #ifdef COLORRAMP + diffuseColor.rgb *= texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb; + specularColor.rgb *= texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb; + #endif + + // Workaround, since it is not possible to modify varying variables + vec4 SpecularSum2 = vec4(SpecularSum, 1.0); + #ifdef USE_REFLECTION + vec4 refColor = Optics_GetEnvColor(m_EnvMap, refVec.xyz); + + // Interpolate light specularity toward reflection color + // Multiply result by specular map + specularColor = mix(SpecularSum2 * light.y, refColor, refVec.w) * specularColor; + + SpecularSum2 = vec4(1.0); + light.y = 1.0; + #endif + + gl_FragColor.rgb = AmbientSum * diffuseColor.rgb + + DiffuseSum.rgb * diffuseColor.rgb * vec3(light.x) + + SpecularSum2.rgb * specularColor.rgb * vec3(light.y); + #endif + gl_FragColor.a = alpha; +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md new file mode 100644 index 000000000..acfbc7e72 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md @@ -0,0 +1,333 @@ +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 + + // Alpha threshold for fragment discarding + Float AlphaDiscardThreshold (AlphaTestFallOff) + + // Normal map is in BC5/ATI2n/LATC/3Dc compression format + Boolean LATC + + // Use the provided ambient, diffuse, and specular colors + Boolean UseMaterialColors + + // Activate shading along the tangent, instead of the normal + // Requires tangent data to be available on the model. + Boolean VTangent + + // Use minnaert diffuse instead of lambert + Boolean Minnaert + + // Use ward specular instead of phong + Boolean WardIso + + // Use vertex color as an additional diffuse color. + Boolean UseVertexColor + + // Ambient color + Color Ambient (MaterialAmbient) + + // Diffuse color + Color Diffuse (MaterialDiffuse) + + // Specular color + Color Specular (MaterialSpecular) + + // Specular power/shininess + Float Shininess (MaterialShininess) : 1 + + // Diffuse map + Texture2D DiffuseMap + + // Normal map + Texture2D NormalMap + + // Specular/gloss map + Texture2D SpecularMap + + // Parallax/height map + Texture2D ParallaxMap + + //Set to true is parallax map is stored in the alpha channel of the normal map + Boolean PackedNormalParallax + + //Sets the relief height for parallax mapping + Float ParallaxHeight : 0.05 + + //Set to true to activate Steep Parallax mapping + Boolean SteepParallax + + // Texture that specifies alpha values + Texture2D AlphaMap + + // Color ramp, will map diffuse and specular values through it. + Texture2D ColorRamp + + // Texture of the glowing parts of the material + Texture2D GlowMap + + // Set to Use Lightmap + Texture2D LightMap + + // Set to use TexCoord2 for the lightmap sampling + Boolean SeparateTexCoord + + // The glow color of the object + Color GlowColor + + // Parameters for fresnel + // X = bias + // Y = scale + // Z = power + Vector3 FresnelParams + + // Env Map for reflection + TextureCubeMap EnvMap + + // the env map is a spheremap and not a cube map + Boolean EnvMapAsSphereMap + + //shadows + Int FilterMode + Boolean HardwareShadows + + Texture2D ShadowMap0 + Texture2D ShadowMap1 + Texture2D ShadowMap2 + Texture2D ShadowMap3 + //pointLights + Texture2D ShadowMap4 + Texture2D ShadowMap5 + + Float ShadowIntensity + Vector4 Splits + Vector2 FadeInfo + + Matrix4 LightViewProjectionMatrix0 + Matrix4 LightViewProjectionMatrix1 + Matrix4 LightViewProjectionMatrix2 + Matrix4 LightViewProjectionMatrix3 + //pointLight + Matrix4 LightViewProjectionMatrix4 + Matrix4 LightViewProjectionMatrix5 + Vector3 LightPos + + Float PCFEdge + Float ShadowMapSize + + // For hardware skinning + Int NumberOfBones + Matrix4Array BoneMatrices + } + + Technique { + + LightMode MultiPass + + VertexShader GLSL100: Common/MatDefs/Light/Lighting.vert + FragmentShader GLSL100: Common/MatDefs/Light/Lighting.frag + + WorldParameters { + WorldViewProjectionMatrix + NormalMatrix + WorldViewMatrix + ViewMatrix + CameraPosition + WorldMatrix + } + + Defines { + LATC : LATC + VERTEX_COLOR : UseVertexColor + VERTEX_LIGHTING : VertexLighting + ATTENUATION : Attenuation + MATERIAL_COLORS : UseMaterialColors + V_TANGENT : VTangent + MINNAERT : Minnaert + WARDISO : WardIso + LOW_QUALITY : LowQuality + HQ_ATTENUATION : HighQuality + + DIFFUSEMAP : DiffuseMap + NORMALMAP : NormalMap + SPECULARMAP : SpecularMap + PARALLAXMAP : ParallaxMap + NORMALMAP_PARALLAX : PackedNormalParallax + STEEP_PARALLAX : SteepParallax + ALPHAMAP : AlphaMap + COLORRAMP : ColorRamp + LIGHTMAP : LightMap + SEPARATE_TEXCOORD : SeparateTexCoord + + USE_REFLECTION : EnvMap + SPHERE_MAP : SphereMap + + NUM_BONES : NumberOfBones + } + } + + Technique PreShadow { + + VertexShader GLSL100 : Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL100 : Common/MatDefs/Shadow/PreShadow.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + } + + Defines { + COLOR_MAP : ColorMap + DISCARD_ALPHA : AlphaDiscardThreshold + NUM_BONES : NumberOfBones + } + + ForcedRenderState { + FaceCull Off + DepthTest On + DepthWrite On + PolyOffset 5 3 + ColorWrite Off + } + + } + + + Technique PostShadow15{ + VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert + FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + } + + Defines { + HARDWARE_SHADOWS : HardwareShadows + FILTER_MODE : FilterMode + PCFEDGE : PCFEdge + DISCARD_ALPHA : AlphaDiscardThreshold + COLOR_MAP : ColorMap + SHADOWMAP_SIZE : ShadowMapSize + FADE : FadeInfo + PSSM : Splits + POINTLIGHT : LightViewProjectionMatrix5 + NUM_BONES : NumberOfBones + } + + ForcedRenderState { + Blend Modulate + DepthWrite Off + PolyOffset -0.1 0 + } + } + + Technique PostShadow{ + VertexShader GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadow.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + } + + Defines { + HARDWARE_SHADOWS : HardwareShadows + FILTER_MODE : FilterMode + PCFEDGE : PCFEdge + DISCARD_ALPHA : AlphaDiscardThreshold + COLOR_MAP : ColorMap + SHADOWMAP_SIZE : ShadowMapSize + FADE : FadeInfo + PSSM : Splits + POINTLIGHT : LightViewProjectionMatrix5 + NUM_BONES : NumberOfBones + } + + ForcedRenderState { + Blend Modulate + DepthWrite Off + PolyOffset -0.1 0 + } + } + + Technique PreNormalPass { + + VertexShader GLSL100 : Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + NormalMatrix + } + + Defines { + DIFFUSEMAP_ALPHA : DiffuseMap + NUM_BONES : NumberOfBones + } + + } + + 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 { + LightMode FixedPipeline + } + + Technique Glow { + + VertexShader GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag + + WorldParameters { + WorldViewProjectionMatrix + } + + Defines { + NEED_TEXCOORD1 + HAS_GLOWMAP : GlowMap + HAS_GLOWCOLOR : GlowColor + + NUM_BONES : NumberOfBones + } + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert new file mode 100644 index 000000000..72323cbae --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert @@ -0,0 +1,223 @@ +#define ATTENUATION +//#define HQ_ATTENUATION + +#import "Common/ShaderLib/Skinning.glsllib" + +uniform mat4 g_WorldViewProjectionMatrix; +uniform mat4 g_WorldViewMatrix; +uniform mat3 g_NormalMatrix; +uniform mat4 g_ViewMatrix; + +uniform vec4 m_Ambient; +uniform vec4 m_Diffuse; +uniform vec4 m_Specular; +uniform float m_Shininess; + +uniform vec4 g_LightColor; +uniform vec4 g_LightPosition; +uniform vec4 g_AmbientLightColor; + +varying vec2 texCoord; +#ifdef SEPARATE_TEXCOORD + varying vec2 texCoord2; + attribute vec2 inTexCoord2; +#endif + +varying vec3 AmbientSum; +varying vec4 DiffuseSum; +varying vec3 SpecularSum; + +attribute vec3 inPosition; +attribute vec2 inTexCoord; +attribute vec3 inNormal; + +varying vec3 lightVec; +//varying vec4 spotVec; + +#ifdef VERTEX_COLOR + attribute vec4 inColor; +#endif + +#ifndef VERTEX_LIGHTING + attribute vec4 inTangent; + + #ifndef NORMALMAP + varying vec3 vNormal; + #endif + //varying vec3 vPosition; + varying vec3 vViewDir; + varying vec4 vLightDir; +#else + varying vec2 vertexLightValues; + uniform vec4 g_LightDirection; +#endif + +#ifdef USE_REFLECTION + uniform vec3 g_CameraPosition; + uniform mat4 g_WorldMatrix; + + uniform vec3 m_FresnelParams; + varying vec4 refVec; + + + /** + * Input: + * attribute inPosition + * attribute inNormal + * uniform g_WorldMatrix + * uniform g_CameraPosition + * + * Output: + * varying refVec + */ + void computeRef(in vec4 modelSpacePos){ + vec3 worldPos = (g_WorldMatrix * modelSpacePos).xyz; + + vec3 I = normalize( g_CameraPosition - worldPos ).xyz; + vec3 N = normalize( (g_WorldMatrix * vec4(inNormal, 0.0)).xyz ); + + refVec.xyz = reflect(I, N); + refVec.w = m_FresnelParams.x + m_FresnelParams.y * pow(1.0 + dot(I, N), m_FresnelParams.z); + } +#endif + +// JME3 lights in world space +void lightComputeDir(in vec3 worldPos, in vec4 color, in vec4 position, out vec4 lightDir){ + float posLight = step(0.5, color.w); + vec3 tempVec = position.xyz * sign(posLight - 0.5) - (worldPos * posLight); + lightVec = tempVec; + #ifdef ATTENUATION + float dist = length(tempVec); + lightDir.w = clamp(1.0 - position.w * dist * posLight, 0.0, 1.0); + lightDir.xyz = tempVec / vec3(dist); + #else + lightDir = vec4(normalize(tempVec), 1.0); + #endif +} + +#ifdef VERTEX_LIGHTING + float lightComputeDiffuse(in vec3 norm, in vec3 lightdir){ + return max(0.0, dot(norm, lightdir)); + } + + float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){ + if (shiny <= 1.0){ + return 0.0; + } + #ifndef LOW_QUALITY + vec3 H = (viewdir + lightdir) * vec3(0.5); + return pow(max(dot(H, norm), 0.0), shiny); + #else + return 0.0; + #endif + } + +vec2 computeLighting(in vec3 wvPos, in vec3 wvNorm, in vec3 wvViewDir, in vec4 wvLightPos){ + vec4 lightDir; + lightComputeDir(wvPos, g_LightColor, wvLightPos, lightDir); + float spotFallOff = 1.0; + if(g_LightDirection.w != 0.0){ + vec3 L=normalize(lightVec.xyz); + vec3 spotdir = normalize(g_LightDirection.xyz); + float curAngleCos = dot(-L, spotdir); + float innerAngleCos = floor(g_LightDirection.w) * 0.001; + float outerAngleCos = fract(g_LightDirection.w); + float innerMinusOuter = innerAngleCos - outerAngleCos; + spotFallOff = clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0); + } + float diffuseFactor = lightComputeDiffuse(wvNorm, lightDir.xyz); + float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, lightDir.xyz, m_Shininess); + //specularFactor *= step(0.01, diffuseFactor); + return vec2(diffuseFactor, specularFactor) * vec2(lightDir.w)*spotFallOff; + } +#endif + +void main(){ + vec4 modelSpacePos = vec4(inPosition, 1.0); + vec3 modelSpaceNorm = inNormal; + + #ifndef VERTEX_LIGHTING + vec3 modelSpaceTan = inTangent.xyz; + #endif + + #ifdef NUM_BONES + #ifndef VERTEX_LIGHTING + Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); + #else + Skinning_Compute(modelSpacePos, modelSpaceNorm); + #endif + #endif + + gl_Position = g_WorldViewProjectionMatrix * modelSpacePos; + texCoord = inTexCoord; + #ifdef SEPARATE_TEXCOORD + texCoord2 = inTexCoord2; + #endif + + vec3 wvPosition = (g_WorldViewMatrix * modelSpacePos).xyz; + vec3 wvNormal = normalize(g_NormalMatrix * modelSpaceNorm); + vec3 viewDir = normalize(-wvPosition); + + //vec4 lightColor = g_LightColor[gl_InstanceID]; + //vec4 lightPos = g_LightPosition[gl_InstanceID]; + //vec4 wvLightPos = (g_ViewMatrix * vec4(lightPos.xyz, lightColor.w)); + //wvLightPos.w = lightPos.w; + + vec4 wvLightPos = (g_ViewMatrix * vec4(g_LightPosition.xyz,clamp(g_LightColor.w,0.0,1.0))); + wvLightPos.w = g_LightPosition.w; + vec4 lightColor = g_LightColor; + + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + vec3 wvTangent = normalize(g_NormalMatrix * modelSpaceTan); + vec3 wvBinormal = cross(wvNormal, wvTangent); + + mat3 tbnMat = mat3(wvTangent, wvBinormal * -inTangent.w,wvNormal); + + //vPosition = wvPosition * tbnMat; + //vViewDir = viewDir * tbnMat; + vViewDir = -wvPosition * tbnMat; + lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir); + vLightDir.xyz = (vLightDir.xyz * tbnMat).xyz; + #elif !defined(VERTEX_LIGHTING) + vNormal = wvNormal; + + //vPosition = wvPosition; + vViewDir = viewDir; + + lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir); + + #ifdef V_TANGENT + vNormal = normalize(g_NormalMatrix * inTangent.xyz); + vNormal = -cross(cross(vLightDir.xyz, vNormal), vNormal); + #endif + #endif + + //computing spot direction in view space and unpacking spotlight cos +// spotVec = (g_ViewMatrix * vec4(g_LightDirection.xyz, 0.0) ); +// spotVec.w = floor(g_LightDirection.w) * 0.001; +// lightVec.w = fract(g_LightDirection.w); + + lightColor.w = 1.0; + #ifdef MATERIAL_COLORS + AmbientSum = (m_Ambient * g_AmbientLightColor).rgb; + DiffuseSum = m_Diffuse * lightColor; + SpecularSum = (m_Specular * lightColor).rgb; + #else + AmbientSum = vec3(0.2, 0.2, 0.2) * g_AmbientLightColor.rgb; // Default: ambient color is dark gray + DiffuseSum = lightColor; + SpecularSum = vec3(0.0); + #endif + + #ifdef VERTEX_COLOR + AmbientSum *= inColor.rgb; + DiffuseSum *= inColor; + #endif + + #ifdef VERTEX_LIGHTING + vertexLightValues = computeLighting(wvPosition, wvNormal, viewDir, wvLightPos); + #endif + + #ifdef USE_REFLECTION + computeRef(modelSpacePos); + #endif +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.frag new file mode 100644 index 000000000..272f100be --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.j3md new file mode 100644 index 000000000..6b74c3074 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.j3md @@ -0,0 +1,20 @@ +MaterialDef Colored Textured { + + MaterialParameters { + Texture2D ColorMap + Color Color (Color) + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Misc/ColoredTextured.vert + FragmentShader GLSL100: Common/MatDefs/Misc/ColoredTextured.frag + + WorldParameters { + WorldViewProjectionMatrix + } + } + + Technique { + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.vert new file mode 100644 index 000000000..572d84191 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.frag new file mode 100644 index 000000000..08cd2a3bf --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.frag @@ -0,0 +1,22 @@ +#ifdef USE_TEXTURE +uniform sampler2D m_Texture; +varying vec4 texCoord; +#endif + +varying vec4 color; + +void main(){ + if (color.a <= 0.01) + discard; + + #ifdef USE_TEXTURE + #ifdef POINT_SPRITE + vec2 uv = mix(texCoord.xy, texCoord.zw, gl_PointCoord.xy); + #else + vec2 uv = texCoord.xy; + #endif + gl_FragColor = texture2D(m_Texture, uv) * color; + #else + gl_FragColor = color; + #endif +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.j3md new file mode 100644 index 000000000..80b45914b --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.j3md @@ -0,0 +1,142 @@ +MaterialDef Point Sprite { + + MaterialParameters { + Texture2D Texture + Float Quadratic + Boolean PointSprite + + //only used for soft particles + Texture2D DepthTexture + Float Softness + Int NumSamplesDepth + + // Texture of the glowing parts of the material + Texture2D GlowMap + // The glow color of the object + Color GlowColor + } + + Technique { + + VertexShader GLSL100 : Common/MatDefs/Misc/Particle.vert + FragmentShader GLSL120 : Common/MatDefs/Misc/Particle.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + WorldMatrix + CameraPosition + } + + RenderState { + Blend AlphaAdditive + DepthWrite Off + PointSprite On + // AlphaTestFalloff 0.01 + } + + Defines { + USE_TEXTURE : Texture + POINT_SPRITE : PointSprite + } + } + + Technique { + + VertexShader GLSL100 : Common/MatDefs/Misc/Particle.vert + FragmentShader GLSL100 : Common/MatDefs/Misc/Particle.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + WorldMatrix + CameraPosition + } + + RenderState { + Blend AlphaAdditive + DepthWrite Off + } + + Defines { + USE_TEXTURE : Texture + } + } + + Technique SoftParticles{ + + VertexShader GLSL100 : Common/MatDefs/Misc/SoftParticle.vert + FragmentShader GLSL100 : Common/MatDefs/Misc/SoftParticle.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + WorldMatrix + CameraPosition + } + + RenderState { + Blend AlphaAdditive + DepthWrite Off + } + + Defines { + USE_TEXTURE : Texture + } + } + + Technique SoftParticles15{ + + VertexShader GLSL100 : Common/MatDefs/Misc/SoftParticle.vert + FragmentShader GLSL150 : Common/MatDefs/Misc/SoftParticle15.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + WorldMatrix + CameraPosition + } + + RenderState { + Blend AlphaAdditive + DepthWrite Off + PointSprite On + } + + Defines { + USE_TEXTURE : Texture + POINT_SPRITE : PointSprite + RESOLVE_DEPTH_MS : NumSamplesDepth + } + } + + Technique { + RenderState { + Blend AlphaAdditive + // DepthWrite Off + // AlphaTestFalloff 0.01 + } + } + + Technique Glow { + + VertexShader GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag + + WorldParameters { + WorldViewProjectionMatrix + } + + Defines { + NEED_TEXCOORD1 + HAS_GLOWMAP : GlowMap + HAS_GLOWCOLOR : GlowColor + } + + RenderState { + PointSprite On + Blend AlphaAdditive + DepthWrite Off + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert new file mode 100644 index 000000000..9c2733615 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.frag new file mode 100644 index 000000000..93e488230 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md new file mode 100644 index 000000000..db480b75f --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.vert new file mode 100644 index 000000000..3813043b0 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.frag new file mode 100644 index 000000000..9f5940304 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.frag @@ -0,0 +1,11 @@ +#import "Common/ShaderLib/Optics.glsllib" + +uniform ENVMAP m_Texture; + +varying vec3 direction; + +void main() { + vec3 dir = normalize(direction); + gl_FragColor = Optics_GetEnvColor(m_Texture, dir); +} + diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.j3md new file mode 100644 index 000000000..515777428 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.j3md @@ -0,0 +1,27 @@ +MaterialDef Sky Plane { + MaterialParameters { + TextureCubeMap Texture + Boolean SphereMap + Vector3 NormalScale + } + Technique { + VertexShader GLSL100: Common/MatDefs/Misc/Sky.vert + FragmentShader GLSL100: Common/MatDefs/Misc/Sky.frag + + RenderState { + FaceCull Off + } + + WorldParameters { + ViewMatrix + ProjectionMatrix + WorldMatrix + } + + Defines { + SPHERE_MAP : SphereMap + } + } + Technique { + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.vert new file mode 100644 index 000000000..c65753d1e --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.vert @@ -0,0 +1,25 @@ +uniform mat4 g_ViewMatrix; +uniform mat4 g_ProjectionMatrix; +uniform mat4 g_WorldMatrix; + +uniform vec3 m_NormalScale; + +attribute vec3 inPosition; +attribute vec3 inNormal; + +varying vec3 direction; + +void main(){ + // set w coordinate to 0 + vec4 pos = vec4(inPosition, 0.0); + + // compute rotation only for view matrix + pos = g_ViewMatrix * pos; + + // now find projection + pos.w = 1.0; + gl_Position = g_ProjectionMatrix * pos; + + vec4 normal = vec4(inNormal * m_NormalScale, 0.0); + direction = (g_WorldMatrix * normal).xyz; +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle.frag new file mode 100644 index 000000000..d3108b54e --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle.frag @@ -0,0 +1,50 @@ +uniform sampler2D m_DepthTexture; +uniform float m_Softness; // Power used in the contrast function +varying vec2 vPos; // Position of the pixel +varying vec2 projPos;// z and w valus in projection space + +#ifdef USE_TEXTURE +uniform sampler2D m_Texture; +varying vec4 texCoord; +#endif + +varying vec4 color; + +float Contrast(float d){ + float val = clamp( 2.0*( (d > 0.5) ? 1.0-d : d ), 0.0, 1.0); + float a = 0.5 * pow(val, m_Softness); + return (d > 0.5) ? 1.0 - a : a; +} + +float stdDiff(float d){ + return clamp((d)*m_Softness,0.0,1.0); +} + + +void main(){ + if (color.a <= 0.01) + discard; + + vec4 c = vec4(1.0,1.0,1.0,1.0);//color; + #ifdef USE_TEXTURE + #ifdef POINT_SPRITE + vec2 uv = mix(texCoord.xy, texCoord.zw, gl_PointCoord.xy); + #else + vec2 uv = texCoord.xy; + #endif + c = texture2D(m_Texture, uv) * color; + #endif + + + float depthv = texture2D(m_DepthTexture, vPos).x*2.0-1.0; // Scene depth + depthv*=projPos.y; + float particleDepth = projPos.x; + + float zdiff =depthv-particleDepth; + if(zdiff<=0.0){ + discard; + } + // Computes alpha based on the particles distance to the rest of the scene + c.a = c.a * stdDiff(zdiff);// Contrast(zdiff); + gl_FragColor =c; +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle.vert new file mode 100644 index 000000000..4f0dfd5bf --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle.vert @@ -0,0 +1,53 @@ +uniform mat4 g_WorldViewProjectionMatrix; + +attribute vec3 inPosition; +attribute vec4 inColor; +attribute vec4 inTexCoord; + +varying vec4 color; +// z and w values in projection space +varying vec2 projPos; +varying vec2 vPos; // Position of the pixel in clip space + + + +#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; + + projPos = gl_Position.zw; + // projPos.x = 0.5 * (projPos.x) + 0.5; + + // Transforms the vPosition data to the range [0,1] + vPos = (gl_Position.xy / gl_Position.w + 1.0) / 2.0; + + #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/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle15.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle15.frag new file mode 100644 index 000000000..3e2f0955e --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle15.frag @@ -0,0 +1,51 @@ +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform DEPTHTEXTURE m_DepthTexture; +uniform float m_Softness; // Power used in the contrast function +in vec2 vPos; // Position of the pixel +in vec2 projPos;// z and w valus in projection space + +#ifdef USE_TEXTURE +uniform sampler2D m_Texture; +in vec4 texCoord; +#endif + +in vec4 color; +out vec4 outColor; + +float Contrast(in float d){ + float val = clamp( 2.0*( (d > 0.5) ? 1.0-d : d ), 0.0, 1.0); + float a = 0.5 * pow(val, m_Softness); + return (d > 0.5) ? 1.0 - a : a; +} + +float stdDiff(in float d){ + return clamp((d)*m_Softness,0.0,1.0); +} + + +void main(){ + if (color.a <= 0.01) + discard; + + outColor = vec4(1.0,1.0,1.0,1.0);//color; + #ifdef USE_TEXTURE + #ifdef POINT_SPRITE + vec2 uv = mix(texCoord.xy, texCoord.zw, gl_PointCoord.xy); + #else + vec2 uv = texCoord.xy; + #endif + outColor = getColor(m_Texture, uv) * color; + #endif + + float depthv = getDepth(m_DepthTexture, vPos).x*2.0-1.0; // Scene depth + depthv*=projPos.y; + float particleDepth = projPos.x; + + float zdiff =depthv-particleDepth; + if(zdiff<=0.0){ + discard; + } + // Computes alpha based on the particles distance to the rest of the scene + outColor.a = outColor.a * stdDiff(zdiff);// Contrast(zdiff); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.frag new file mode 100644 index 000000000..ce8219b6e --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.frag @@ -0,0 +1,48 @@ +#if defined(HAS_GLOWMAP) || defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD)) + #define NEED_TEXCOORD1 +#endif + +#if defined(DISCARD_ALPHA) + uniform float m_AlphaDiscardThreshold; +#endif + +uniform vec4 m_Color; +uniform sampler2D m_ColorMap; +uniform sampler2D m_LightMap; + +varying vec2 texCoord1; +varying vec2 texCoord2; + +varying vec4 vertColor; + +void main(){ + vec4 color = vec4(1.0); + + #ifdef HAS_COLORMAP + color *= texture2D(m_ColorMap, texCoord1); + #endif + + #ifdef HAS_VERTEXCOLOR + color *= vertColor; + #endif + + #ifdef HAS_COLOR + color *= m_Color; + #endif + + #ifdef HAS_LIGHTMAP + #ifdef SEPARATE_TEXCOORD + color.rgb *= texture2D(m_LightMap, texCoord2).rgb; + #else + color.rgb *= texture2D(m_LightMap, texCoord1).rgb; + #endif + #endif + + #if defined(DISCARD_ALPHA) + if(color.a < m_AlphaDiscardThreshold){ + discard; + } + #endif + + gl_FragColor = color; +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md new file mode 100644 index 000000000..aae534582 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md @@ -0,0 +1,191 @@ +MaterialDef Unshaded { + + MaterialParameters { + Texture2D ColorMap + Texture2D LightMap + Color Color (Color) + Boolean VertexColor (UseVertexColor) + Boolean SeparateTexCoord + + // Texture of the glowing parts of the material + Texture2D GlowMap + // The glow color of the object + Color GlowColor + + // For hardware skinning + Int NumberOfBones + Matrix4Array BoneMatrices + + // Alpha threshold for fragment discarding + Float AlphaDiscardThreshold (AlphaTestFallOff) + + //Shadows + Int FilterMode + Boolean HardwareShadows + + Texture2D ShadowMap0 + Texture2D ShadowMap1 + Texture2D ShadowMap2 + Texture2D ShadowMap3 + //pointLights + Texture2D ShadowMap4 + Texture2D ShadowMap5 + + Float ShadowIntensity + Vector4 Splits + Vector2 FadeInfo + + Matrix4 LightViewProjectionMatrix0 + Matrix4 LightViewProjectionMatrix1 + Matrix4 LightViewProjectionMatrix2 + Matrix4 LightViewProjectionMatrix3 + //pointLight + Matrix4 LightViewProjectionMatrix4 + Matrix4 LightViewProjectionMatrix5 + Vector3 LightPos + + Float PCFEdge + + Float ShadowMapSize + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL100: Common/MatDefs/Misc/Unshaded.frag + + WorldParameters { + WorldViewProjectionMatrix + } + + Defines { + SEPARATE_TEXCOORD : SeparateTexCoord + HAS_COLORMAP : ColorMap + HAS_LIGHTMAP : LightMap + HAS_VERTEXCOLOR : VertexColor + HAS_COLOR : Color + NUM_BONES : NumberOfBones + DISCARD_ALPHA : AlphaDiscardThreshold + } + } + + Technique { + } + + Technique PreNormalPass { + + VertexShader GLSL100 : Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + NormalMatrix + } + + Defines { + NUM_BONES : NumberOfBones + } + } + + Technique PreShadow { + + VertexShader GLSL100 : Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL100 : Common/MatDefs/Shadow/PreShadow.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + } + + Defines { + COLOR_MAP : ColorMap + DISCARD_ALPHA : AlphaDiscardThreshold + NUM_BONES : NumberOfBones + } + + ForcedRenderState { + FaceCull Off + DepthTest On + DepthWrite On + PolyOffset 5 3 + ColorWrite Off + } + + } + + + Technique PostShadow15{ + VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert + FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + } + + Defines { + HARDWARE_SHADOWS : HardwareShadows + FILTER_MODE : FilterMode + PCFEDGE : PCFEdge + DISCARD_ALPHA : AlphaDiscardThreshold + COLOR_MAP : ColorMap + SHADOWMAP_SIZE : ShadowMapSize + FADE : FadeInfo + PSSM : Splits + POINTLIGHT : LightViewProjectionMatrix5 + NUM_BONES : NumberOfBones + } + + ForcedRenderState { + Blend Modulate + DepthWrite Off + PolyOffset -0.1 0 + } + } + + Technique PostShadow{ + VertexShader GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadow.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + } + + Defines { + HARDWARE_SHADOWS : HardwareShadows + FILTER_MODE : FilterMode + PCFEDGE : PCFEdge + DISCARD_ALPHA : AlphaDiscardThreshold + COLOR_MAP : ColorMap + SHADOWMAP_SIZE : ShadowMapSize + FADE : FadeInfo + PSSM : Splits + POINTLIGHT : LightViewProjectionMatrix5 + NUM_BONES : NumberOfBones + } + + ForcedRenderState { + Blend Modulate + DepthWrite Off + PolyOffset -0.1 0 + } + } + + Technique Glow { + + VertexShader GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag + + WorldParameters { + WorldViewProjectionMatrix + } + + Defines { + NEED_TEXCOORD1 + HAS_GLOWMAP : GlowMap + HAS_GLOWCOLOR : GlowColor + NUM_BONES : NumberOfBones + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert new file mode 100644 index 000000000..1d47bb395 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert @@ -0,0 +1,37 @@ +#import "Common/ShaderLib/Skinning.glsllib" + +uniform mat4 g_WorldViewProjectionMatrix; +attribute vec3 inPosition; + +#if defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD)) + #define NEED_TEXCOORD1 +#endif + +attribute vec2 inTexCoord; +attribute vec2 inTexCoord2; +attribute vec4 inColor; + +varying vec2 texCoord1; +varying vec2 texCoord2; + +varying vec4 vertColor; + +void main(){ + #ifdef NEED_TEXCOORD1 + texCoord1 = inTexCoord; + #endif + + #ifdef SEPARATE_TEXCOORD + texCoord2 = inTexCoord2; + #endif + + #ifdef HAS_VERTEXCOLOR + vertColor = inColor; + #endif + + vec4 modelSpacePos = vec4(inPosition, 1.0); + #ifdef NUM_BONES + Skinning_Compute(modelSpacePos); + #endif + gl_Position = g_WorldViewProjectionMatrix * modelSpacePos; +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/UnshadedNodes.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/UnshadedNodes.j3md new file mode 100644 index 000000000..f9fb05288 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/UnshadedNodes.j3md @@ -0,0 +1,97 @@ +MaterialDef UnshadedNodes { + + MaterialParameters { + Texture2D ColorMap + Texture2D LightMap + Color Color (Color) + Boolean VertexColor (UseVertexColor) + Boolean SeparateTexCoord + + // Alpha threshold for fragment discarding + Float AlphaDiscardThreshold (AlphaTestFallOff) + + // For hardware skinning + Int NumberOfBones + Matrix4Array BoneMatrices + + } + + Technique { + + WorldParameters { + WorldViewProjectionMatrix + //used for fog + WorldViewMatrix + } + + VertexShaderNodes{ + ShaderNode GpuSkinning{ + Definition: BasicGPUSkinning : Common/MatDefs/ShaderNodes/HardwareSkinning/HardwareSkinning.j3sn + Condition : NumberOfBones + InputMapping{ + modelPosition = Global.position; + boneMatrices = MatParam.BoneMatrices + boneWeight = Attr.inHWBoneWeight + boneIndex = Attr.inHWBoneIndex + } + OutputMapping{ + Global.position = modModelPosition + } + } + ShaderNode UnshadedVert{ + Definition: CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn + InputMapping{ + worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix + modelPosition = Global.position.xyz + texCoord1 = Attr.inTexCoord: ColorMap || (LightMap && !SeparateTexCoord) + texCoord2 = Attr.inTexCoord2: SeparateTexCoord + vertColor = Attr.inColor: VertexColor + } + OutputMapping{ + Global.position = projPosition + } + } + } + FragmentShaderNodes{ + ShaderNode UnshadedFrag{ + Definition: Unshaded : Common/MatDefs/ShaderNodes/Common/Unshaded.j3sn + InputMapping{ + texCoord = UnshadedVert.texCoord1: ColorMap + vertColor = UnshadedVert.vertColor: VertexColor + matColor = MatParam.Color: Color + colorMap = MatParam.ColorMap: ColorMap + color = Global.outColor + } + OutputMapping{ + Global.outColor = color + } + } + + ShaderNode AlphaDiscardThreshold{ + Definition: AlphaDiscard : Common/MatDefs/ShaderNodes/Basic/AlphaDiscard.j3sn + Condition : AlphaDiscardThreshold + InputMapping{ + alpha = Global.outColor.a + threshold = MatParam.AlphaDiscardThreshold + } + } + ShaderNode LightMap{ + Definition: LightMapping : Common/MatDefs/ShaderNodes/LightMapping/LightMapping.j3sn + Condition: LightMap + InputMapping{ + texCoord = UnshadedVert.texCoord1: !SeparateTexCoord + texCoord = UnshadedVert.texCoord2: SeparateTexCoord + lightMap = MatParam.LightMap + color = Global.outColor + } + OutputMapping{ + Global.outColor = color + } + } + + } + + } + + +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/AlphaDiscard.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/AlphaDiscard.j3sn new file mode 100644 index 000000000..00b30aa92 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/AlphaDiscard.j3sn @@ -0,0 +1,18 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition AlphaDiscard { + Type: Fragment + Shader GLSL100: Common/MatDefs/ShaderNodes/Basic/alphaDiscard.frag + Documentation{ + Discards the current pixel if its alpha channel value is below the given threshold + @input alpha the alpha value + @input threshold the discard threshold + } + Input { + float alpha + float threshold + } + Output { + None + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/AttributeToVarying.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/AttributeToVarying.j3sn new file mode 100644 index 000000000..ca2a7909a --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/AttributeToVarying.j3sn @@ -0,0 +1,29 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition AttributeToVarying{ + Type : Vertex + Shader GLSL100: Common/MatDefs/ShaderNodes/Basic/null.vert + Documentation{ + This node can pass an attribute value to a varying value. + @input floatVariable a float attribute + @input vec2Variable a vec2 attribute + @input vec3Variable a vec3 attribute + @input vec4Variable a vec4 attribute + @output floatVariable a float varying + @output vec2Variable a vec2 varying + @output vec3Variable a vec3 varying + @output vec4Variable a vec4 varying + } + Input { + float floatVariable + vec2 vec2Variable + vec3 vec3Variable + vec4 vec4Variable + } + Output { + float floatVariable + vec2 vec2Variable + vec3 vec3Variable + vec4 vec4Variable + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorMix.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorMix.j3sn new file mode 100644 index 000000000..414615b6a --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorMix.j3sn @@ -0,0 +1,21 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition ColorMix { + Type: Fragment + Shader GLSL100: Common/MatDefs/ShaderNodes/Basic/colorMix.frag + Documentation{ + mixes two colors according to a mix factor + @input color1 the first color to mix + @input color2 the second color to mix + @input factor the mix factor (from 0.0 to 1.0) fpr more information see the gsls mix function + @output outColor the mixed color + } + Input { + vec4 color1 + vec4 color2 + float factor + } + Output { + vec4 outColor + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorMult.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorMult.j3sn new file mode 100644 index 000000000..4d1f44328 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorMult.j3sn @@ -0,0 +1,19 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition ColorMult { + Type: Fragment + Shader GLSL100: Common/MatDefs/ShaderNodes/Basic/colorMult.frag + Documentation{ + Multiplies two colors + @input color1 the first color + @input color2 the second color + @output outColor the resulting color + } + Input { + vec4 color1 + vec4 color2 + } + Output { + vec4 outColor + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn new file mode 100644 index 000000000..b4ccd3640 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn @@ -0,0 +1,19 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition TextureFetch { + Type: Fragment + Shader GLSL100: Common/MatDefs/ShaderNodes/Basic/texture.frag + Documentation{ + Fetches a color value in the given texture acording to given texture coordinates + @input texture the texture to read + @input texCoord the texture coordinates + @output outColor the fetched color + } + Input { + sampler2D texture + vec2 texCoord + } + Output { + vec4 outColor + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TransformPosition.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TransformPosition.j3sn new file mode 100644 index 000000000..e50ca8457 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TransformPosition.j3sn @@ -0,0 +1,19 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition TransformPosition{ + Type: Vertex + Shader GLSL100: Common/MatDefs/ShaderNodes/Basic/transformPosition.vert + Documentation { + This node transforms a position according to the given matrix + @input inputPosition the position to transform + @input transformsMatrix the matrix to use for this transformation + @output outPosition the transformed position + } + Input { + vec3 inputPosition + mat4 transformsMatrix + } + Output { + vec4 outPosition + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/alphaDiscard.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/alphaDiscard.frag new file mode 100644 index 000000000..7a1c8c701 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/alphaDiscard.frag @@ -0,0 +1,3 @@ +void main(){ + if( alpha <= threshold )discard; +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/colorMix.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/colorMix.frag new file mode 100644 index 000000000..26e0b8873 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/colorMix.frag @@ -0,0 +1,3 @@ +void main(){ + outColor = mix(color1,color2,factor); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/colorMult.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/colorMult.frag new file mode 100644 index 000000000..b27581289 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/colorMult.frag @@ -0,0 +1,3 @@ +void main(){ + outColor = color1 * color2; +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/null.vert b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/null.vert new file mode 100644 index 000000000..e69de29bb diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture.frag new file mode 100644 index 000000000..eb83a7b1f --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/texture.frag @@ -0,0 +1,3 @@ +void main(){ + outColor = texture2D(texture,texCoord); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/transformPosition.vert b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/transformPosition.vert new file mode 100644 index 000000000..f83240d56 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/transformPosition.vert @@ -0,0 +1,3 @@ +void main(){ + outPosition = transformsMatrix * vec4(inputPosition, 1.0); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn new file mode 100644 index 000000000..9afc7c763 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn @@ -0,0 +1,32 @@ +ShaderNodesDefinitions { + ShaderNodeDefinition CommonVert { + Type: Vertex + Shader GLSL100: Common/MatDefs/ShaderNodes/Common/commonVert.vert + Documentation { + This Node is responsible for computing vertex position in projection space. + It also can pass texture coordinates 1 & 2, and vertexColor to the frgment shader as varying (or inputs for glsl >=1.3) + @input modelPosition the vertex position in model space (usually assigned with Attr.inPosition or Global.position) + @input worldViewProjectionMatrix the World View Projection Matrix transforms model space to projection space. + @input texCoord1 The first texture coordinates of the vertex (usually assigned with Attr.inTexCoord) + @input texCoord2 The second texture coordinates of the vertex (usually assigned with Attr.inTexCoord2) + @input vertColor The color of the vertex (usually assigned with Attr.inColor) + @output projPosition Position of the vertex in projection space.(usually assigned to Global.position) + @output vec2 texCoord1 The first texture coordinates of the vertex (output as a varying) + @output vec2 texCoord2 The second texture coordinates of the vertex (output as a varying) + @output vec4 vertColor The color of the vertex (output as a varying) + } + Input{ + vec3 modelPosition + mat4 worldViewProjectionMatrix + vec2 texCoord1 + vec2 texCoord2 + vec4 vertColor + } + Output{ + vec4 projPosition + vec2 texCoord1 + vec2 texCoord2 + vec4 vertColor + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Unshaded.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Unshaded.j3sn new file mode 100644 index 000000000..f3cb30127 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Unshaded.j3sn @@ -0,0 +1,27 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition Unshaded{ + Type: Fragment + Shader GLSL100: Common/MatDefs/ShaderNodes/Common/unshaded.frag + Documentation { + This Node is responsible for outputing the unshaded color of a fragment. + It can support texture mapping, an arbitrary input color and a vertex color + (all resulting colors will be multiplied) + @input texCoord the texture coordinates to use for texture mapping + @input vertColor the vertex color (often comming from a varrying) + @input matColor the material color (often comming from a material parameter) + @input colorMap the texture to use for texture mapping + @input color the color this node contribution will be multiplied to + @output outColor the color of the pixel (usually assigned to Global.color) + } + Input{ + vec2 texCoord + vec4 vertColor + vec4 matColor + sampler2D colorMap + vec4 color + } + Output{ + vec4 color + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/commonVert.vert b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/commonVert.vert new file mode 100644 index 000000000..7cd1a7622 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/commonVert.vert @@ -0,0 +1,3 @@ +void main(){ + projPosition = worldViewProjectionMatrix * vec4(modelPosition, 1.0); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/unshaded.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/unshaded.frag new file mode 100644 index 000000000..c14835fd4 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/unshaded.frag @@ -0,0 +1,14 @@ +void main(){ + #ifdef colorMap + color *= texture2D(colorMap, texCoord); + #endif + + #ifdef vertColor + color *= vertColor; + #endif + + #ifdef matColor + color *= matColor; + #endif + +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/Fog.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/Fog.j3sn new file mode 100644 index 000000000..f6c549ff5 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/Fog.j3sn @@ -0,0 +1,46 @@ +ShaderNodesDefinitions { + ShaderNodeDefinition FogFactor{ + Type: Vertex + Shader GLSL100: Common/MatDefs/ShaderNodes/Fog/fogFactor.vert + Documentation { + This Node is responsible for computing the fog factor of a vertex in the vertex shader. + It computes the fogFactor according to view space z (distance from cam to vertex) and a fogDensity parameter. + This Node should be used with a FogOutput for the fragment shader to effectively output the fog color. + @input modelPostion the vertex position in model space + @input modelViewMatrix the model view matrix responsible to transform a vertex position from model space to view space. + @input fogDensity the fog density (usually assigned with a material parameter) + @output fogFactor the fog factor of the vertex output as a varying + } + Input{ + vec4 modelPosition + // Note here that the fog vertex shader will compute position of the vertex in view space + // This is a pretty common operation that could be used elsewhere. + // IMO I would split this in 2 ShaderNodes, so that the view space pos could be reused. + mat4 modelViewMatrix + float fogDensity + } + Output{ + float fogFactor + } + } + ShaderNodeDefinition FogOutput{ + Type: Fragment + Shader GLSL100: Common/MatDefs/ShaderNodes/Fog/fogOutput.frag + Documentation { + This Node is responsible for multiplying a fog contribution to a color according to a fogColor and a fogFactor. + This node should be used with a FogFactor node that will be responsible to compute the fogFactor in the vertex shader. + @input fogFactor the previously computed fog factor + @input fogColor the fog color + @input color the color the fog contribution will be multiplied to. + @output color the color with fog contribution (usually assigned to Global.color) + } + Input{ + float fogFactor + vec4 fogColor + vec4 color + } + Output{ + vec4 color + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/fogFactor.vert b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/fogFactor.vert new file mode 100644 index 000000000..acfe8aced --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/fogFactor.vert @@ -0,0 +1,6 @@ +const float LOG2 = 1.442695; +void main(){ + vec4 viewSpacePos = modelViewMatrix * modelPosition; + fogFactor = exp2(-fogDensity * fogDensity * viewSpacePos.z * viewSpacePos.z * LOG2 ); + fogFactor = clamp(fogFactor, 0.0, 1.0); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/fogOutput.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/fogOutput.frag new file mode 100644 index 000000000..d9f245a3b --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/fogOutput.frag @@ -0,0 +1,3 @@ +void main(){ + color = mix(fogColor, color, fogFactor); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/HardwareSkinning.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/HardwareSkinning.j3sn new file mode 100644 index 000000000..2b734b5c8 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/HardwareSkinning.j3sn @@ -0,0 +1,61 @@ +ShaderNodesDefinitions { + ShaderNodeDefinition BasicGPUSkinning{ + Type: Vertex + Shader GLSL100: Common/MatDefs/ShaderNodes/HardwareSkinning/basicGpuSkinning.vert + Documentation { + This Node is responsible for computing vertex positions transformation + of the vertex due to skinning in model space + Note that the input position and the output are both in model Space so the output + of this node will need to be translated to projection space. + This shader node doesn't take Normals and Tangent into account for full support use FullGPUSkinning + IMPORTANT NOTE : for this node to work properly, you must declare a Int NumberOfBones material parameter to which the number of bones will be passed. + @input modelPosition the vertex position in model space (usually assigned with Attr.inPosition or Global.position) + @input boneMatrices an array of matrice holding the transforms of the bones assigned to this vertex. Its size is defined by the NumberOfBones material parameter + @input boneWeight a vec4 holding the bone weights applied to this vertex (4 weights max). + @input boneIndex a vec4 holding the bone indices assignes to this vertex (4 bones max). + @output modModelPosition transformed position of the vertex in model space. + } + Input{ + vec4 modelPosition + mat4 boneMatrices[NumberOfBones] + vec4 boneWeight + vec4 boneIndex + } + Output{ + vec4 modModelPosition + } + } + ShaderNodeDefinition FullGPUSkinning{ + Type: Vertex + Shader GLSL100: Common/MatDefs/ShaderNodes/HardwareSkinning/fullGpuSkinning.vert + Documentation { + This Node is responsible for computing vertex positions, normals and tangents transformation + of the vertex due to skinning in model space + Note that the input position and the output are both in model Space so the output + of this node will need to be translated to projection space. + IMPORTANT NOTE : for this node to work properly, you must declare a Int NumberOfBones material parameter to which the number of bones will be passed. + @input modelPosition the vertex position in model space (usually assigned with Attr.inPosition or Global.position) + @input modelNormal the vertex normal in model space (usually assigned with Attr.inNormal) + @input modelTangent the vertex tangent in model space (usually assigned with Attr.inTangent) + @input boneMatrices an array of matrice holding the transforms of the bones assigned to this vertex. Its size is defined by the NumberOfBones material parameter + @input boneWeight a vec4 holding the bone weights applied to this vertex (4 weights max). + @input boneIndex a vec4 holding the bone indices assignes to this vertex (4 bones max). + @output modModelPosition transformed position of the vertex in model space. + @output modModelNormal transformed normal of the vertex in model space. + @output modModelTangent transformed tangent of the vertex in model space. + } + Input{ + vec4 modelPosition + vec3 modelNormal + vec3 modelTangent + mat4 boneMatrices[NumberOfBones] + vec4 boneWeight + vec4 boneIndex + } + Output{ + vec4 modModelPosition + vec3 modModelNormal + vec3 modModelTangent + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/basicGpuSkinning.vert b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/basicGpuSkinning.vert new file mode 100644 index 000000000..4c153e27f --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/basicGpuSkinning.vert @@ -0,0 +1,8 @@ + +void main(){ + modModelPosition = (mat4(0.0) + + boneMatrices[int(boneIndex.x)] * boneWeight.x + + boneMatrices[int(boneIndex.y)] * boneWeight.y + + boneMatrices[int(boneIndex.z)] * boneWeight.z + + boneMatrices[int(boneIndex.w)] * boneWeight.w) * vec4(modelPosition.xyz,1.0); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/fullGpuSkinning.vert b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/fullGpuSkinning.vert new file mode 100644 index 000000000..49f8a659e --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/fullGpuSkinning.vert @@ -0,0 +1,12 @@ + +void main(){ + modModelPosition = (mat4(0.0) + + boneMatrices[int(boneIndex.x)] * boneWeight.x + + boneMatrices[int(boneIndex.y)] * boneWeight.y + + boneMatrices[int(boneIndex.z)] * boneWeight.z + + boneMatrices[int(boneIndex.w)] * boneWeight.w) * modelPosition; + + mat3 rotMat = mat3(mat[0].xyz, mat[1].xyz, mat[2].xyz); + modModelTangent = rotMat * modelTangent; + modModelNormal = rotMat * modelNormal; +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/LightMapping/LightMapping.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/LightMapping/LightMapping.j3sn new file mode 100644 index 000000000..548519ec9 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/LightMapping/LightMapping.j3sn @@ -0,0 +1,21 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition LightMapping{ + Type: Fragment + Shader GLSL100: Common/MatDefs/ShaderNodes/LightMapping/lightMap.frag + Documentation { + This Node is responsible for multiplying a light mapping contribution to a given color. + @input texCoord the texture coordinates to use for light mapping + @input lightMap the texture to use for light mapping + @input color the color the lightmap color will be multiplied to + @output color the resulting color + } + Input{ + vec2 texCoord + sampler2D lightMap + vec4 color + } + Output{ + vec4 color + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/LightMapping/lightMap.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/LightMapping/lightMap.frag new file mode 100644 index 000000000..cb0af8345 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/LightMapping/lightMap.frag @@ -0,0 +1,3 @@ +void main(){ + color *= texture2D(lightMap, texCoord); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.frag new file mode 100644 index 000000000..84bac8281 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.frag @@ -0,0 +1,12 @@ +#import "Common/ShaderLib/BasicShadow.glsllib" + +uniform SHADOWMAP m_ShadowMap; +varying vec4 projCoord; + +void main() { + vec4 coord = projCoord; + coord.xyz /= coord.w; + float shad = Shadow_GetShadow(m_ShadowMap, coord) * 0.7 + 0.3; + gl_FragColor = vec4(shad,shad,shad,1.0); +} + diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.j3md new file mode 100644 index 000000000..a07a23de4 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.j3md @@ -0,0 +1,28 @@ +MaterialDef Basic Post Shadow { + + MaterialParameters { + Texture2D ShadowMap + Matrix4 LightViewProjectionMatrix + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Shadow/BasicPostShadow.vert + FragmentShader GLSL100: Common/MatDefs/Shadow/BasicPostShadow.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + } + + Defines { + NO_SHADOW2DPROJ + } + + RenderState { + Blend Modulate + DepthWrite Off + PolyOffset -0.1 0 + } + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.vert new file mode 100644 index 000000000..3f09753f3 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.vert @@ -0,0 +1,31 @@ +uniform mat4 m_LightViewProjectionMatrix; +uniform mat4 g_WorldViewProjectionMatrix; +uniform mat4 g_WorldMatrix; + +varying vec4 projCoord; + +attribute vec3 inPosition; + +const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); + +void main(){ + gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0); + + // get the vertex in world space + vec4 worldPos = g_WorldMatrix * vec4(inPosition, 1.0); + + // convert vertex to light viewProj space + //projCoord = biasMat * (m_LightViewProjectionMatrix * worldPos); + vec4 coord = m_LightViewProjectionMatrix * worldPos; + projCoord = biasMat * coord; + //projCoord.z /= gl_DepthRange.far; + //projCoord = (m_LightViewProjectionMatrix * worldPos); + //projCoord /= projCoord.w; + //projCoord.xy = projCoord.xy * vec2(0.5, -0.5) + vec2(0.5); + + // bias from [-1, 1] to [0, 1] for sampling shadow map + //projCoord = (projCoord.xyzw * vec4(0.5)) + vec4(0.5); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag new file mode 100644 index 000000000..06276fd0f --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag @@ -0,0 +1,72 @@ +#import "Common/ShaderLib/Shadows.glsllib" + +#ifdef PSSM +varying float shadowPosition; +#endif + +varying vec4 projCoord0; +varying vec4 projCoord1; +varying vec4 projCoord2; +varying vec4 projCoord3; + +#ifdef POINTLIGHT + varying vec4 projCoord4; + varying vec4 projCoord5; + uniform vec3 m_LightPos; + varying vec4 worldPos; +#endif + +#ifdef DISCARD_ALPHA + #ifdef COLOR_MAP + uniform sampler2D m_ColorMap; + #else + uniform sampler2D m_DiffuseMap; + #endif + uniform float m_AlphaDiscardThreshold; + varying vec2 texCoord; +#endif + +#ifdef FADE +uniform vec2 m_FadeInfo; +#endif + +void main(){ + + #ifdef DISCARD_ALPHA + #ifdef COLOR_MAP + float alpha = texture2D(m_ColorMap,texCoord).a; + #else + float alpha = texture2D(m_DiffuseMap,texCoord).a; + #endif + if(alpha<=m_AlphaDiscardThreshold){ + discard; + } + + #endif + + float shadow = 1.0; + + #ifdef POINTLIGHT + shadow = getPointLightShadows(worldPos, m_LightPos, + m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5, + projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5); + #else + #ifdef PSSM + shadow = getDirectionalLightShadows(m_Splits, shadowPosition, + m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3, + projCoord0, projCoord1, projCoord2, projCoord3); + #else + //spotlight + shadow = getSpotLightShadows(m_ShadowMap0,projCoord0); + #endif + #endif + + #ifdef FADE + shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y)); + #endif + shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); + + gl_FragColor = vec4(shadow, shadow, shadow, 1.0); + +} + diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md new file mode 100644 index 000000000..a79e7ba61 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md @@ -0,0 +1,85 @@ +MaterialDef Post Shadow { + + MaterialParameters { + Int FilterMode + Boolean HardwareShadows + + Texture2D ShadowMap0 + Texture2D ShadowMap1 + Texture2D ShadowMap2 + Texture2D ShadowMap3 + //pointLights + Texture2D ShadowMap4 + Texture2D ShadowMap5 + + Float ShadowIntensity + Vector4 Splits + Vector2 FadeInfo + + Matrix4 LightViewProjectionMatrix0 + Matrix4 LightViewProjectionMatrix1 + Matrix4 LightViewProjectionMatrix2 + Matrix4 LightViewProjectionMatrix3 + //pointLight + Matrix4 LightViewProjectionMatrix4 + Matrix4 LightViewProjectionMatrix5 + Vector3 LightPos + + Float PCFEdge + + Float ShadowMapSize + } + + Technique { + VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert + FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + } + + Defines { + HARDWARE_SHADOWS : HardwareShadows + FILTER_MODE : FilterMode + PCFEDGE : PCFEdge + SHADOWMAP_SIZE : ShadowMapSize + FADE : FadeInfo + PSSM : Splits + POINTLIGHT : LightViewProjectionMatrix5 + } + + RenderState { + Blend Modulate + DepthWrite Off + PolyOffset -0.1 0 + } + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadow.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + } + + Defines { + HARDWARE_SHADOWS : HardwareShadows + FILTER_MODE : FilterMode + PCFEDGE : PCFEdge + SHADOWMAP_SIZE : ShadowMapSize + FADE : FadeInfo + PSSM : Splits + POINTLIGHT : LightViewProjectionMatrix5 + } + + RenderState { + Blend Modulate + DepthWrite Off + PolyOffset -0.1 0 + } + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert new file mode 100644 index 000000000..50c228d63 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert @@ -0,0 +1,78 @@ +#import "Common/ShaderLib/Skinning.glsllib" +uniform mat4 m_LightViewProjectionMatrix0; +uniform mat4 m_LightViewProjectionMatrix1; +uniform mat4 m_LightViewProjectionMatrix2; +uniform mat4 m_LightViewProjectionMatrix3; + +uniform mat4 g_WorldViewProjectionMatrix; +uniform mat4 g_WorldMatrix; +uniform mat4 g_ViewMatrix; +uniform vec3 m_LightPos; + +varying vec4 projCoord0; +varying vec4 projCoord1; +varying vec4 projCoord2; +varying vec4 projCoord3; + +#ifdef POINTLIGHT +uniform mat4 m_LightViewProjectionMatrix4; +uniform mat4 m_LightViewProjectionMatrix5; +varying vec4 projCoord4; +varying vec4 projCoord5; +varying vec4 worldPos; +#endif + +#ifdef PSSM +varying float shadowPosition; +#endif +varying vec3 lightVec; + +varying vec2 texCoord; + +attribute vec3 inPosition; + +#ifdef DISCARD_ALPHA + attribute vec2 inTexCoord; +#endif + +const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); + + +void main(){ + vec4 modelSpacePos = vec4(inPosition, 1.0); + + #ifdef NUM_BONES + Skinning_Compute(modelSpacePos); + #endif + gl_Position = g_WorldViewProjectionMatrix * modelSpacePos; + + #ifndef POINTLIGHT + #ifdef PSSM + shadowPosition = gl_Position.z; + #endif + vec4 worldPos=vec4(0.0); + #endif + // get the vertex in world space + worldPos = g_WorldMatrix * modelSpacePos; + + #ifdef DISCARD_ALPHA + texCoord = inTexCoord; + #endif + // populate the light view matrices array and convert vertex to light viewProj space + projCoord0 = biasMat * m_LightViewProjectionMatrix0 * worldPos; + projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos; + projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos; + projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos; + #ifdef POINTLIGHT + projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos; + projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos; + #else + + vec4 vLightPos = g_ViewMatrix * vec4(m_LightPos,1.0); + vec4 vPos = g_ViewMatrix * worldPos; + lightVec = vLightPos.xyz - vPos.xyz; + #endif +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag new file mode 100644 index 000000000..255610e11 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag @@ -0,0 +1,72 @@ +#import "Common/ShaderLib/Shadows15.glsllib" + +out vec4 outFragColor; + +#ifdef PSSM +in float shadowPosition; +#endif + +in vec4 projCoord0; +in vec4 projCoord1; +in vec4 projCoord2; +in vec4 projCoord3; + +#ifdef POINTLIGHT + in vec4 projCoord4; + in vec4 projCoord5; + in vec4 worldPos; + uniform vec3 m_LightPos; +#endif + +#ifdef DISCARD_ALPHA + #ifdef COLOR_MAP + uniform sampler2D m_ColorMap; + #else + uniform sampler2D m_DiffuseMap; + #endif + uniform float m_AlphaDiscardThreshold; + varying vec2 texCoord; +#endif + +#ifdef FADE +uniform vec2 m_FadeInfo; +#endif + +void main(){ + + #ifdef DISCARD_ALPHA + #ifdef COLOR_MAP + float alpha = texture2D(m_ColorMap,texCoord).a; + #else + float alpha = texture2D(m_DiffuseMap,texCoord).a; + #endif + + if(alpha < m_AlphaDiscardThreshold){ + discard; + } + #endif + + float shadow = 1.0; + #ifdef POINTLIGHT + shadow = getPointLightShadows(worldPos, m_LightPos, + m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5, + projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5); + #else + #ifdef PSSM + shadow = getDirectionalLightShadows(m_Splits, shadowPosition, + m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3, + projCoord0, projCoord1, projCoord2, projCoord3); + #else + //spotlight + shadow = getSpotLightShadows(m_ShadowMap0,projCoord0); + #endif + #endif + + #ifdef FADE + shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y)); + #endif + + shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); + outFragColor = vec4(shadow, shadow, shadow, 1.0); +} + diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert new file mode 100644 index 000000000..0488440e1 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert @@ -0,0 +1,78 @@ +#import "Common/ShaderLib/Skinning.glsllib" +uniform mat4 m_LightViewProjectionMatrix0; +uniform mat4 m_LightViewProjectionMatrix1; +uniform mat4 m_LightViewProjectionMatrix2; +uniform mat4 m_LightViewProjectionMatrix3; + +uniform mat4 g_WorldViewProjectionMatrix; +uniform mat4 g_WorldMatrix; +uniform mat4 g_ViewMatrix; +uniform vec3 m_LightPos; + +out vec4 projCoord0; +out vec4 projCoord1; +out vec4 projCoord2; +out vec4 projCoord3; + +#ifdef POINTLIGHT +uniform mat4 m_LightViewProjectionMatrix4; +uniform mat4 m_LightViewProjectionMatrix5; +out vec4 projCoord4; +out vec4 projCoord5; +out vec4 worldPos; +#endif + +#ifdef PSSM +out float shadowPosition; +#endif +out vec3 lightVec; + +out vec2 texCoord; + +in vec3 inPosition; + +#ifdef DISCARD_ALPHA + in vec2 inTexCoord; +#endif + +const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); + + +void main(){ + vec4 modelSpacePos = vec4(inPosition, 1.0); + + #ifdef NUM_BONES + Skinning_Compute(modelSpacePos); + #endif + gl_Position = g_WorldViewProjectionMatrix * modelSpacePos; + + #ifndef POINTLIGHT + #ifdef PSSM + shadowPosition = gl_Position.z; + #endif + vec4 worldPos=vec4(0.0); + #endif + // get the vertex in world space + worldPos = g_WorldMatrix * modelSpacePos; + + #ifdef DISCARD_ALPHA + texCoord = inTexCoord; + #endif + // populate the light view matrices array and convert vertex to light viewProj space + projCoord0 = biasMat * m_LightViewProjectionMatrix0 * worldPos; + projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos; + projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos; + projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos; + #ifdef POINTLIGHT + projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos; + projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos; + #else + + vec4 vLightPos = g_ViewMatrix * vec4(m_LightPos,1.0); + vec4 vPos = g_ViewMatrix * worldPos; + lightVec = vLightPos.xyz - vPos.xyz; + #endif +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag new file mode 100644 index 000000000..f22a93af2 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag @@ -0,0 +1,90 @@ +#import "Common/ShaderLib/Shadows.glsllib" + +uniform sampler2D m_Texture; +uniform sampler2D m_DepthTexture; +uniform mat4 m_ViewProjectionMatrixInverse; +uniform vec4 m_ViewProjectionMatrixRow2; + +varying vec2 texCoord; + + +const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); + +uniform mat4 m_LightViewProjectionMatrix0; +uniform mat4 m_LightViewProjectionMatrix1; +uniform mat4 m_LightViewProjectionMatrix2; +uniform mat4 m_LightViewProjectionMatrix3; + +#ifdef POINTLIGHT + uniform vec3 m_LightPos; + uniform mat4 m_LightViewProjectionMatrix4; + uniform mat4 m_LightViewProjectionMatrix5; +#endif + +#ifdef FADE +uniform vec2 m_FadeInfo; +#endif + +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; +} + +void main(){ + #if !defined( RENDER_SHADOWS ) + gl_FragColor = texture2D(m_Texture,texCoord); + return; + #endif + + float depth = texture2D(m_DepthTexture,texCoord).r; + vec4 color = texture2D(m_Texture,texCoord); + + //Discard shadow computation on the sky + if(depth == 1.0){ + gl_FragColor = color; + return; + } + + // get the vertex in world space + vec4 worldPos = vec4(getPosition(depth,texCoord),1.0); + + // populate the light view matrices array and convert vertex to light viewProj space + vec4 projCoord0 = biasMat * m_LightViewProjectionMatrix0 * worldPos; + vec4 projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos; + vec4 projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos; + vec4 projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos; + #ifdef POINTLIGHT + vec4 projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos; + vec4 projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos; + #endif + + float shadow = 1.0; + + #ifdef POINTLIGHT + shadow = getPointLightShadows(worldPos, m_LightPos, + m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5, + projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5); + #else + #ifdef PSSM + float shadowPosition = m_ViewProjectionMatrixRow2.x * worldPos.x + m_ViewProjectionMatrixRow2.y * worldPos.y + m_ViewProjectionMatrixRow2.z * worldPos.z + m_ViewProjectionMatrixRow2.w; + shadow = getDirectionalLightShadows(m_Splits, shadowPosition, + m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3, + projCoord0, projCoord1, projCoord2, projCoord3); + #else + //spotlight + shadow = getSpotLightShadows(m_ShadowMap0,projCoord0); + #endif + #endif + + #ifdef FADE + shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y)); + #endif + shadow= shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); + gl_FragColor = color * vec4(shadow, shadow, shadow, 1.0); + +} + diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md new file mode 100644 index 000000000..c97f0a323 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md @@ -0,0 +1,88 @@ +MaterialDef Post Shadow { + + MaterialParameters { + Int FilterMode + Boolean HardwareShadows + + Texture2D ShadowMap0 + Texture2D ShadowMap1 + Texture2D ShadowMap2 + Texture2D ShadowMap3 + //pointLights + Texture2D ShadowMap4 + Texture2D ShadowMap5 + + Float ShadowIntensity + Vector4 Splits + Vector2 FadeInfo + + Matrix4 LightViewProjectionMatrix0 + Matrix4 LightViewProjectionMatrix1 + Matrix4 LightViewProjectionMatrix2 + Matrix4 LightViewProjectionMatrix3 + //pointLight + Matrix4 LightViewProjectionMatrix4 + Matrix4 LightViewProjectionMatrix5 + Vector3 LightPos + + Float PCFEdge + + Float ShadowMapSize + + Matrix4 ViewProjectionMatrixInverse + Vector4 ViewProjectionMatrixRow2 + + Int NumSamples + Int NumSamplesDepth + Texture2D Texture + Texture2D DepthTexture + + } + + Technique { + VertexShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.vert + FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.frag + + WorldParameters { + } + + Defines { + RESOLVE_MS : NumSamples + RESOLVE_DEPTH_MS : NumSamplesDepth + HARDWARE_SHADOWS : HardwareShadows + FILTER_MODE : FilterMode + PCFEDGE : PCFEdge + SHADOWMAP_SIZE : ShadowMapSize + FADE : FadeInfo + PSSM : Splits + POINTLIGHT : LightViewProjectionMatrix5 + //if no shadow map don't render shadows + RENDER_SHADOWS : ShadowMap0 + + } + + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.vert + FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.frag + + WorldParameters { + } + + Defines { + HARDWARE_SHADOWS : HardwareShadows + FILTER_MODE : FilterMode + PCFEDGE : PCFEdge + SHADOWMAP_SIZE : ShadowMapSize + FADE : FadeInfo + PSSM : Splits + POINTLIGHT : LightViewProjectionMatrix5 + } + + } + + + + +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.vert new file mode 100644 index 000000000..8ddf7cfee --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.vert @@ -0,0 +1,9 @@ +attribute vec4 inPosition; +attribute vec2 inTexCoord; +varying vec2 texCoord; + +void main() { + vec2 pos = inPosition.xy * 2.0 - 1.0; + gl_Position = vec4(pos, 0.0, 1.0); + texCoord = inTexCoord; +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag new file mode 100644 index 000000000..c4579fdf8 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag @@ -0,0 +1,108 @@ +#import "Common/ShaderLib/MultiSample.glsllib" +#import "Common/ShaderLib/Shadows15.glsllib" + + +uniform COLORTEXTURE m_Texture; +uniform DEPTHTEXTURE m_DepthTexture; +uniform mat4 m_ViewProjectionMatrixInverse; +uniform vec4 m_ViewProjectionMatrixRow2; + +in vec2 texCoord; +out vec4 outFragColor; + +const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); + +uniform mat4 m_LightViewProjectionMatrix0; +uniform mat4 m_LightViewProjectionMatrix1; +uniform mat4 m_LightViewProjectionMatrix2; +uniform mat4 m_LightViewProjectionMatrix3; + +#ifdef POINTLIGHT + uniform vec3 m_LightPos; + uniform mat4 m_LightViewProjectionMatrix4; + uniform mat4 m_LightViewProjectionMatrix5; +#endif + +#ifdef FADE +uniform vec2 m_FadeInfo; +#endif + +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; +} + +vec4 main_multiSample(in int numSample){ + float depth = fetchTextureSample(m_DepthTexture,texCoord,numSample).r;//getDepth(m_DepthTexture,texCoord).r; + vec4 color = fetchTextureSample(m_Texture,texCoord,numSample); + + //Discard shadow computation on the sky + if(depth == 1.0){ + return color; + } + + // get the vertex in world space + vec4 worldPos = vec4(getPosition(depth,texCoord),1.0); + + // populate the light view matrices array and convert vertex to light viewProj space + vec4 projCoord0 = biasMat * m_LightViewProjectionMatrix0 * worldPos; + vec4 projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos; + vec4 projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos; + vec4 projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos; + #ifdef POINTLIGHT + vec4 projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos; + vec4 projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos; + #endif + + float shadow = 1.0; + + #ifdef POINTLIGHT + shadow = getPointLightShadows(worldPos, m_LightPos, + m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5, + projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5); + #else + #ifdef PSSM + float shadowPosition = m_ViewProjectionMatrixRow2.x * worldPos.x + m_ViewProjectionMatrixRow2.y * worldPos.y + m_ViewProjectionMatrixRow2.z * worldPos.z + m_ViewProjectionMatrixRow2.w; + shadow = getDirectionalLightShadows(m_Splits, shadowPosition, + m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3, + projCoord0, projCoord1, projCoord2, projCoord3); + #else + //spotlight + shadow = getSpotLightShadows(m_ShadowMap0,projCoord0); + #endif + #endif + + + #ifdef FADE + shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y)); + #endif + + shadow= shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); + return color * vec4(shadow, shadow, shadow, 1.0); +} + +void main(){ + + #if !defined( RENDER_SHADOWS ) + outFragColor = fetchTextureSample(m_Texture,texCoord,0); + return; + #endif + + #ifdef RESOLVE_MS + vec4 color = vec4(0.0); + for (int i = 0; i < m_NumSamples; i++){ + color += main_multiSample(i); + } + outFragColor = color / m_NumSamples; + #else + outFragColor = main_multiSample(0); + #endif + +} + + + diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.vert new file mode 100644 index 000000000..2f09f4f29 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.vert @@ -0,0 +1,10 @@ +in vec4 inPosition; +in vec2 inTexCoord; + +out vec2 texCoord; + +void main() { + vec2 pos = inPosition.xy * 2.0 - 1.0; + gl_Position = vec4(pos, 0.0, 1.0); + texCoord = inTexCoord; +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.frag new file mode 100644 index 000000000..080666a4e --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.frag @@ -0,0 +1,27 @@ +varying vec2 texCoord; + +#ifdef DISCARD_ALPHA + #ifdef COLOR_MAP + uniform sampler2D m_ColorMap; + #else + uniform sampler2D m_DiffuseMap; + #endif + uniform float m_AlphaDiscardThreshold; +#endif + + +void main(){ + #ifdef DISCARD_ALPHA + #ifdef COLOR_MAP + if (texture2D(m_ColorMap, texCoord).a <= m_AlphaDiscardThreshold){ + discard; + } + #else + if (texture2D(m_DiffuseMap, texCoord).a <= m_AlphaDiscardThreshold){ + discard; + } + #endif + #endif + + gl_FragColor = vec4(1.0); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.j3md new file mode 100644 index 000000000..d270e71a9 --- /dev/null +++ b/jme3-core/src/main/resources/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 3 + ColorWrite Off + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert new file mode 100644 index 000000000..e57d45141 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert @@ -0,0 +1,18 @@ +#import "Common/ShaderLib/Skinning.glsllib" +attribute vec3 inPosition; +attribute vec2 inTexCoord; + +uniform mat4 g_WorldViewProjectionMatrix; +uniform mat4 g_WorldViewMatrix; + +varying vec2 texCoord; + +void main(){ + vec4 modelSpacePos = vec4(inPosition, 1.0); + + #ifdef NUM_BONES + Skinning_Compute(modelSpacePos); + #endif + gl_Position = g_WorldViewProjectionMatrix * modelSpacePos; + texCoord = inTexCoord; +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/Materials/RedColor.j3m b/jme3-core/src/main/resources/Common/Materials/RedColor.j3m new file mode 100644 index 000000000..c7c8e77e6 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/Materials/VertexColor.j3m b/jme3-core/src/main/resources/Common/Materials/VertexColor.j3m new file mode 100644 index 000000000..ae7209204 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/Materials/WhiteColor.j3m b/jme3-core/src/main/resources/Common/Materials/WhiteColor.j3m new file mode 100644 index 000000000..1a5d78e95 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/ShaderLib/BasicShadow.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/BasicShadow.glsllib new file mode 100644 index 000000000..81a5361a8 --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/BasicShadow.glsllib @@ -0,0 +1,105 @@ +#ifdef NO_SHADOW2DPROJ +#define SHADOWMAP sampler2D +#define SHADOWTEX texture2D +#define SHADCOORD(coord) coord.xy +#else +#define SHADOWMAP sampler2DShadow +#define SHADOWTEX shadow2D +#define SHADCOORD(coord) vec3(coord.xy,0.0) +#endif + +//float shadowDepth = texture2DProj(tex, projCoord); + +const float texSize = 1024.0; +const float pixSize = 1.0 / texSize; +const vec2 pixSize2 = vec2(pixSize); + +float Shadow_DoShadowCompareOffset(in SHADOWMAP tex, vec4 projCoord, vec2 offset){ + return step(projCoord.z, SHADOWTEX(tex, SHADCOORD(projCoord.xy + offset * pixSize2)).r); +} + +float Shadow_DoShadowCompare(in SHADOWMAP tex, vec4 projCoord){ + return step(projCoord.z, SHADOWTEX(tex, SHADCOORD(projCoord.xy)).r); +} + +float Shadow_BorderCheck(in vec2 coord){ + // Very slow method (uses 24 instructions) + //if (coord.x >= 1.0) + // return 1.0; + //else if (coord.x <= 0.0) + // return 1.0; + //else if (coord.y >= 1.0) + // return 1.0; + //else if (coord.y <= 0.0) + // return 1.0; + //else + // return 0.0; + + // Fastest, "hack" method (uses 4-5 instructions) + vec4 t = vec4(coord.xy, 0.0, 1.0); + t = step(t.wwxy, t.xyzz); + return dot(t,t); +} + +float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){ + float shadow = 0.0; + vec2 o = mod(floor(gl_FragCoord.xy), 2.0); + shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, 1.5) + o); + shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, 1.5) + o); + shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, -0.5) + o); + shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, -0.5) + o); + shadow *= 0.25 ; + return shadow; +} + +float Shadow_DoBilinear(in SHADOWMAP tex, in vec4 projCoord){ + const vec2 size = vec2(256.0); + const vec2 pixel = vec2(1.0) / vec2(256.0); + + vec2 tc = projCoord.xy * size; + vec2 bl = fract(tc); + vec2 dn = floor(tc) * pixel; + vec2 up = dn + pixel; + + vec4 coord = vec4(dn.xy, projCoord.zw); + float s_00 = Shadow_DoShadowCompare(tex, coord); + s_00 = clamp(s_00, 0.0, 1.0); + + coord = vec4(up.x, dn.y, projCoord.zw); + float s_10 = Shadow_DoShadowCompare(tex, coord); + s_10 = clamp(s_10, 0.0, 1.0); + + coord = vec4(dn.x, up.y, projCoord.zw); + float s_01 = Shadow_DoShadowCompare(tex, coord); + s_01 = clamp(s_01, 0.0, 1.0); + + coord = vec4(up.xy, projCoord.zw); + float s_11 = Shadow_DoShadowCompare(tex, coord); + s_11 = clamp(s_11, 0.0, 1.0); + + float xb0 = mix(s_00, s_10, clamp(bl.x, 0.0, 1.0)); + float xb1 = mix(s_01, s_11, clamp(bl.x, 0.0, 1.0)); + float yb = mix(xb0, xb1, clamp(bl.y, 0.0, 1.0)); + return yb; +} + +float Shadow_DoPCF_2x2(in SHADOWMAP tex, in vec4 projCoord){ + + float shadow = 0.0; + float x,y; + for (y = -1.5 ; y <=1.5 ; y+=1.0) + for (x = -1.5 ; x <=1.5 ; x+=1.0) + shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) + + Shadow_BorderCheck(projCoord.xy), + 0.0, 1.0); + + shadow /= 16.0 ; + return shadow; +} + + +float Shadow_GetShadow(in SHADOWMAP tex, in vec4 projCoord){ + return clamp(Shadow_DoDither_2x2(tex, projCoord) + Shadow_BorderCheck(projCoord.xy), 0.0, 1.0); +} + + diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Bump.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Bump.glsllib new file mode 100644 index 000000000..6b9149bcc --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/ShaderLib/Common.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Common.glsllib new file mode 100644 index 000000000..8dce15bb4 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/ShaderLib/Fog.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Fog.glsllib new file mode 100644 index 000000000..0a28362cc --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/ShaderLib/Hdr.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Hdr.glsllib new file mode 100644 index 000000000..5db1423ef --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib new file mode 100644 index 000000000..4d1b40436 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/ShaderLib/Math.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Math.glsllib new file mode 100644 index 000000000..6f4cc9074 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/ShaderLib/MultiSample.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/MultiSample.glsllib new file mode 100644 index 000000000..f49612e31 --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/MultiSample.glsllib @@ -0,0 +1,62 @@ +#extension GL_ARB_texture_multisample : enable + +uniform int m_NumSamples; +uniform int m_NumSamplesDepth; + +#ifdef RESOLVE_MS + #define COLORTEXTURE sampler2DMS +#else + #define COLORTEXTURE sampler2D +#endif + +#ifdef RESOLVE_DEPTH_MS + #define DEPTHTEXTURE sampler2DMS +#else + #define DEPTHTEXTURE sampler2D +#endif + +// NOTE: Only define multisample functions if multisample is available and is being used! +#if defined(GL_ARB_texture_multisample) && (defined(RESOLVE_MS) || defined(RESOLVE_DEPTH_MS)) +vec4 textureFetch(in sampler2DMS tex,in vec2 texC, in int numSamples){ + ivec2 iTexC = ivec2(texC * vec2(textureSize(tex))); + vec4 color = vec4(0.0); + for (int i = 0; i < numSamples; i++){ + color += texelFetch(tex, iTexC, i); + } + return color / float(numSamples); +} + +vec4 fetchTextureSample(in sampler2DMS tex,in vec2 texC,in int sample){ + ivec2 iTexC = ivec2(texC * vec2(textureSize(tex))); + return texelFetch(tex, iTexC, sample); +} + +vec4 getColor(in sampler2DMS tex, in vec2 texC){ + return textureFetch(tex, texC, m_NumSamples); +} + +vec4 getColorSingle(in sampler2DMS tex, in vec2 texC){ + ivec2 iTexC = ivec2(texC * vec2(textureSize(tex))); + return texelFetch(tex, iTexC, 0); +} + +vec4 getDepth(in sampler2DMS tex,in vec2 texC){ + return textureFetch(tex,texC,m_NumSamplesDepth); +} +#endif + +vec4 fetchTextureSample(in sampler2D tex,in vec2 texC,in int sample){ + return texture2D(tex,texC); +} + +vec4 getColor(in sampler2D tex, in vec2 texC){ + return texture2D(tex,texC); +} + +vec4 getColorSingle(in sampler2D tex, in vec2 texC){ + return texture2D(tex, texC); +} + +vec4 getDepth(in sampler2D tex,in vec2 texC){ + return texture2D(tex,texC); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Optics.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Optics.glsllib new file mode 100644 index 000000000..5f762434f --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/Optics.glsllib @@ -0,0 +1,32 @@ +#ifdef SPHERE_MAP +#define ENVMAP sampler2D +#define TEXENV texture2D +#else +#define ENVMAP samplerCube +#define TEXENV textureCube +#endif + +// converts a normalized direction vector +// into a texture coordinate for fetching +// texel from a sphere map +vec2 Optics_SphereCoord(in vec3 dir){ + float dzplus1 = dir.z + 1.0; + + // compute 1/2p + // NOTE: this simplification only works if dir is normalized. + float inv_two_p = 1.414 * sqrt(dzplus1); + //float inv_two_p = sqrt(dir.x * dir.x + dir.y * dir.y + dzplus1 * dzplus1); + inv_two_p *= 2.0; + inv_two_p = 1.0 / inv_two_p; + + // compute texcoord + return (dir.xy * vec2(inv_two_p)) + vec2(0.5); +} + +vec4 Optics_GetEnvColor(in ENVMAP envMap, in vec3 dir){ + #ifdef SPHERE_MAP + return texture2D(envMap, Optics_SphereCoord(dir)); + #else + return textureCube(envMap, dir); + #endif +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Parallax.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Parallax.glsllib new file mode 100644 index 000000000..bb728f717 --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/Parallax.glsllib @@ -0,0 +1,77 @@ +#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) + vec2 steepParallaxOffset(sampler2D parallaxMap, vec3 vViewDir,vec2 texCoord,float parallaxScale){ + vec2 vParallaxDirection = normalize( vViewDir.xy ); + + // The length of this vector determines the furthest amount of displacement: (Ati's comment) + float fLength = length( vViewDir ); + float fParallaxLength = sqrt( fLength * fLength - vViewDir.z * vViewDir.z ) / vViewDir.z; + + // Compute the actual reverse parallax displacement vector: (Ati's comment) + vec2 vParallaxOffsetTS = vParallaxDirection * fParallaxLength; + + // Need to scale the amount of displacement to account for different height ranges + // in height maps. This is controlled by an artist-editable parameter: (Ati's comment) + parallaxScale *=0.3; + vParallaxOffsetTS *= parallaxScale; + + vec3 eyeDir = normalize(vViewDir).xyz; + + float nMinSamples = 6.0; + float nMaxSamples = 1000.0 * parallaxScale; + float nNumSamples = mix( nMinSamples, nMaxSamples, 1.0 - eyeDir.z ); //In reference shader: int nNumSamples = (int)(lerp( nMinSamples, nMaxSamples, dot( eyeDirWS, N ) )); + float fStepSize = 1.0 / nNumSamples; + float fCurrHeight = 0.0; + float fPrevHeight = 1.0; + float fNextHeight = 0.0; + float nStepIndex = 0.0; + vec2 vTexOffsetPerStep = fStepSize * vParallaxOffsetTS; + vec2 vTexCurrentOffset = texCoord; + float fCurrentBound = 1.0; + float fParallaxAmount = 0.0; + + while ( nStepIndex < nNumSamples && fCurrHeight <= fCurrentBound ) { + vTexCurrentOffset -= vTexOffsetPerStep; + fPrevHeight = fCurrHeight; + + + #ifdef NORMALMAP_PARALLAX + //parallax map is stored in the alpha channel of the normal map + fCurrHeight = texture2D( parallaxMap, vTexCurrentOffset).a; + #else + //parallax map is a texture + fCurrHeight = texture2D( parallaxMap, vTexCurrentOffset).r; + #endif + + fCurrentBound -= fStepSize; + nStepIndex+=1.0; + } + vec2 pt1 = vec2( fCurrentBound, fCurrHeight ); + vec2 pt2 = vec2( fCurrentBound + fStepSize, fPrevHeight ); + + float fDelta2 = pt2.x - pt2.y; + float fDelta1 = pt1.x - pt1.y; + + float fDenominator = fDelta2 - fDelta1; + + fParallaxAmount = (pt1.x * fDelta2 - pt2.x * fDelta1 ) / fDenominator; + + vec2 vParallaxOffset = vParallaxOffsetTS * (1.0 - fParallaxAmount ); + return texCoord - vParallaxOffset; + } + + vec2 classicParallaxOffset(sampler2D parallaxMap, vec3 vViewDir,vec2 texCoord,float parallaxScale){ + float h; + #ifdef NORMALMAP_PARALLAX + //parallax map is stored in the alpha channel of the normal map + h = texture2D(parallaxMap, texCoord).a; + #else + //parallax map is a texture + h = texture2D(parallaxMap, texCoord).r; + #endif + float heightScale = parallaxScale; + float heightBias = heightScale* -0.6; + vec3 normView = normalize(vViewDir); + h = (h * heightScale + heightBias) * normView.z; + return texCoord + (h * normView.xy); + } +#endif \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib new file mode 100644 index 000000000..d2f1a942b --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib @@ -0,0 +1,231 @@ +#ifdef HARDWARE_SHADOWS + #define SHADOWMAP sampler2DShadow + #define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r +#else + #define SHADOWMAP sampler2D + #define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r) +#endif + +#if FILTER_MODE == 0 + #define GETSHADOW Shadow_DoShadowCompare + #define KERNEL 1.0 +#elif FILTER_MODE == 1 + #ifdef HARDWARE_SHADOWS + #define GETSHADOW Shadow_DoShadowCompare + #else + #define GETSHADOW Shadow_DoBilinear_2x2 + #endif + #define KERNEL 1.0 +#elif FILTER_MODE == 2 + #define GETSHADOW Shadow_DoDither_2x2 + #define KERNEL 1.0 +#elif FILTER_MODE == 3 + #define GETSHADOW Shadow_DoPCF + #define KERNEL 4.0 +#elif FILTER_MODE == 4 + #define GETSHADOW Shadow_DoPCFPoisson + #define KERNEL 4.0 +#elif FILTER_MODE == 5 + #define GETSHADOW Shadow_DoPCF + #define KERNEL 8.0 +#endif + + +uniform SHADOWMAP m_ShadowMap0; +uniform SHADOWMAP m_ShadowMap1; +uniform SHADOWMAP m_ShadowMap2; +uniform SHADOWMAP m_ShadowMap3; +#ifdef POINTLIGHT +uniform SHADOWMAP m_ShadowMap4; +uniform SHADOWMAP m_ShadowMap5; +#endif + +#ifdef PSSM +uniform vec4 m_Splits; +#endif + +uniform float m_ShadowIntensity; + +const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE); +float shadowBorderScale = 1.0; + +float Shadow_DoShadowCompareOffset(SHADOWMAP tex, vec4 projCoord, vec2 offset){ + vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw); + return SHADOWCOMPARE(tex, coord); +} + +float Shadow_DoShadowCompare(SHADOWMAP tex, vec4 projCoord){ + return SHADOWCOMPARE(tex, projCoord); +} + +float Shadow_BorderCheck(vec2 coord){ + // Fastest, "hack" method (uses 4-5 instructions) + vec4 t = vec4(coord.xy, 0.0, 1.0); + t = step(t.wwxy, t.xyzz); + return dot(t,t); +} + +float Shadow_Nearest(SHADOWMAP tex, vec4 projCoord){ + float border = Shadow_BorderCheck(projCoord.xy); + if (border > 0.0){ + return 1.0; + } + return Shadow_DoShadowCompare(tex,projCoord); +} + +float Shadow_DoDither_2x2(SHADOWMAP tex, vec4 projCoord){ + float border = Shadow_BorderCheck(projCoord.xy); + if (border > 0.0) + return 1.0; + + + float shadow = 0.0; + vec2 o = mod(floor(gl_FragCoord.xy), 2.0); + shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, 1.5) + o); + shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, 1.5) + o); + shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, -0.5) + o); + shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, -0.5) + o); + shadow *= 0.25 ; + return shadow; +} + +float Shadow_DoBilinear_2x2(SHADOWMAP tex, vec4 projCoord){ + float border = Shadow_BorderCheck(projCoord.xy); + if (border > 0.0) + return 1.0; + vec4 gather = vec4(0.0); + gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0)); + gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0)); + gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0)); + gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0)); + + vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE ); + vec2 mx = mix( gather.xz, gather.yw, f.x ); + return mix( mx.x, mx.y, f.y ); +} + +float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){ + float shadow = 0.0; + float border = Shadow_BorderCheck(projCoord.xy); + if (border > 0.0) + return 1.0; + float bound = KERNEL * 0.5 - 0.5; + bound *= PCFEDGE; + for (float y = -bound; y <= bound; y += PCFEDGE){ + for (float x = -bound; x <= bound; x += PCFEDGE){ + shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) + + border, + 0.0, 1.0); + } + } + + shadow = shadow / (KERNEL * KERNEL); + return shadow; +} + + +//12 tap poisson disk + const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016); + const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201); + const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521); + const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819); + const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957); + const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841); + const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516); + const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126); + const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693); + const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707); + const vec2 poissonDisk10 = vec2(0.5653144, 0.60262); + const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419); + +float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){ + float shadow = 0.0; + float border = Shadow_BorderCheck(projCoord.xy); + if (border > 0.0) + return 1.0; + + vec2 texelSize = vec2( 4.0 * PCFEDGE * shadowBorderScale); + + shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk0 * texelSize); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk1 * texelSize); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk2 * texelSize); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk3 * texelSize); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk4 * texelSize); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk5 * texelSize); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk6 * texelSize); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk7 * texelSize); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk8 * texelSize); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk9 * texelSize); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk10 * texelSize); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk11 * texelSize); + + shadow = shadow * 0.08333333333;//this is divided by 12 + return shadow; +} + + +#ifdef POINTLIGHT + float getPointLightShadows(vec4 worldPos,vec3 lightPos, + SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,SHADOWMAP shadowMap4,SHADOWMAP shadowMap5, + vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3,vec4 projCoord4,vec4 projCoord5){ + float shadow = 1.0; + vec3 vect = worldPos.xyz - lightPos; + vec3 absv= abs(vect); + float maxComp = max(absv.x,max(absv.y,absv.z)); + if(maxComp == absv.y){ + if(vect.y < 0.0){ + shadow = GETSHADOW(shadowMap0, projCoord0 / projCoord0.w); + }else{ + shadow = GETSHADOW(shadowMap1, projCoord1 / projCoord1.w); + } + }else if(maxComp == absv.z){ + if(vect.z < 0.0){ + shadow = GETSHADOW(shadowMap2, projCoord2 / projCoord2.w); + }else{ + shadow = GETSHADOW(shadowMap3, projCoord3 / projCoord3.w); + } + }else if(maxComp == absv.x){ + if(vect.x < 0.0){ + shadow = GETSHADOW(shadowMap4, projCoord4 / projCoord4.w); + }else{ + shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w); + } + } + return shadow; + } +#else + #ifdef PSSM + float getDirectionalLightShadows(vec4 splits,float shadowPosition, + SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3, + vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3){ + float shadow = 1.0; + if(shadowPosition < splits.x){ + shadow = GETSHADOW(shadowMap0, projCoord0 ); + }else if( shadowPosition < splits.y){ + shadowBorderScale = 0.5; + shadow = GETSHADOW(shadowMap1, projCoord1); + }else if( shadowPosition < splits.z){ + shadowBorderScale = 0.25; + shadow = GETSHADOW(shadowMap2, projCoord2); + }else if( shadowPosition < splits.w){ + shadowBorderScale = 0.125; + shadow = GETSHADOW(shadowMap3, projCoord3); + } + return shadow; + } + #else + float getSpotLightShadows(SHADOWMAP shadowMap, vec4 projCoord){ + float shadow = 1.0; + projCoord /= projCoord.w; + shadow = GETSHADOW(shadowMap, projCoord); + + //a small falloff to make the shadow blend nicely into the not lighten + //we translate the texture coordinate value to a -1,1 range so the length + //of the texture coordinate vector is actually the radius of the lighten area on the ground + projCoord = projCoord * 2.0 - 1.0; + float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1; + return mix(shadow,1.0,clamp(fallOff,0.0,1.0)); + + } + #endif +#endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib new file mode 100644 index 000000000..038403802 --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib @@ -0,0 +1,242 @@ +// Because gpu_shader5 is actually where those +// gather functions are declared to work on shadowmaps +#extension GL_ARB_gpu_shader5 : enable + +#ifdef HARDWARE_SHADOWS + #define SHADOWMAP sampler2DShadow + #define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset) + #define SHADOWCOMPARE(tex,coord) textureProj(tex, coord) + #define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z) +#else + #define SHADOWMAP sampler2D + #define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r) + #define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r) + #define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy)) +#endif + + +#if FILTER_MODE == 0 + #define GETSHADOW Shadow_Nearest + #define KERNEL 1.0 +#elif FILTER_MODE == 1 + #ifdef HARDWARE_SHADOWS + #define GETSHADOW Shadow_Nearest + #else + #define GETSHADOW Shadow_DoBilinear_2x2 + #endif + #define KERNEL 1.0 +#elif FILTER_MODE == 2 + #define GETSHADOW Shadow_DoDither_2x2 + #define KERNEL 1.0 +#elif FILTER_MODE == 3 + #define GETSHADOW Shadow_DoPCF + #define KERNEL 4.0 +#elif FILTER_MODE == 4 + #define GETSHADOW Shadow_DoPCFPoisson + #define KERNEL 4.0 +#elif FILTER_MODE == 5 + #define GETSHADOW Shadow_DoPCF + #define KERNEL 8.0 +#endif + + + +uniform SHADOWMAP m_ShadowMap0; +uniform SHADOWMAP m_ShadowMap1; +uniform SHADOWMAP m_ShadowMap2; +uniform SHADOWMAP m_ShadowMap3; +#ifdef POINTLIGHT +uniform SHADOWMAP m_ShadowMap4; +uniform SHADOWMAP m_ShadowMap5; +#endif + +#ifdef PSSM +uniform vec4 m_Splits; +#endif +uniform float m_ShadowIntensity; + +const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE); +float shadowBorderScale = 1.0; + +float Shadow_BorderCheck(in vec2 coord){ + // Fastest, "hack" method (uses 4-5 instructions) + vec4 t = vec4(coord.xy, 0.0, 1.0); + t = step(t.wwxy, t.xyzz); + return dot(t,t); +} + +float Shadow_Nearest(in SHADOWMAP tex, in vec4 projCoord){ + float border = Shadow_BorderCheck(projCoord.xy); + if (border > 0.0){ + return 1.0; + } + return SHADOWCOMPARE(tex,projCoord); +} + +float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){ + float border = Shadow_BorderCheck(projCoord.xy); + if (border > 0.0) + return 1.0; + + vec2 pixSize = pixSize2 * shadowBorderScale; + + float shadow = 0.0; + ivec2 o = ivec2(mod(floor(gl_FragCoord.xy), 2.0)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2(-1.5, 1.5)+o), projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2( 0.5, 1.5)+o), projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2(-1.5, -0.5)+o), projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2( 0.5, -0.5)+o), projCoord.zw)); + shadow *= 0.25; + return shadow; +} + +float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){ + float border = Shadow_BorderCheck(projCoord.xy); + if (border > 0.0) + return 1.0; + + #ifdef GL_ARB_gpu_shader5 + vec4 coord = vec4(projCoord.xyz / projCoord.www,0.0); + vec4 gather = SHADOWGATHER(tex, coord); + #else + vec4 gather = vec4(0.0); + gather.x = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 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 * SHADOWMAP_SIZE ); + 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){ + + vec2 pixSize = pixSize2 * shadowBorderScale; + float shadow = 0.0; + float border = Shadow_BorderCheck(projCoord.xy); + if (border > 0.0) + return 1.0; + + float bound = KERNEL * 0.5 - 0.5; + bound *= PCFEDGE; + for (float y = -bound; y <= bound; y += PCFEDGE){ + for (float x = -bound; x <= bound; x += PCFEDGE){ + vec4 coord = vec4(projCoord.xy + vec2(x,y) * pixSize, projCoord.zw); + shadow += SHADOWCOMPARE(tex, coord); + } + } + + shadow = shadow / (KERNEL * KERNEL); + return shadow; +} + + +//12 tap poisson disk + const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016); + const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201); + const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521); + const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819); + const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957); + const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841); + const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516); + const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126); + const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693); + const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707); + const vec2 poissonDisk10 = vec2(0.5653144, 0.60262); + const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419); + + +float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){ + + float shadow = 0.0; + float border = Shadow_BorderCheck(projCoord.xy); + if (border > 0.0){ + return 1.0; + } + + vec2 texelSize = pixSize2 * 4.0 * PCFEDGE * shadowBorderScale; + + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk0 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk1 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk2 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk3 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk4 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk5 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk6 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk7 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk8 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk9 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk10 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk11 * texelSize, projCoord.zw)); + + //this is divided by 12 + return shadow * 0.08333333333; +} + +#ifdef POINTLIGHT + float getPointLightShadows(in vec4 worldPos,in vec3 lightPos, + in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5, + in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){ + float shadow = 1.0; + vec3 vect = worldPos.xyz - lightPos; + vec3 absv= abs(vect); + float maxComp = max(absv.x,max(absv.y,absv.z)); + if(maxComp == absv.y){ + if(vect.y < 0.0){ + shadow = GETSHADOW(shadowMap0, projCoord0 / projCoord0.w); + }else{ + shadow = GETSHADOW(shadowMap1, projCoord1 / projCoord1.w); + } + }else if(maxComp == absv.z){ + if(vect.z < 0.0){ + shadow = GETSHADOW(shadowMap2, projCoord2 / projCoord2.w); + }else{ + shadow = GETSHADOW(shadowMap3, projCoord3 / projCoord3.w); + } + }else if(maxComp == absv.x){ + if(vect.x < 0.0){ + shadow = GETSHADOW(shadowMap4, projCoord4 / projCoord4.w); + }else{ + shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w); + } + } + return shadow; + } +#else + #ifdef PSSM + float getDirectionalLightShadows(in vec4 splits,in float shadowPosition, + in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3, + in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){ + float shadow = 1.0; + if(shadowPosition < splits.x){ + shadow = GETSHADOW(shadowMap0, projCoord0 ); + }else if( shadowPosition < splits.y){ + shadowBorderScale = 0.5; + shadow = GETSHADOW(shadowMap1, projCoord1); + }else if( shadowPosition < splits.z){ + shadowBorderScale = 0.25; + shadow = GETSHADOW(shadowMap2, projCoord2); + }else if( shadowPosition < splits.w){ + shadowBorderScale = 0.125; + shadow = GETSHADOW(shadowMap3, projCoord3); + } + return shadow; + } + #else + float getSpotLightShadows(in SHADOWMAP shadowMap,in vec4 projCoord){ + float shadow = 1.0; + projCoord /= projCoord.w; + shadow = GETSHADOW(shadowMap,projCoord); + + //a small falloff to make the shadow blend nicely into the not lighten + //we translate the texture coordinate value to a -1,1 range so the length + //of the texture coordinate vector is actually the radius of the lighten area on the ground + projCoord = projCoord * 2.0 - 1.0; + float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1; + return mix(shadow,1.0,clamp(fallOff,0.0,1.0)); + + } + #endif +#endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Skinning.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Skinning.glsllib new file mode 100644 index 000000000..66a9ff73b --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/Skinning.glsllib @@ -0,0 +1,72 @@ +#ifdef NUM_BONES + +#if NUM_BONES < 1 || NUM_BONES > 255 +#error NUM_BONES must be between 1 and 255. +#endif + +#define NUM_WEIGHTS_PER_VERT 4 + +attribute vec4 inHWBoneWeight; +attribute vec4 inHWBoneIndex; +uniform mat4 m_BoneMatrices[NUM_BONES]; + +void Skinning_Compute(inout vec4 position){ + if (inHWBoneWeight.x != 0.0) { +#if NUM_WEIGHTS_PER_VERT == 1 + position = m_BoneMatrices[int(inHWBoneIndex.x)] * position; +#else + mat4 mat = mat4(0.0); + mat += m_BoneMatrices[int(inHWBoneIndex.x)] * inHWBoneWeight.x; + mat += m_BoneMatrices[int(inHWBoneIndex.y)] * inHWBoneWeight.y; + mat += m_BoneMatrices[int(inHWBoneIndex.z)] * inHWBoneWeight.z; + mat += m_BoneMatrices[int(inHWBoneIndex.w)] * inHWBoneWeight.w; + position = mat * position; +#endif + } +} + +void Skinning_Compute(inout vec4 position, inout vec3 normal){ + if (inHWBoneWeight.x != 0.0) { +#if NUM_WEIGHTS_PER_VERT == 1 + position = m_BoneMatrices[int(inHWBoneIndex.x)] * position; + normal = (mat3(m_BoneMatrices[int(inHWBoneIndex.x)][0].xyz, + m_BoneMatrices[int(inHWBoneIndex.x)][1].xyz, + m_BoneMatrices[int(inHWBoneIndex.x)][2].xyz) * normal); +#else + mat4 mat = mat4(0.0); + mat += m_BoneMatrices[int(inHWBoneIndex.x)] * inHWBoneWeight.x; + mat += m_BoneMatrices[int(inHWBoneIndex.y)] * inHWBoneWeight.y; + mat += m_BoneMatrices[int(inHWBoneIndex.z)] * inHWBoneWeight.z; + mat += m_BoneMatrices[int(inHWBoneIndex.w)] * inHWBoneWeight.w; + position = mat * position; + + mat3 rotMat = mat3(mat[0].xyz, mat[1].xyz, mat[2].xyz); + normal = rotMat * normal; +#endif + } +} + +void Skinning_Compute(inout vec4 position, inout vec3 tangent, inout vec3 normal){ + if (inHWBoneWeight.x != 0.0) { +#if NUM_WEIGHTS_PER_VERT == 1 + position = m_BoneMatrices[int(inHWBoneIndex.x)] * position; + tangent = m_BoneMatrices[int(inHWBoneIndex.x)] * tangent; + normal = (mat3(m_BoneMatrices[int(inHWBoneIndex.x)][0].xyz, + m_BoneMatrices[int(inHWBoneIndex.x)][1].xyz, + m_BoneMatrices[int(inHWBoneIndex.x)][2].xyz) * normal); +#else + mat4 mat = mat4(0.0); + mat += m_BoneMatrices[int(inHWBoneIndex.x)] * inHWBoneWeight.x; + mat += m_BoneMatrices[int(inHWBoneIndex.y)] * inHWBoneWeight.y; + mat += m_BoneMatrices[int(inHWBoneIndex.z)] * inHWBoneWeight.z; + mat += m_BoneMatrices[int(inHWBoneIndex.w)] * inHWBoneWeight.w; + position = mat * position; + + mat3 rotMat = mat3(mat[0].xyz, mat[1].xyz, mat[2].xyz); + tangent = rotMat * tangent; + normal = rotMat * normal; +#endif + } +} + +#endif \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Splatting.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Splatting.glsllib new file mode 100644 index 000000000..044fee399 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/ShaderLib/Tangent.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Tangent.glsllib new file mode 100644 index 000000000..308c13dd7 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/ShaderLib/Texture.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Texture.glsllib new file mode 100644 index 000000000..829f51b47 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/ShaderLib/Ubo.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Ubo.glsllib new file mode 100644 index 000000000..5dbb5b9e1 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Common/ShaderLib/WaterUtil.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/WaterUtil.glsllib new file mode 100644 index 000000000..51c4d8af8 --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/WaterUtil.glsllib @@ -0,0 +1,10 @@ +#ifdef ENABLE_AREA +bool isOverExtent(vec3 position,vec3 center,float radius){ + vec2 dist = position.xz-center.xz; + #ifdef SQUARE_AREA + return dist.x*dist.x >radius || dist.y*dist.y >radius; + #else + return dot(dist,dist)>radius; + #endif +} +#endif \ No newline at end of file diff --git a/jme3-core/src/main/resources/Interface/Fonts/Console.fnt b/jme3-core/src/main/resources/Interface/Fonts/Console.fnt new file mode 100644 index 000000000..15ea60cf7 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Interface/Fonts/Console.png b/jme3-core/src/main/resources/Interface/Fonts/Console.png new file mode 100644 index 000000000..821f92a6a Binary files /dev/null and b/jme3-core/src/main/resources/Interface/Fonts/Console.png differ diff --git a/jme3-core/src/main/resources/Interface/Fonts/Default.fnt b/jme3-core/src/main/resources/Interface/Fonts/Default.fnt new file mode 100644 index 000000000..97230e855 --- /dev/null +++ b/jme3-core/src/main/resources/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/jme3-core/src/main/resources/Interface/Fonts/Default.png b/jme3-core/src/main/resources/Interface/Fonts/Default.png new file mode 100644 index 000000000..3c2dee217 Binary files /dev/null and b/jme3-core/src/main/resources/Interface/Fonts/Default.png differ diff --git a/jme3-core/src/main/resources/joystick-mapping.properties b/jme3-core/src/main/resources/joystick-mapping.properties new file mode 100644 index 000000000..5f29e4105 --- /dev/null +++ b/jme3-core/src/main/resources/joystick-mapping.properties @@ -0,0 +1,58 @@ +# +# Add compatibility entries for different joysticks +# to map button and axis arrangments when possible. +# This is keyed off of the reported joystick name and +# reported button or axis logical ID. The value half is +# the new name as it will be reported through the Joystick +# interface. +# +# Keys with spaces in them should have those spaces escaped. +# Values do not need their spaces escaped. For example: +# +# Some\ Joystick.0=3 +# + + +# Final Fantasy XIV mapping +FF-GP1.0=3 +FF-GP1.1=2 +FF-GP1.2=1 +FF-GP1.3=0 + +# Xbox 360 Controller (Wireless) +Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).0=2 +Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).1=1 +Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).2=3 +Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).3=0 + +Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).6=8 +Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).7=9 + +Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).8=10 +Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).9=11 + +Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).rx=z +Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).ry=rz + +# requires custom code to support trigger buttons but this +# keeps it from confusing the .rx mapping. +Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).z=trigger + +# Xbox 360 Controller (copied from wireless version) +Controller\ (XBOX\ 360\ For\ Windows).0=2 +Controller\ (XBOX\ 360\ For\ Windows).1=1 +Controller\ (XBOX\ 360\ For\ Windows).2=3 +Controller\ (XBOX\ 360\ For\ Windows).3=0 + +Controller\ (XBOX\ 360\ For\ Windows).6=8 +Controller\ (XBOX\ 360\ For\ Windows).7=9 + +Controller\ (XBOX\ 360\ For\ Windows).8=10 +Controller\ (XBOX\ 360\ For\ Windows).9=11 + +Controller\ (XBOX\ 360\ For\ Windows).rx=z +Controller\ (XBOX\ 360\ For\ Windows).ry=rz + +# requires custom code to support trigger buttons but this +# keeps it from confusing the .rx mapping. +Controller\ (XBOX\ 360\ For\ Windows).z=trigger diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/ClasspathLocator.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/ClasspathLocator.java new file mode 100644 index 000000000..43feaa113 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/ClasspathLocator.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset.plugins; + +import com.jme3.asset.*; +import com.jme3.system.JmeSystem; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.logging.Logger; + +/** + * The 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 = ""; + + public ClasspathLocator(){ + } + + public void setRootPath(String rootPath) { + this.root = rootPath; + if (root.equals("/")) + root = ""; + else if (root.length() > 1){ + if (root.startsWith("/")){ + root = root.substring(1); + } + if (!root.endsWith("/")) + root += "/"; + } + } + + public AssetInfo locate(AssetManager manager, AssetKey key) { + URL url; + String name = key.getName(); + if (name.startsWith("/")) + name = name.substring(1); + + name = root + name; +// if (!name.startsWith(root)){ +// name = root + name; +// } + + if (JmeSystem.isLowPermissions()){ + url = ClasspathLocator.class.getResource("/" + name); + }else{ + url = Thread.currentThread().getContextClassLoader().getResource(name); + } + if (url == null) + return null; + + if (url.getProtocol().equals("file")){ + try { + String path = new File(url.toURI()).getCanonicalPath(); + + // convert to / for windows + if (File.separatorChar == '\\'){ + path = path.replace('\\', '/'); + } + + // compare path + if (!path.endsWith(name)){ + throw new AssetNotFoundException("Asset name doesn't match requirements.\n"+ + "\"" + path + "\" doesn't match \"" + name + "\""); + } + } catch (URISyntaxException ex) { + throw new AssetLoadException("Error converting URL to URI", ex); + } catch (IOException ex){ + throw new AssetLoadException("Failed to get canonical path for " + url, ex); + } + } + + try{ + return UrlAssetInfo.create(manager, key, url); + }catch (IOException ex){ + // This is different handling than URL locator + // since classpath locating would return null at the getResource() + // call, otherwise there's a more critical error... + throw new AssetLoadException("Failed to read URL " + url, ex); + } + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/FileLocator.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/FileLocator.java new file mode 100644 index 000000000..88d4d3ee0 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/FileLocator.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset.plugins; + +import com.jme3.asset.*; +import java.io.*; + +/** + * 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(); + + try { + root = new File(rootPath).getCanonicalFile(); + if (!root.isDirectory()){ + throw new IllegalArgumentException("Given root path \"" + root + "\" is not a directory"); + } + } catch (IOException ex) { + throw new AssetLoadException("Root path is invalid", ex); + } + } + + private static class AssetInfoFile extends AssetInfo { + + private File file; + + public AssetInfoFile(AssetManager manager, AssetKey key, File file){ + super(manager, key); + this.file = file; + } + + @Override + public InputStream openStream() { + try{ + return new FileInputStream(file); + }catch (FileNotFoundException ex){ + // NOTE: Can still happen even if file.exists() is true, e.g. + // permissions issue and similar + throw new AssetLoadException("Failed to open file: " + file, ex); + } + } + } + + public AssetInfo locate(AssetManager manager, AssetKey key) { + String name = key.getName(); + File file = new File(root, name); + if (file.exists() && file.isFile()){ + try { + // Now, check asset name requirements + String canonical = file.getCanonicalPath(); + String absolute = file.getAbsolutePath(); + if (!canonical.endsWith(absolute)){ + throw new AssetNotFoundException("Asset name doesn't match requirements.\n"+ + "\"" + canonical + "\" doesn't match \"" + absolute + "\""); + } + } catch (IOException ex) { + throw new AssetLoadException("Failed to get file canonical path " + file, ex); + } + + return new AssetInfoFile(manager, key, file); + }else{ + return null; + } + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/HttpZipLocator.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/HttpZipLocator.java new file mode 100644 index 000000000..ab964ab2c --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/HttpZipLocator.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLocator; +import com.jme3.asset.AssetManager; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; +import java.util.zip.ZipEntry; + +public class HttpZipLocator implements AssetLocator { + + private static final Logger logger = Logger.getLogger(HttpZipLocator.class.getName()); + + private URL zipUrl; + private String rootPath = ""; + private int numEntries; + private int tableOffset; + private int tableLength; + private HashMap entries; + + private static final ByteBuffer byteBuf = ByteBuffer.allocate(250); + private static final CharBuffer charBuf = CharBuffer.allocate(250); + private static final CharsetDecoder utf8Decoder; + + static { + Charset utf8 = Charset.forName("UTF-8"); + utf8Decoder = utf8.newDecoder(); + } + + private static class ZipEntry2 { + String name; + int length; + int offset; + int compSize; + long crc; + boolean deflate; + + @Override + public String toString(){ + return "ZipEntry[name=" + name + + ", length=" + length + + ", compSize=" + compSize + + ", offset=" + offset + "]"; + } + } + + private static int get16(byte[] b, int off) { + return (b[off++] & 0xff) | + ((b[off] & 0xff) << 8); + } + + private static int get32(byte[] b, int off) { + return (b[off++] & 0xff) | + ((b[off++] & 0xff) << 8) | + ((b[off++] & 0xff) << 16) | + ((b[off] & 0xff) << 24); + } + + private static long getu32(byte[] b, int off) throws IOException{ + return (b[off++]&0xff) | + ((b[off++]&0xff) << 8) | + ((b[off++]&0xff) << 16) | + (((long)(b[off]&0xff)) << 24); + } + + private static String getUTF8String(byte[] b, int off, int len) throws CharacterCodingException { + StringBuilder sb = new StringBuilder(); + + int read = 0; + while (read < len){ + // Either read n remaining bytes in b or 250 if n is higher. + int toRead = Math.min(len - read, byteBuf.capacity()); + + boolean endOfInput = toRead < byteBuf.capacity(); + + // read 'toRead' bytes into byteBuf + byteBuf.put(b, off + read, toRead); + + // set limit to position and set position to 0 + // so data can be decoded + byteBuf.flip(); + + // decode data in byteBuf + CoderResult result = utf8Decoder.decode(byteBuf, charBuf, endOfInput); + + // if the result is not an underflow its an error + // that cannot be handled. + // if the error is an underflow and its the end of input + // then the decoder expects more bytes but there are no more => error + if (!result.isUnderflow() || !endOfInput){ + result.throwException(); + } + + // flip the char buf to get the string just decoded + charBuf.flip(); + + // append the decoded data into the StringBuilder + sb.append(charBuf.toString()); + + // clear buffers for next use + byteBuf.clear(); + charBuf.clear(); + + read += toRead; + } + + return sb.toString(); + } + + private InputStream readData(int offset, int length) throws IOException{ + HttpURLConnection conn = (HttpURLConnection) zipUrl.openConnection(); + conn.setDoOutput(false); + conn.setUseCaches(false); + conn.setInstanceFollowRedirects(false); + String range = "-"; + if (offset != Integer.MAX_VALUE){ + range = offset + range; + } + if (length != Integer.MAX_VALUE){ + if (offset != Integer.MAX_VALUE){ + range = range + (offset + length - 1); + }else{ + range = range + length; + } + } + + conn.setRequestProperty("Range", "bytes=" + range); + conn.connect(); + if (conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL){ + return conn.getInputStream(); + }else if (conn.getResponseCode() == HttpURLConnection.HTTP_OK){ + throw new IOException("Your server does not support HTTP feature Content-Range. Please contact your server administrator."); + }else{ + throw new IOException(conn.getResponseCode() + " " + conn.getResponseMessage()); + } + } + + private int readTableEntry(byte[] table, int offset) throws IOException{ + if (get32(table, offset) != ZipEntry.CENSIG){ + throw new IOException("Central directory error, expected 'PK12'"); + } + + int nameLen = get16(table, offset + ZipEntry.CENNAM); + int extraLen = get16(table, offset + ZipEntry.CENEXT); + int commentLen = get16(table, offset + ZipEntry.CENCOM); + int newOffset = offset + ZipEntry.CENHDR + nameLen + extraLen + commentLen; + + int flags = get16(table, offset + ZipEntry.CENFLG); + if ((flags & 1) == 1){ + // ignore this entry, it uses encryption + return newOffset; + } + + int method = get16(table, offset + ZipEntry.CENHOW); + if (method != ZipEntry.DEFLATED && method != ZipEntry.STORED){ + // ignore this entry, it uses unknown compression method + return newOffset; + } + + String name = getUTF8String(table, offset + ZipEntry.CENHDR, nameLen); + if (name.charAt(name.length()-1) == '/'){ + // ignore this entry, it is directory node + // or it has no name (?) + return newOffset; + } + + ZipEntry2 entry = new ZipEntry2(); + entry.name = name; + entry.deflate = (method == ZipEntry.DEFLATED); + entry.crc = getu32(table, offset + ZipEntry.CENCRC); + entry.length = get32(table, offset + ZipEntry.CENLEN); + entry.compSize = get32(table, offset + ZipEntry.CENSIZ); + entry.offset = get32(table, offset + ZipEntry.CENOFF); + + // we want offset directly into file data .. + // move the offset forward to skip the LOC header + entry.offset += ZipEntry.LOCHDR + nameLen + extraLen; + + entries.put(entry.name, entry); + + return newOffset; + } + + private void fillByteArray(byte[] array, InputStream source) throws IOException{ + int total = 0; + int length = array.length; + while (total < length) { + int read = source.read(array, total, length - total); + if (read < 0) + throw new IOException("Failed to read entire array"); + + total += read; + } + } + + private void readCentralDirectory() throws IOException{ + InputStream in = readData(tableOffset, tableLength); + byte[] header = new byte[tableLength]; + + // Fix for "PK12 bug in town.zip": sometimes + // not entire byte array will be read with InputStream.read() + // (especially for big headers) + fillByteArray(header, in); + +// in.read(header); + in.close(); + + entries = new HashMap(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/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java new file mode 100644 index 000000000..dde2c0165 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLoadException; +import com.jme3.asset.AssetManager; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +/** + * Handles loading of assets from a URL + * + * @author Kirill Vainer + */ +public class UrlAssetInfo extends AssetInfo { + + private URL url; + private InputStream in; + + public static UrlAssetInfo create(AssetManager assetManager, AssetKey key, URL url) throws IOException { + // Check if URL can be reached. This will throw + // IOException which calling code will handle. + URLConnection conn = url.openConnection(); + conn.setUseCaches(false); + InputStream in = conn.getInputStream(); + + // For some reason url cannot be reached? + if (in == null){ + return null; + }else{ + return new UrlAssetInfo(assetManager, key, url, in); + } + } + + private UrlAssetInfo(AssetManager assetManager, AssetKey key, URL url, InputStream in) throws IOException { + super(assetManager, key); + this.url = url; + this.in = in; + } + + public boolean hasInitialConnection(){ + return in != null; + } + + @Override + public InputStream openStream() { + if (in != null){ + // Reuse the already existing stream (only once) + InputStream in2 = in; + in = null; + return in2; + }else{ + // Create a new stream for subsequent invocations. + try { + URLConnection conn = url.openConnection(); + conn.setUseCaches(false); + return conn.getInputStream(); + } catch (IOException ex) { + throw new AssetLoadException("Failed to read URL " + url, ex); + } + } + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlLocator.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlLocator.java new file mode 100644 index 000000000..1de7008d8 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlLocator.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLocator; +import com.jme3.asset.AssetManager; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * 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 Kirill Vainer + */ +public class UrlLocator implements AssetLocator { + + private static final Logger logger = Logger.getLogger(UrlLocator.class.getName()); + private URL root; + + public void setRootPath(String rootPath) { + try { + this.root = new URL(rootPath); + } catch (MalformedURLException ex) { + throw new IllegalArgumentException("Invalid rootUrl specified", ex); + } + } + + public AssetInfo locate(AssetManager manager, AssetKey key) { + String name = key.getName(); + try{ + //TODO: remove workaround for SDK +// URL url = new URL(root, name); + if(name.startsWith("/")){ + name = name.substring(1); + } + URL url = new URL(root.toExternalForm() + name); + return UrlAssetInfo.create(manager, key, url); + }catch (FileNotFoundException e){ + return null; + }catch (IOException ex){ + logger.log(Level.WARNING, "Error while locating " + name, ex); + return null; + } + } + + +} diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java new file mode 100644 index 000000000..9f1508f44 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset.plugins; + +import com.jme3.asset.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * 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){ + throw new AssetLoadException("Failed to load zip entry: "+entry, ex); + } + } + } + + public void setRootPath(String rootPath) { + try{ + zipfile = new ZipFile(new File(rootPath), ZipFile.OPEN_READ); + }catch (IOException ex){ + throw new AssetLoadException("Failed to open zip file: " + rootPath, ex); + } + } + + public AssetInfo locate(AssetManager manager, AssetKey key) { + String name = key.getName(); + ZipEntry entry = zipfile.getEntry(name); + if (entry == null) + return null; + + return new JarAssetInfo(manager, key, entry); + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java b/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java new file mode 100644 index 000000000..74feef0ff --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.audio.AudioBuffer; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioKey; +import com.jme3.audio.AudioStream; +import com.jme3.util.BufferUtils; +import com.jme3.util.LittleEndien; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class WAVLoader implements AssetLoader { + + private static final Logger logger = Logger.getLogger(WAVLoader.class.getName()); + + // all these are in big endian + private static final int i_RIFF = 0x46464952; + private static final int i_WAVE = 0x45564157; + private static final int i_fmt = 0x20746D66; + private static final int i_data = 0x61746164; + + private boolean readStream = false; + + private AudioBuffer audioBuffer; + private AudioStream audioStream; + private AudioData audioData; + private int bytesPerSec; + private float duration; + + private LittleEndien in; + + private void readFormatChunk(int size) throws IOException{ + // if other compressions are supported, size doesn't have to be 16 +// if (size != 16) +// logger.warning("Expected size of format chunk to be 16"); + + int compression = in.readShort(); + if (compression != 1){ + throw new IOException("WAV Loader only supports PCM wave files"); + } + + int channels = in.readShort(); + int sampleRate = in.readInt(); + + bytesPerSec = in.readInt(); // used to calculate duration + + int bytesPerSample = in.readShort(); + int bitsPerSample = in.readShort(); + + int expectedBytesPerSec = (bitsPerSample * channels * sampleRate) / 8; + if (expectedBytesPerSec != bytesPerSec){ + logger.log(Level.WARNING, "Expected {0} bytes per second, got {1}", + new Object[]{expectedBytesPerSec, bytesPerSec}); + } + + if (bitsPerSample != 8 && bitsPerSample != 16) + throw new IOException("Only 8 and 16 bits per sample are supported!"); + + if ( (bitsPerSample / 8) * channels != bytesPerSample) + throw new IOException("Invalid bytes per sample value"); + + if (bytesPerSample * sampleRate != bytesPerSec) + throw new IOException("Invalid bytes per second value"); + + audioData.setupFormat(channels, bitsPerSample, sampleRate); + + int remaining = size - 16; + if (remaining > 0){ + in.skipBytes(remaining); + } + } + + private void readDataChunkForBuffer(int len) throws IOException { + ByteBuffer data = BufferUtils.createByteBuffer(len); + byte[] buf = new byte[512]; + int read = 0; + while ( (read = in.read(buf)) > 0){ + data.put(buf, 0, Math.min(read, data.remaining()) ); + } + data.flip(); + audioBuffer.updateData(data); + in.close(); + } + + private void readDataChunkForStream(int len) throws IOException { + audioStream.updateData(in, duration); + } + + private AudioData load(InputStream inputStream, boolean stream) throws IOException{ + this.in = new LittleEndien(inputStream); + + int sig = in.readInt(); + if (sig != i_RIFF) + throw new IOException("File is not a WAVE file"); + + // skip size + in.readInt(); + if (in.readInt() != i_WAVE) + throw new IOException("WAVE File does not contain audio"); + + readStream = stream; + if (readStream){ + audioStream = new AudioStream(); + audioData = audioStream; + }else{ + audioBuffer = new AudioBuffer(); + audioData = audioBuffer; + } + + while (true) { + int type = in.readInt(); + int len = in.readInt(); + + switch (type) { + case i_fmt: + readFormatChunk(len); + break; + case i_data: + // Compute duration based on data chunk size + duration = len / bytesPerSec; + + if (readStream) { + readDataChunkForStream(len); + } else { + readDataChunkForBuffer(len); + } + return audioData; + default: + int skipped = in.skipBytes(len); + if (skipped <= 0) { + return null; + } + break; + } + } + } + + public Object load(AssetInfo info) throws IOException { + AudioData data; + InputStream inputStream = null; + try { + inputStream = info.openStream(); + data = load(inputStream, ((AudioKey)info.getKey()).isStream()); + if (data instanceof AudioStream){ + inputStream = null; + } + return data; + } finally { + if (inputStream != null){ + inputStream.close(); + } + } + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/cursors/plugins/JmeCursor.java b/jme3-core/src/plugins/java/com/jme3/cursors/plugins/JmeCursor.java new file mode 100644 index 000000000..1c5f4a21d --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/cursors/plugins/JmeCursor.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cursors.plugins; + +import java.nio.IntBuffer; + +/** + * A Jme representation of the LWJGL Cursor class. + * + * @author MadJack + * @creation Jun 6, 2012 12:12:38 PM + */ +public class JmeCursor { + + private int width; + private int height; + private int xHotSpot; + private int yHotSpot; + private int numImages; + private IntBuffer imagesData; + private IntBuffer imagesDelay; + + /** + * Queries the cursor's height. Note that + * the coordinate system is the same as OpenGL. 0, 0 being lower left. + * @return the height in pixel. + */ + public int getHeight() { + return height; + } + + /** + * Queries the cursor's images' data. + * @return An {@link IntBuffer} containing the cursor's image(s) data in + * sequence. + */ + public IntBuffer getImagesData() { + return imagesData; + } + + /** + * Queries the cursor's delay for each frame. + * @return An {@link IntBuffer} containing the cursor's delay in + * sequence. The delay is expressed in milliseconds. + */ + public IntBuffer getImagesDelay() { + return imagesDelay; + } + + /** + * Queries the number of images contained in the cursor. Static cursors should + * contain only 1 image. + * @return The number of image(s) composing the cursor. 1 if the cursor is + * static. + */ + public int getNumImages() { + return numImages; + } + + /** + * Queries the cursor's width. Note that + * the coordinate system is the same as OpenGL. 0, 0 being lower left. + * @return the width of the cursor in pixel. + */ + public int getWidth() { + return width; + } + + /** + * Queries the cursor's X hotspot coordinate. Note that + * the coordinate system is the same as OpenGL. 0, 0 being lower left. + * @return the coordinate on the cursor's X axis where the hotspot is located. + */ + public int getXHotSpot() { + return xHotSpot; + } + + /** + * Queries the cursor's Y hotspot coordinate. Note that + * the coordinate system is the same as OpenGL. 0, 0 being lower left. + * @return the coordinate on the cursor's Y axis where the hotspot is located. + */ + public int getYHotSpot() { + return yHotSpot; + } + + /** + * Sets the cursor's height. + * @param height The height of the cursor in pixels. Note that all images + * in a cursor have to be the same size. + */ + public void setHeight(int height) { + this.height = height; + } + + /** + * Sets the cursor's image(s) data. Each image data should be consecutively + * stored in the {@link IntBuffer} if more tha one image is contained in the + * cursor. + * @param imagesData the cursor's image(s) data. Each image data should be consecutively + * stored in the {@link IntBuffer} if more than one image is contained in the + * cursor. + */ + public void setImagesData(IntBuffer imagesData) { + this.imagesData = imagesData; + } + + /** + * Sets the cursor image delay for each frame of an animated cursor. If the + * cursor has no animation and consist of only 1 image, null is expected. + * @param imagesDelay + */ + public void setImagesDelay(IntBuffer imagesDelay) { + this.imagesDelay = imagesDelay; + } + + /** + * Sets the number of images in the cursor. + * @param numImages number of images in the cursor. + */ + public void setNumImages(int numImages) { + this.numImages = numImages; + } + + /** + * Sets the cursor's width. + * @param width The width of the cursor in pixels. Note that all images + * in a cursor have to be the same size. + */ + public void setWidth(int width) { + this.width = width; + } + + /** + * Sets the cursor's X coordinate for its hotspot. + * @param xHotSpot the cursor's X axis coordinate for its hotspot. Note that + * the coordinate system is the same as OpenGL. 0, 0 being lower left. + */ + public void setxHotSpot(int xHotSpot) { + this.xHotSpot = xHotSpot; + } + + /** + * Sets the cursor's Y axis coordinate for its hotspot. + * @param yHotSpot the cursor's Y axis coordinate for its hotspot. Note that + * the coordinate system is the same as OpenGL. 0, 0 being lower left. + */ + public void setyHotSpot(int yHotSpot) { + this.yHotSpot = yHotSpot; + } + + +} diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryClassField.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryClassField.java new file mode 100644 index 000000000..f2857707e --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryClassField.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryClassObject.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryClassObject.java new file mode 100644 index 000000000..551439e8d --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryClassObject.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export.binary; + +import java.util.HashMap; + +class BinaryClassObject { + + // When exporting, use nameFields field, importing use aliasFields. + HashMap nameFields; + HashMap aliasFields; + + byte[] alias; + String className; + int[] classHierarchyVersions; +} diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java new file mode 100644 index 000000000..c02f7088a --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java @@ -0,0 +1,404 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export.binary; + +import com.jme3.export.FormatVersion; +import com.jme3.export.JmeExporter; +import com.jme3.export.Savable; +import com.jme3.export.SavableClassUtil; +import com.jme3.math.FastMath; +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Exports to the jME Binary Format. Format descriptor: (each numbered item + * denotes a series of bytes that follows sequentially one after the next.) + *

      + * 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(); + + // write signature and version + os.write(ByteUtils.convertToBytes(FormatVersion.SIGNATURE)); + os.write(ByteUtils.convertToBytes(FormatVersion.VERSION)); + + int id = processBinarySavable(object); + + // write out tag table + int classTableSize = 0; + int classNum = classes.keySet().size(); + int aliasSize = ((int) FastMath.log(classNum, 256) + 1); // make all + // aliases a + // fixed width + + os.write(ByteUtils.convertToBytes(classNum)); + for (String key : classes.keySet()) { + BinaryClassObject bco = classes.get(key); + + // write alias + byte[] aliasBytes = fixClassAlias(bco.alias, + aliasSize); + os.write(aliasBytes); + classTableSize += aliasSize; + + // jME3 NEW: Write class hierarchy version numbers + os.write( bco.classHierarchyVersions.length ); + for (int version : bco.classHierarchyVersions){ + os.write(ByteUtils.convertToBytes(version)); + } + classTableSize += 1 + bco.classHierarchyVersions.length * 4; + + // write classname size & classname + byte[] classBytes = key.getBytes(); + os.write(ByteUtils.convertToBytes(classBytes.length)); + os.write(classBytes); + classTableSize += 4 + classBytes.length; + + // for each field, write alias, type, and name + os.write(ByteUtils.convertToBytes(bco.nameFields.size())); + for (String fieldName : bco.nameFields.keySet()) { + BinaryClassField bcf = bco.nameFields.get(fieldName); + os.write(bcf.alias); + os.write(bcf.type); + + // write classname size & classname + byte[] fNameBytes = fieldName.getBytes(); + os.write(ByteUtils.convertToBytes(fNameBytes.length)); + os.write(fNameBytes); + classTableSize += 2 + 4 + fNameBytes.length; + } + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // write out data to a seperate stream + int location = 0; + // keep track of location for each piece + HashMap> 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, aliasSize); + out.write(aliasBytes); + location += aliasSize; + BinaryOutputCapsule cap = contentTable.get(savable).getContent(); + out.write(ByteUtils.convertToBytes(cap.bytes.length)); + location += 4; // length of bytes + out.write(cap.bytes); + location += cap.bytes.length; + } + + // write out location table + // tag/location + int numLocations = locationTable.keySet().size(); + os.write(ByteUtils.convertToBytes(numLocations)); + int locationTableSize = 0; + for (Integer key : locationTable.keySet()) { + os.write(ByteUtils.convertToBytes(key)); + os.write(ByteUtils.convertToBytes(locationTable.get(key))); + locationTableSize += 8; + } + + // write out number of root ids - hardcoded 1 for now + os.write(ByteUtils.convertToBytes(1)); + + // write out root id + os.write(ByteUtils.convertToBytes(id)); + + // append stream to the output stream + out.writeTo(os); + + + out = null; + os = null; + + if (debug ) { + logger.fine("Stats:"); + logger.log(Level.FINE, "classes: {0}", classNum); + logger.log(Level.FINE, "class table: {0} bytes", classTableSize); + logger.log(Level.FINE, "objects: {0}", numLocations); + logger.log(Level.FINE, "location table: {0} bytes", locationTableSize); + logger.log(Level.FINE, "data: {0} bytes", location); + } + + return true; + } + + protected String getChunk(BinaryIdContentPair pair) { + return new String(pair.getContent().bytes, 0, Math.min(64, pair + .getContent().bytes.length)); + } + + protected int findPrevMatch(BinaryIdContentPair oldPair, + ArrayList 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); + try { + return save(object, fos); + } finally { + if (fos != null) { + fos.close(); + } + } + } + + public BinaryOutputCapsule getCapsule(Savable object) { + return contentTable.get(object).getContent(); + } + + private BinaryClassObject createClassObject(Class clazz) throws IOException{ + BinaryClassObject bco = new BinaryClassObject(); + bco.alias = generateTag(); + bco.nameFields = new HashMap(); + bco.classHierarchyVersions = SavableClassUtil.getSavableVersions(clazz); + + classes.put(clazz.getName(), bco); + + return bco; + } + + public int processBinarySavable(Savable object) throws IOException { + if (object == null) { + return -1; + } + Class clazz = object.getClass(); + BinaryClassObject bco = classes.get(object.getClass().getName()); + // is this class been looked at before? in tagTable? + if (bco == null) { + bco = createClassObject(object.getClass()); + } + + // is object in contentTable? + if (contentTable.get(object) != null) { + return (contentTable.get(object).getId()); + } + BinaryIdContentPair newPair = generateIdContentPair(bco); + BinaryIdContentPair old = contentTable.put(object, newPair); + if (old == null) { + contentKeys.add(object); + } + object.write(this); + newPair.getContent().finish(); + return newPair.getId(); + + } + + protected byte[] generateTag() { + int width = ((int) FastMath.log(aliasCount, 256) + 1); + int count = aliasCount; + aliasCount++; + byte[] bytes = new byte[width]; + for (int x = width - 1; x >= 0; x--) { + int pow = (int) FastMath.pow(256, x); + int factor = count / pow; + bytes[width - x - 1] = (byte) factor; + count %= pow; + } + return bytes; + } + + protected BinaryIdContentPair generateIdContentPair(BinaryClassObject bco) { + BinaryIdContentPair pair = new BinaryIdContentPair(idCount++, + new BinaryOutputCapsule(this, bco)); + return pair; + } +} \ No newline at end of file diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryIdContentPair.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryIdContentPair.java new file mode 100644 index 000000000..a79442236 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryIdContentPair.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java new file mode 100644 index 000000000..933bee721 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export.binary; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetManager; +import com.jme3.export.*; +import com.jme3.math.FastMath; +import java.io.*; +import java.net.URL; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Joshua Slack + * @author Kirill Vainer - Version number, Fast buffer reading + */ +public final class BinaryImporter implements JmeImporter { + private static final Logger logger = Logger.getLogger(BinaryImporter.class + .getName()); + + private AssetManager assetManager; + + //Key - alias, object - bco + private HashMap 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 int formatVersion; + + private static final boolean fastRead = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN; + + public BinaryImporter() { + } + + public int getFormatVersion(){ + return formatVersion; + } + + public static boolean canUseFastBuffers(){ + return fastRead; + } + + public static BinaryImporter getInstance() { + return new BinaryImporter(); + } + + public void setAssetManager(AssetManager manager){ + this.assetManager = manager; + } + + public AssetManager getAssetManager(){ + return assetManager; + } + + public Object load(AssetInfo info){ +// if (!(info.getKey() instanceof ModelKey)) +// throw new IllegalArgumentException("Model assets must be loaded using a ModelKey"); + + assetManager = info.getManager(); + + InputStream is = null; + try { + is = info.openStream(); + Savable s = load(is); + + return s; + } catch (IOException ex) { + logger.log(Level.SEVERE, "An error occured while loading jME binary object", ex); + } finally { + if (is != null){ + try { + is.close(); + } catch (IOException ex) {} + } + } + return null; + } + + public Savable load(InputStream is) throws IOException { + return load(is, null, null); + } + + public Savable load(InputStream is, ReadListener listener) throws IOException { + return load(is, listener, null); + } + + public Savable load(InputStream is, ReadListener listener, ByteArrayOutputStream baos) throws IOException { + contentTable.clear(); + BufferedInputStream bis = new BufferedInputStream(is); + + int numClasses; + + // Try to read signature + int maybeSignature = ByteUtils.readInt(bis); + if (maybeSignature == FormatVersion.SIGNATURE){ + // this is a new version J3O file + formatVersion = ByteUtils.readInt(bis); + numClasses = ByteUtils.readInt(bis); + + // check if this binary is from the future + if (formatVersion > FormatVersion.VERSION){ + throw new IOException("The binary file is of newer version than expected! " + + formatVersion + " > " + FormatVersion.VERSION); + } + }else{ + // this is an old version J3O file + // the signature was actually the class count + numClasses = maybeSignature; + + // 0 indicates version before we started adding + // version numbers + formatVersion = 0; + } + + int bytes = 4; + aliasWidth = ((int)FastMath.log(numClasses, 256) + 1); + + classes.clear(); + for(int i = 0; i < numClasses; i++) { + String alias = readString(bis, aliasWidth); + + // jME3 NEW: Read class version number + int[] classHierarchyVersions; + if (formatVersion >= 1){ + int classHierarchySize = bis.read(); + classHierarchyVersions = new int[classHierarchySize]; + for (int j = 0; j < classHierarchySize; j++){ + classHierarchyVersions[j] = ByteUtils.readInt(bis); + } + }else{ + classHierarchyVersions = new int[]{ 0 }; + } + + // read classname and classname size + int classLength = ByteUtils.readInt(bis); + String className = readString(bis, classLength); + + BinaryClassObject bco = new BinaryClassObject(); + bco.alias = alias.getBytes(); + bco.className = className; + bco.classHierarchyVersions = classHierarchyVersions; + + int fields = ByteUtils.readInt(bis); + bytes += (8 + aliasWidth + classLength); + + bco.nameFields = new HashMap(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.fine("Importer Stats: "); + logger.log(Level.FINE, "Tags: {0}", numClasses); + logger.log(Level.FINE, "Objects: {0}", numLocs); + logger.log(Level.FINE, "Data Size: {0}", dataArray.length); + } + dataArray = null; + return rVal; + } + + public Savable load(URL f) throws IOException { + return load(f, null); + } + + public Savable load(URL f, ReadListener listener) throws IOException { + InputStream is = f.openStream(); + Savable rVal = load(is, listener); + is.close(); + return rVal; + } + + public Savable load(File f) throws IOException { + return load(f, null); + } + + public Savable load(File f, ReadListener listener) throws IOException { + FileInputStream fis = new FileInputStream(f); + try { + return load(fis, listener); + } finally { + if (fis != null) { + fis.close(); + } + } + } + + public Savable load(byte[] data) throws IOException { + ByteArrayInputStream bais = new ByteArrayInputStream(data); + Savable rVal = load(bais); + bais.close(); + return rVal; + } + + @Override + public InputCapsule getCapsule(Savable id) { + return capsuleTable.get(id); + } + + protected String readString(InputStream f, int length) throws IOException { + byte[] data = new byte[length]; + for(int j = 0; j < length; j++) { + data[j] = (byte)f.read(); + } + + return new String(data); + } + + protected String readString(int length, int offset) throws IOException { + byte[] data = new byte[length]; + for(int j = 0; j < length; j++) { + data[j] = dataArray[j+offset]; + } + + return new String(data); + } + + public Savable readObject(int id) { + + if(contentTable.get(id) != null) { + return contentTable.get(id); + } + + try { + int loc = locationTable.get(id); + + String alias = readString(aliasWidth, loc); + loc+=aliasWidth; + + BinaryClassObject bco = classes.get(alias); + + if(bco == null) { + logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "NULL class object: " + alias); + return null; + } + + int dataLength = ByteUtils.convertIntFromBytes(dataArray, loc); + loc+=4; + + Savable out = null; + if (assetManager != null) { + out = SavableClassUtil.fromName(bco.className, assetManager.getClassLoaders()); + } else { + out = SavableClassUtil.fromName(bco.className); + } + + BinaryInputCapsule cap = new BinaryInputCapsule(this, out, bco); + cap.setContent(dataArray, loc, loc+dataLength); + + capsuleTable.put(out, cap); + contentTable.put(id, out); + + out.read(this); + + capsuleTable.remove(out); + + return out; + + } catch (IOException e) { + logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "Exception", e); + return null; + } catch (ClassNotFoundException e) { + logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "Exception", e); + return null; + } catch (InstantiationException e) { + logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "Exception", e); + return null; + } catch (IllegalAccessException e) { + logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "Exception", e); + return null; + } + } +} \ No newline at end of file diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryInputCapsule.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryInputCapsule.java new file mode 100644 index 000000000..794617393 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryInputCapsule.java @@ -0,0 +1,1379 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export.binary; + +import com.jme3.export.InputCapsule; +import com.jme3.export.Savable; +import com.jme3.export.SavableClassUtil; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Joshua Slack + */ +final class BinaryInputCapsule implements InputCapsule { + + private static final Logger logger = Logger + .getLogger(BinaryInputCapsule.class.getName()); + + protected BinaryImporter importer; + protected BinaryClassObject cObj; + protected Savable savable; + protected HashMap fieldData; + + protected int index = 0; + + public BinaryInputCapsule(BinaryImporter importer, Savable savable, BinaryClassObject bco) { + this.importer = importer; + this.cObj = bco; + this.savable = savable; + } + + public void setContent(byte[] content, int start, int limit) { + fieldData = new HashMap(); + for (index = start; index < limit;) { + byte alias = content[index]; + + index++; + + try { + byte type = cObj.aliasFields.get(alias).type; + Object value = null; + + switch (type) { + case BinaryClassField.BITSET: { + value = readBitSet(content); + break; + } + case BinaryClassField.BOOLEAN: { + value = readBoolean(content); + break; + } + case BinaryClassField.BOOLEAN_1D: { + value = readBooleanArray(content); + break; + } + case BinaryClassField.BOOLEAN_2D: { + value = readBooleanArray2D(content); + break; + } + case BinaryClassField.BYTE: { + value = readByte(content); + break; + } + case BinaryClassField.BYTE_1D: { + value = readByteArray(content); + break; + } + case BinaryClassField.BYTE_2D: { + value = readByteArray2D(content); + break; + } + case BinaryClassField.BYTEBUFFER: { + value = readByteBuffer(content); + break; + } + case BinaryClassField.DOUBLE: { + value = readDouble(content); + break; + } + case BinaryClassField.DOUBLE_1D: { + value = readDoubleArray(content); + break; + } + case BinaryClassField.DOUBLE_2D: { + value = readDoubleArray2D(content); + break; + } + case BinaryClassField.FLOAT: { + value = readFloat(content); + break; + } + case BinaryClassField.FLOAT_1D: { + value = readFloatArray(content); + break; + } + case BinaryClassField.FLOAT_2D: { + value = readFloatArray2D(content); + break; + } + case BinaryClassField.FLOATBUFFER: { + value = readFloatBuffer(content); + break; + } + case BinaryClassField.FLOATBUFFER_ARRAYLIST: { + value = readFloatBufferArrayList(content); + break; + } + case BinaryClassField.BYTEBUFFER_ARRAYLIST: { + value = readByteBufferArrayList(content); + break; + } + case BinaryClassField.INT: { + value = readInt(content); + break; + } + case BinaryClassField.INT_1D: { + value = readIntArray(content); + break; + } + case BinaryClassField.INT_2D: { + value = readIntArray2D(content); + break; + } + case BinaryClassField.INTBUFFER: { + value = readIntBuffer(content); + break; + } + case BinaryClassField.LONG: { + value = readLong(content); + break; + } + case BinaryClassField.LONG_1D: { + value = readLongArray(content); + break; + } + case BinaryClassField.LONG_2D: { + value = readLongArray2D(content); + break; + } + case BinaryClassField.SAVABLE: { + value = readSavable(content); + break; + } + case BinaryClassField.SAVABLE_1D: { + value = readSavableArray(content); + break; + } + case BinaryClassField.SAVABLE_2D: { + value = readSavableArray2D(content); + break; + } + case BinaryClassField.SAVABLE_ARRAYLIST: { + value = readSavableArray(content); + break; + } + case BinaryClassField.SAVABLE_ARRAYLIST_1D: { + value = readSavableArray2D(content); + break; + } + case BinaryClassField.SAVABLE_ARRAYLIST_2D: { + value = readSavableArray3D(content); + break; + } + case BinaryClassField.SAVABLE_MAP: { + value = readSavableMap(content); + break; + } + case BinaryClassField.STRING_SAVABLE_MAP: { + value = readStringSavableMap(content); + break; + } + case BinaryClassField.INT_SAVABLE_MAP: { + value = readIntSavableMap(content); + break; + } + case BinaryClassField.SHORT: { + value = readShort(content); + break; + } + case BinaryClassField.SHORT_1D: { + value = readShortArray(content); + break; + } + case BinaryClassField.SHORT_2D: { + value = readShortArray2D(content); + break; + } + case BinaryClassField.SHORTBUFFER: { + value = readShortBuffer(content); + break; + } + case BinaryClassField.STRING: { + value = readString(content); + break; + } + case BinaryClassField.STRING_1D: { + value = readStringArray(content); + break; + } + case BinaryClassField.STRING_2D: { + value = readStringArray2D(content); + break; + } + + default: + // skip put statement + continue; + } + + fieldData.put(alias, value); + + } catch (IOException e) { + logger.logp(Level.SEVERE, this.getClass().toString(), + "setContent(byte[] content)", "Exception", e); + } + } + } + + public int getSavableVersion(Class desiredClass){ + return SavableClassUtil.getSavedSavableVersion(savable, desiredClass, + cObj.classHierarchyVersions, importer.getFormatVersion()); + } + + public BitSet readBitSet(String name, BitSet defVal) throws IOException { + BinaryClassField field = cObj.nameFields.get(name); + if (field == null || !fieldData.containsKey(field.alias)) + return defVal; + return (BitSet) fieldData.get(field.alias); + } + + public boolean readBoolean(String name, boolean defVal) throws IOException { + BinaryClassField field = cObj.nameFields.get(name); + if (field == null || !fieldData.containsKey(field.alias)) + return defVal; + return ((Boolean) fieldData.get(field.alias)).booleanValue(); + } + + public boolean[] readBooleanArray(String name, boolean[] defVal) + throws IOException { + BinaryClassField field = cObj.nameFields.get(name); + if (field == null || !fieldData.containsKey(field.alias)) + return defVal; + return (boolean[]) fieldData.get(field.alias); + } + + public boolean[][] readBooleanArray2D(String name, boolean[][] defVal) + throws IOException { + BinaryClassField field = cObj.nameFields.get(name); + if (field == null || !fieldData.containsKey(field.alias)) + return defVal; + return (boolean[][]) fieldData.get(field.alias); + } + + public byte readByte(String name, byte defVal) throws IOException { + BinaryClassField field = cObj.nameFields.get(name); + if (field == null || !fieldData.containsKey(field.alias)) + return defVal; + return ((Byte) fieldData.get(field.alias)).byteValue(); + } + + public byte[] readByteArray(String name, byte[] defVal) throws IOException { + BinaryClassField field = cObj.nameFields.get(name); + if (field == null || !fieldData.containsKey(field.alias)) + return defVal; + return (byte[]) fieldData.get(field.alias); + } + + public byte[][] readByteArray2D(String name, byte[][] defVal) + throws IOException { + BinaryClassField field = cObj.nameFields.get(name); + if (field == null || !fieldData.containsKey(field.alias)) + return defVal; + return (byte[][]) fieldData.get(field.alias); + } + + public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) + throws IOException { + BinaryClassField field = cObj.nameFields.get(name); + if (field == null || !fieldData.containsKey(field.alias)) + return defVal; + return (ByteBuffer) fieldData.get(field.alias); + } + + @SuppressWarnings("unchecked") + public ArrayList 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/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryOutputCapsule.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryOutputCapsule.java new file mode 100644 index 000000000..380772eaf --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryOutputCapsule.java @@ -0,0 +1,943 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + Arrays.hashCode(this.bytes); + return hash; + } + + public void finish() { + // renamed to finish as 'finalize' in java.lang.Object should not be + // overridden like this + // - finalize should not be called directly but is called by garbage + // collection!!! + bytes = baos.toByteArray(); + baos = null; + } + + // byte primitive + + protected void write(byte value) throws IOException { + baos.write(value); + } + + protected void writeForBuffer(byte value) throws IOException { + baos.write(value); + } + + protected void write(byte[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + baos.write(value); + } + + protected void write(byte[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + // int primitive + + protected void write(int value) throws IOException { + baos.write(deflate(ByteUtils.convertToBytes(value))); + } + + protected void writeForBuffer(int value) throws IOException { + byte[] byteArray = new byte[4]; + byteArray[0] = (byte) value; + byteArray[1] = (byte) (value >> 8); + byteArray[2] = (byte) (value >> 16); + byteArray[3] = (byte) (value >> 24); + baos.write(byteArray); + } + + protected void write(int[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + protected void write(int[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + // float primitive + + protected void write(float value) throws IOException { + baos.write(ByteUtils.convertToBytes(value)); + } + + protected void writeForBuffer(float value) throws IOException { + int integer = Float.floatToIntBits(value); + writeForBuffer(integer); + } + + protected void write(float[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + protected void write(float[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + // double primitive + + protected void write(double value) throws IOException { + baos.write(ByteUtils.convertToBytes(value)); + } + + protected void write(double[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + protected void write(double[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + // long primitive + + protected void write(long value) throws IOException { + baos.write(deflate(ByteUtils.convertToBytes(value))); + } + + protected void write(long[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + protected void write(long[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + // short primitive + + protected void write(short value) throws IOException { + baos.write(ByteUtils.convertToBytes(value)); + } + + protected void writeForBuffer(short value) throws IOException { + byte[] byteArray = new byte[2]; + byteArray[0] = (byte) value; + byteArray[1] = (byte) (value >> 8); + baos.write(byteArray); + } + + protected void write(short[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + protected void write(short[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + // boolean primitive + + protected void write(boolean value) throws IOException { + baos.write(ByteUtils.convertToBytes(value)); + } + + protected void write(boolean[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + protected void write(boolean[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + // String + + protected void write(String value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + // write our output as UTF-8. Java misspells UTF-8 as UTF8 for official use in java.lang + byte[] bytes = value.getBytes("UTF8"); + write(bytes.length); + baos.write(bytes); + } + + protected void write(String[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + protected void write(String[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) + write(value[x]); + } + + // BitSet + + protected void write(BitSet value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.size()); + // TODO: MAKE THIS SMALLER + for (int x = 0, max = value.size(); x < max; x++) + write(value.get(x)); + } + + // DEFLATOR for int and long + + protected static byte[] deflate(byte[] bytes) { + int size = bytes.length; + if (size == 4) { + int possibleMagic = ByteUtils.convertIntFromBytes(bytes); + if (possibleMagic == NULL_OBJECT) + return NULL_BYTES; + else if (possibleMagic == DEFAULT_OBJECT) + return DEFAULT_BYTES; + } + for (int x = 0; x < bytes.length; x++) { + if (bytes[x] != 0) + break; + size--; + } + if (size == 0) + return new byte[1]; + + byte[] rVal = new byte[1 + size]; + rVal[0] = (byte) size; + for (int x = 1; x < rVal.length; x++) + rVal[x] = bytes[bytes.length - size - 1 + x]; + + return rVal; + } + + // BinarySavable + + protected void write(Savable object) throws IOException { + if (object == null) { + write(NULL_OBJECT); + return; + } + int id = exporter.processBinarySavable(object); + write(id); + } + + // BinarySavable array + + protected void write(Savable[] objects) throws IOException { + if (objects == null) { + write(NULL_OBJECT); + return; + } + write(objects.length); + for (int x = 0; x < objects.length; x++) { + write(objects[x]); + } + } + + protected void write(Savable[][] objects) throws IOException { + if (objects == null) { + write(NULL_OBJECT); + return; + } + write(objects.length); + for (int x = 0; x < objects.length; x++) { + write(objects[x]); + } + } + + // ArrayList + + 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/jme3-core/src/plugins/java/com/jme3/export/binary/ByteUtils.java b/jme3-core/src/plugins/java/com/jme3/export/binary/ByteUtils.java new file mode 100644 index 000000000..62b01ecc5 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/ByteUtils.java @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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/jme3-core/src/plugins/java/com/jme3/font/plugins/BitmapFontLoader.java b/jme3-core/src/plugins/java/com/jme3/font/plugins/BitmapFontLoader.java new file mode 100644 index 000000000..93d8bb5f0 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/font/plugins/BitmapFontLoader.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.font.plugins; + +import com.jme3.asset.*; +import com.jme3.font.BitmapCharacter; +import com.jme3.font.BitmapCharacterSet; +import com.jme3.font.BitmapFont; +import com.jme3.material.Material; +import com.jme3.material.MaterialDef; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.texture.Texture; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import org.omg.PortableInterceptor.SYSTEM_EXCEPTION; + +public class BitmapFontLoader implements AssetLoader { + + private BitmapFont load(AssetManager assetManager, String folder, InputStream in) throws IOException{ + MaterialDef spriteMat = + (MaterialDef) assetManager.loadAsset(new AssetKey("Common/MatDefs/Misc/Unshaded.j3md")); + BitmapCharacterSet charSet = new BitmapCharacterSet(); + Material[] matPages = null; + BitmapFont font = new BitmapFont(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String regex = "[\\s=]+"; + font.setCharSet(charSet); + String line; + while ((line = reader.readLine())!=null){ + String[] tokens = line.split(regex); + if (tokens[0].equals("info")){ + // Get rendered size + for (int i = 1; i < tokens.length; i++){ + if (tokens[i].equals("size")){ + charSet.setRenderedSize(Integer.parseInt(tokens[i + 1])); + } + } + }else if (tokens[0].equals("common")){ + // Fill out BitmapCharacterSet fields + for (int i = 1; i < tokens.length; i++){ + String token = tokens[i]; + if (token.equals("lineHeight")){ + charSet.setLineHeight(Integer.parseInt(tokens[i + 1])); + }else if (token.equals("base")){ + charSet.setBase(Integer.parseInt(tokens[i + 1])); + }else if (token.equals("scaleW")){ + charSet.setWidth(Integer.parseInt(tokens[i + 1])); + }else if (token.equals("scaleH")){ + charSet.setHeight(Integer.parseInt(tokens[i + 1])); + }else if (token.equals("pages")){ + // number of texture pages + matPages = new Material[Integer.parseInt(tokens[i + 1])]; + font.setPages(matPages); + } + } + }else if (tokens[0].equals("page")){ + int index = -1; + Texture tex = null; + + for (int i = 1; i < tokens.length; i++){ + String token = tokens[i]; + if (token.equals("id")){ + index = Integer.parseInt(tokens[i + 1]); + }else if (token.equals("file")){ + String file = tokens[i + 1]; + if (file.startsWith("\"")){ + file = file.substring(1, file.length()-1); + } + TextureKey key = new TextureKey(folder + file, true); + key.setGenerateMips(false); + tex = assetManager.loadTexture(key); + tex.setMagFilter(Texture.MagFilter.Bilinear); + tex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); + } + } + // set page + if (index >= 0 && tex != null){ + Material mat = new Material(spriteMat); + mat.setTexture("ColorMap", tex); + mat.setBoolean("VertexColor", true); + mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + matPages[index] = mat; + } + }else if (tokens[0].equals("char")){ + // New BitmapCharacter + BitmapCharacter ch = null; + for (int i = 1; i < tokens.length; i++){ + String token = tokens[i]; + if (token.equals("id")){ + int index = Integer.parseInt(tokens[i + 1]); + ch = new BitmapCharacter(); + charSet.addCharacter(index, ch); + }else if (token.equals("x")){ + ch.setX(Integer.parseInt(tokens[i + 1])); + }else if (token.equals("y")){ + ch.setY(Integer.parseInt(tokens[i + 1])); + }else if (token.equals("width")){ + ch.setWidth(Integer.parseInt(tokens[i + 1])); + }else if (token.equals("height")){ + ch.setHeight(Integer.parseInt(tokens[i + 1])); + }else if (token.equals("xoffset")){ + ch.setXOffset(Integer.parseInt(tokens[i + 1])); + }else if (token.equals("yoffset")){ + ch.setYOffset(Integer.parseInt(tokens[i + 1])); + }else if (token.equals("xadvance")){ + ch.setXAdvance(Integer.parseInt(tokens[i + 1])); + } else if (token.equals("page")) { + ch.setPage(Integer.parseInt(tokens[i + 1])); + } + } + }else if (tokens[0].equals("kerning")){ + // Build kerning list + int index = 0; + int second = 0; + int amount = 0; + + for (int i = 1; i < tokens.length; i++){ + if (tokens[i].equals("first")){ + index = Integer.parseInt(tokens[i + 1]); + }else if (tokens[i].equals("second")){ + second = Integer.parseInt(tokens[i + 1]); + }else if (tokens[i].equals("amount")){ + amount = Integer.parseInt(tokens[i + 1]); + } + } + + BitmapCharacter ch = charSet.getCharacter(index); + ch.addKerning(second, amount); + } + } + return font; + } + + public Object load(AssetInfo info) throws IOException { + InputStream in = null; + try { + in = info.openStream(); + BitmapFont font = load(info.getManager(), info.getKey().getFolder(), in); + return font; + } finally { + if (in != null){ + in.close(); + } + } + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java new file mode 100644 index 000000000..aed88c10c --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material.plugins; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An utility class that allows to parse a define condition in a glsl language + * style. + * + * extractDefines is able to get a list of defines in an expression and update + * the formatter expression with upercased defines + * + * @author Nehon + */ +public class ConditionParser { + + private String formattedExpression = ""; + + public static void main(String argv[]) { + ConditionParser parser = new ConditionParser(); + List defines = parser.extractDefines("(LightMap && SeparateTexCoord) || !ColorMap"); + + for (String string : defines) { + System.err.println(string); + } + System.err.println(parser.formattedExpression); + + defines = parser.extractDefines("#if (defined(LightMap) && defined(SeparateTexCoord)) || !defined(ColorMap)"); + + for (String string : defines) { + System.err.println(string); + } + System.err.println(parser.formattedExpression); + + +// System.err.println(parser.getFormattedExpression()); +// +// parser.parse("ShaderNode.var.xyz"); +// parser.parse("var.xyz"); +// parser.parse("ShaderNode.var"); +// parser.parse("var"); + } + + /** + * parse a condition and returns the list of defines of this condition. + * additionally this methods updates the formattedExpression with uppercased + * defines names + * + * supported expression syntax example: + * "(LightMap && SeparateTexCoord) || !ColorMap" + * "#if (defined(LightMap) && defined(SeparateTexCoord)) || !defined(ColorMap)" + * "#ifdef LightMap" + * "#ifdef (LightMap && SeparateTexCoord) || !ColorMap" + * + * @param expression the expression to parse + * @return the list of defines + */ + public List extractDefines(String expression) { + List defines = new ArrayList(); + expression = expression.replaceAll("#ifdef", "").replaceAll("#if", "").replaceAll("defined", ""); + Pattern pattern = Pattern.compile("(\\w+)"); + formattedExpression = expression; + Matcher m = pattern.matcher(expression); + while (m.find()) { + String match = m.group(); + defines.add(match); + formattedExpression = formattedExpression.replaceAll(match, "defined(" + match.toUpperCase() + ")"); + } + return defines; + } + + /** + * + * @return the formatted expression previously updated by extractDefines + */ + public String getFormattedExpression() { + return formattedExpression; + } +} \ No newline at end of file diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java new file mode 100644 index 000000000..e12a0759f --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java @@ -0,0 +1,601 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material.plugins; + +import com.jme3.asset.*; +import com.jme3.material.*; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.material.RenderState.FaceCullMode; +import com.jme3.material.TechniqueDef.LightMode; +import com.jme3.material.TechniqueDef.ShadowMode; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.util.PlaceholderAssets; +import com.jme3.util.blockparser.BlockLanguageParser; +import com.jme3.util.blockparser.Statement; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class J3MLoader implements AssetLoader { + + private static final Logger logger = Logger.getLogger(J3MLoader.class.getName()); + // private ErrorLogger errors; + private ShaderNodeLoaderDelegate nodesLoaderDelegate; + boolean isUseNodes = false; + + private AssetManager assetManager; + private AssetKey key; + + private MaterialDef materialDef; + private Material material; + private TechniqueDef technique; + private RenderState renderState; + + private String vertLanguage; + private String fragLanguage; + + private String vertName; + private String fragName; + + private static final String whitespacePattern = "\\p{javaWhitespace}+"; + + public J3MLoader(){ + } + + + // : + private void readShaderStatement(String statement) throws IOException { + String[] split = statement.split(":"); + if (split.length != 2) { + throw new IOException("Shader statement syntax incorrect" + statement); + } + String[] typeAndLang = split[0].split(whitespacePattern); + if (typeAndLang.length != 2) { + throw new IOException("Shader statement syntax incorrect: " + statement); + } + + if (typeAndLang[0].equals("VertexShader")) { + vertName = split[1].trim(); + vertLanguage = typeAndLang[1]; + } else if (typeAndLang[0].equals("FragmentShader")) { + fragName = split[1].trim(); + fragLanguage = typeAndLang[1]; + } + } + + // LightMode + private void readLightMode(String statement) throws IOException{ + String[] split = statement.split(whitespacePattern); + if (split.length != 2){ + throw new IOException("LightMode statement syntax incorrect"); + } + LightMode lm = LightMode.valueOf(split[1]); + technique.setLightMode(lm); + } + + // ShadowMode + private void readShadowMode(String statement) throws IOException{ + String[] split = statement.split(whitespacePattern); + if (split.length != 2){ + throw new IOException("ShadowMode statement syntax incorrect"); + } + ShadowMode sm = ShadowMode.valueOf(split[1]); + technique.setShadowMode(sm); + } + + private Object readValue(VarType type, String value) throws IOException{ + if (type.isTextureType()){ +// String texturePath = readString("[\n;(//)(\\})]"); + String texturePath = value.trim(); + boolean flipY = false; + boolean repeat = false; + if (texturePath.startsWith("Flip Repeat ")){ + texturePath = texturePath.substring(12).trim(); + flipY = true; + repeat = true; + }else if (texturePath.startsWith("Flip ")){ + texturePath = texturePath.substring(5).trim(); + flipY = true; + }else if (texturePath.startsWith("Repeat ")){ + texturePath = texturePath.substring(7).trim(); + repeat = true; + } + + TextureKey texKey = new TextureKey(texturePath, flipY); + texKey.setAsCube(type == VarType.TextureCubeMap); + texKey.setGenerateMips(true); + + Texture tex; + try { + tex = assetManager.loadTexture(texKey); + } catch (AssetNotFoundException ex){ + logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key}); + tex = null; + } + if (tex != null){ + if (repeat){ + tex.setWrap(WrapMode.Repeat); + } + }else{ + tex = new Texture2D(PlaceholderAssets.getPlaceholderImage()); + if (repeat){ + tex.setWrap(WrapMode.Repeat); + } + tex.setKey(texKey); + } + return tex; + }else{ + String[] split = value.trim().split(whitespacePattern); + switch (type){ + case Float: + if (split.length != 1){ + throw new IOException("Float value parameter must have 1 entry: " + value); + } + return Float.parseFloat(split[0]); + case Vector2: + if (split.length != 2){ + throw new IOException("Vector2 value parameter must have 2 entries: " + value); + } + return new Vector2f(Float.parseFloat(split[0]), + Float.parseFloat(split[1])); + case Vector3: + if (split.length != 3){ + throw new IOException("Vector3 value parameter must have 3 entries: " + value); + } + return new Vector3f(Float.parseFloat(split[0]), + Float.parseFloat(split[1]), + Float.parseFloat(split[2])); + case Vector4: + if (split.length != 4){ + throw new IOException("Vector4 value parameter must have 4 entries: " + value); + } + return new ColorRGBA(Float.parseFloat(split[0]), + Float.parseFloat(split[1]), + Float.parseFloat(split[2]), + Float.parseFloat(split[3])); + case Int: + if (split.length != 1){ + throw new IOException("Int value parameter must have 1 entry: " + value); + } + return Integer.parseInt(split[0]); + case Boolean: + if (split.length != 1){ + throw new IOException("Boolean value parameter must have 1 entry: " + value); + } + return Boolean.parseBoolean(split[0]); + default: + throw new UnsupportedOperationException("Unknown type: "+type); + } + } + } + + // [ "(" ")" ] [ ":" ] + private void readParam(String statement) throws IOException{ + String name; + String defaultVal = null; + FixedFuncBinding ffBinding = null; + + String[] split = statement.split(":"); + + // Parse default val + if (split.length == 1){ + // Doesn't contain default value + }else{ + if (split.length != 2){ + throw new IOException("Parameter statement syntax incorrect"); + } + statement = split[0].trim(); + defaultVal = split[1].trim(); + } + + // Parse ffbinding + int startParen = statement.indexOf("("); + if (startParen != -1){ + // get content inside parentheses + int endParen = statement.indexOf(")", startParen); + String bindingStr = statement.substring(startParen+1, endParen).trim(); + try { + ffBinding = FixedFuncBinding.valueOf(bindingStr); + } catch (IllegalArgumentException ex){ + throw new IOException("FixedFuncBinding '" + + split[1] + "' does not exist!"); + } + statement = statement.substring(0, startParen); + } + + // Parse type + name + split = statement.split(whitespacePattern); + if (split.length != 2){ + throw new IOException("Parameter statement syntax incorrect"); + } + + VarType type; + if (split[0].equals("Color")){ + type = VarType.Vector4; + }else{ + type = VarType.valueOf(split[0]); + } + + name = split[1]; + + Object defaultValObj = null; + if (defaultVal != null){ + defaultValObj = readValue(type, defaultVal); + } + + materialDef.addMaterialParam(type, name, defaultValObj, ffBinding); + } + + private void readValueParam(String statement) throws IOException{ + // Use limit=1 incase filename contains colons + String[] split = statement.split(":", 2); + if (split.length != 2){ + throw new IOException("Value parameter statement syntax incorrect"); + } + String name = split[0].trim(); + + // parse value + MatParam p = material.getMaterialDef().getMaterialParam(name); + if (p == null){ + throw new IOException("The material parameter: "+name+" is undefined."); + } + + Object valueObj = readValue(p.getVarType(), split[1]); + if (p.getVarType().isTextureType()){ + material.setTextureParam(name, p.getVarType(), (Texture) valueObj); + }else{ + material.setParam(name, p.getVarType(), valueObj); + } + } + + private void readMaterialParams(List paramsList) throws IOException{ + for (Statement statement : paramsList){ + readParam(statement.getLine()); + } + } + + private void readExtendingMaterialParams(List paramsList) throws IOException{ + for (Statement statement : paramsList){ + readValueParam(statement.getLine()); + } + } + + private void readWorldParams(List worldParams) throws IOException{ + for (Statement statement : worldParams){ + technique.addWorldParam(statement.getLine()); + } + } + + private boolean parseBoolean(String word){ + return word != null && word.equals("On"); + } + + private void readRenderStateStatement(Statement statement) throws IOException{ + String[] split = statement.getLine().split(whitespacePattern); + if (split[0].equals("Wireframe")){ + renderState.setWireframe(parseBoolean(split[1])); + }else if (split[0].equals("FaceCull")){ + renderState.setFaceCullMode(FaceCullMode.valueOf(split[1])); + }else if (split[0].equals("DepthWrite")){ + renderState.setDepthWrite(parseBoolean(split[1])); + }else if (split[0].equals("DepthTest")){ + renderState.setDepthTest(parseBoolean(split[1])); + }else if (split[0].equals("Blend")){ + renderState.setBlendMode(BlendMode.valueOf(split[1])); + }else if (split[0].equals("AlphaTestFalloff")){ + renderState.setAlphaTest(true); + renderState.setAlphaFallOff(Float.parseFloat(split[1])); + }else if (split[0].equals("PolyOffset")){ + float factor = Float.parseFloat(split[1]); + float units = Float.parseFloat(split[2]); + renderState.setPolyOffset(factor, units); + }else if (split[0].equals("ColorWrite")){ + renderState.setColorWrite(parseBoolean(split[1])); + }else if (split[0].equals("PointSprite")){ + renderState.setPointSprite(parseBoolean(split[1])); + }else if (split[0].equals("DepthFunc")){ + renderState.setDepthFunc(RenderState.TestFunction.valueOf(split[1])); + }else if (split[0].equals("AlphaFunc")){ + renderState.setAlphaFunc(RenderState.TestFunction.valueOf(split[1])); + } else { + throw new MatParseException(null, split[0], statement); + } + } + + private void readAdditionalRenderState(List renderStates) throws IOException{ + renderState = material.getAdditionalRenderState(); + for (Statement statement : renderStates){ + readRenderStateStatement(statement); + } + renderState = null; + } + + private void readRenderState(List renderStates) throws IOException{ + renderState = new RenderState(); + for (Statement statement : renderStates){ + readRenderStateStatement(statement); + } + technique.setRenderState(renderState); + renderState = null; + } + + private void readForcedRenderState(List renderStates) throws IOException{ + renderState = new RenderState(); + for (Statement statement : renderStates){ + readRenderStateStatement(statement); + } + technique.setForcedRenderState(renderState); + renderState = null; + } + + // [ ":" ] + private void readDefine(String statement) throws IOException{ + String[] split = statement.split(":"); + if (split.length == 1){ + // add preset define + technique.addShaderPresetDefine(split[0].trim(), VarType.Boolean, true); + }else if (split.length == 2){ + technique.addShaderParamDefine(split[1].trim(), split[0].trim()); + }else{ + throw new IOException("Define syntax incorrect"); + } + } + + private void readDefines(List defineList) throws IOException{ + for (Statement statement : defineList){ + readDefine(statement.getLine()); + } + + } + + private void readTechniqueStatement(Statement statement) throws IOException{ + String[] split = statement.getLine().split("[ \\{]"); + if (split[0].equals("VertexShader") || + split[0].equals("FragmentShader")){ + readShaderStatement(statement.getLine()); + }else if (split[0].equals("LightMode")){ + readLightMode(statement.getLine()); + }else if (split[0].equals("ShadowMode")){ + readShadowMode(statement.getLine()); + }else if (split[0].equals("WorldParameters")){ + readWorldParams(statement.getContents()); + }else if (split[0].equals("RenderState")){ + readRenderState(statement.getContents()); + }else if (split[0].equals("ForcedRenderState")){ + readForcedRenderState(statement.getContents()); + }else if (split[0].equals("Defines")){ + readDefines(statement.getContents()); + } else if (split[0].equals("ShaderNodesDefinitions")) { + initNodesLoader(); + if (isUseNodes) { + nodesLoaderDelegate.readNodesDefinitions(statement.getContents()); + } + } else if (split[0].equals("VertexShaderNodes")) { + initNodesLoader(); + if (isUseNodes) { + nodesLoaderDelegate.readVertexShaderNodes(statement.getContents()); + } + } else if (split[0].equals("FragmentShaderNodes")) { + initNodesLoader(); + if (isUseNodes) { + nodesLoaderDelegate.readFragmentShaderNodes(statement.getContents()); + } + } else { + throw new MatParseException(null, split[0], statement); + } + } + + private void readTransparentStatement(String statement) throws IOException{ + String[] split = statement.split(whitespacePattern); + if (split.length != 2){ + throw new IOException("Transparent statement syntax incorrect"); + } + material.setTransparent(parseBoolean(split[1])); + } + + private void readTechnique(Statement techStat) throws IOException{ + isUseNodes = false; + String[] split = techStat.getLine().split(whitespacePattern); + if (split.length == 1) { + technique = new TechniqueDef(null); + } else if (split.length == 2) { + String techName = split[1]; + if (techName.equals("FixedFunc")) { + throw new UnsupportedOperationException( + "In material: " + key + "\nThe 'FixedFunc' technique name no longer has any special meanining.\n" + + "To support fixed pipeline mode, remove that technique's name entirely."); + } + technique = new TechniqueDef(techName); + } else { + throw new IOException("Technique statement syntax incorrect"); + } + + for (Statement statement : techStat.getContents()){ + readTechniqueStatement(statement); + } + + if(isUseNodes){ + nodesLoaderDelegate.computeConditions(); + //used for caching later, the shader here is not a file. + technique.setShaderFile(technique.hashCode() + "", technique.hashCode() + "", "GLSL100", "GLSL100"); + } + + if (vertName != null && fragName != null){ + technique.setShaderFile(vertName, fragName, vertLanguage, fragLanguage); + } + + materialDef.addTechniqueDef(technique); + technique = null; + vertName = null; + fragName = null; + vertLanguage = null; + fragLanguage = null; + } + + private void loadFromRoot(List roots) throws IOException{ + if (roots.size() == 2){ + Statement exception = roots.get(0); + String line = exception.getLine(); + if (line.startsWith("Exception")){ + throw new AssetLoadException(line.substring("Exception ".length())); + }else{ + throw new IOException("In multiroot material, expected first statement to be 'Exception'"); + } + }else if (roots.size() != 1){ + throw new IOException("Too many roots in J3M/J3MD file"); + } + + boolean extending = false; + Statement materialStat = roots.get(0); + String materialName = materialStat.getLine(); + if (materialName.startsWith("MaterialDef")){ + materialName = materialName.substring("MaterialDef ".length()).trim(); + extending = false; + }else if (materialName.startsWith("Material")){ + materialName = materialName.substring("Material ".length()).trim(); + extending = true; + }else{ + throw new IOException("Specified file is not a Material file"); + } + + String[] split = materialName.split(":", 2); + + if (materialName.equals("")){ + throw new MatParseException("Material name cannot be empty", materialStat); + } + + if (split.length == 2){ + if (!extending){ + throw new MatParseException("Must use 'Material' when extending.", materialStat); + } + + String extendedMat = split[1].trim(); + + MaterialDef def = (MaterialDef) assetManager.loadAsset(new AssetKey(extendedMat)); + if (def == null) { + throw new MatParseException("Extended material " + extendedMat + " cannot be found.", materialStat); + } + + material = new Material(def); + material.setKey(key); +// material.setAssetName(fileName); + }else if (split.length == 1){ + if (extending){ + throw new MatParseException("Expected ':', got '{'", materialStat); + } + materialDef = new MaterialDef(assetManager, materialName); + // NOTE: pass file name for defs so they can be loaded later + materialDef.setAssetName(key.getName()); + }else{ + throw new MatParseException("Cannot use colon in material name/path", materialStat); + } + + for (Statement statement : materialStat.getContents()){ + split = statement.getLine().split("[ \\{]"); + String statType = split[0]; + if (extending){ + if (statType.equals("MaterialParameters")){ + readExtendingMaterialParams(statement.getContents()); + }else if (statType.equals("AdditionalRenderState")){ + readAdditionalRenderState(statement.getContents()); + }else if (statType.equals("Transparent")){ + readTransparentStatement(statement.getLine()); + } + }else{ + if (statType.equals("Technique")){ + readTechnique(statement); + }else if (statType.equals("MaterialParameters")){ + readMaterialParams(statement.getContents()); + }else{ + throw new MatParseException("Expected material statement, got '"+statType+"'", statement); + } + } + } + } + + public Object load(AssetInfo info) throws IOException { + this.assetManager = info.getManager(); + + InputStream in = info.openStream(); + try { + key = info.getKey(); + loadFromRoot(BlockLanguageParser.parse(in)); + } finally { + if (in != null){ + in.close(); + } + } + + if (material != null){ + if (!(info.getKey() instanceof MaterialKey)){ + throw new IOException("Material instances must be loaded via MaterialKey"); + } + // material implementation + return material; + }else{ + // material definition + return materialDef; + } + } + + public MaterialDef loadMaterialDef(List roots, AssetManager manager, AssetKey key) throws IOException { + this.key = key; + this.assetManager = manager; + loadFromRoot(roots); + return materialDef; + } + + protected void initNodesLoader() { + if (!isUseNodes) { + isUseNodes = fragName == null && vertName == null; + if (isUseNodes) { + if(nodesLoaderDelegate == null){ + nodesLoaderDelegate = new ShaderNodeLoaderDelegate(); + }else{ + nodesLoaderDelegate.clear(); + } + nodesLoaderDelegate.setTechniqueDef(technique); + nodesLoaderDelegate.setMaterialDef(materialDef); + nodesLoaderDelegate.setAssetManager(assetManager); + } + } + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/MatParseException.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/MatParseException.java new file mode 100644 index 000000000..ac810ae36 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/MatParseException.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material.plugins; + +import com.jme3.util.blockparser.Statement; +import java.io.IOException; + +/** + * Custom Exception to report a j3md Material definition file parsing error. + * This exception reports the line number where the error occured. + * + * @author Nehon + */ +public class MatParseException extends IOException { + + /** + * creates a MatParseException + * + * @param expected the expected value + * @param got the actual value + * @param statement the read statement + */ + public MatParseException(String expected, String got, Statement statement) { + super("Error On line " + statement.getLineNumber() + " : " + statement.getLine() + "\n->Expected " + (expected == null ? "a statement" : expected) + ", got '" + got + "'!"); + + } + + /** + * creates a MatParseException + * + * @param text the error message + * @param statement the statement where the error occur + */ + public MatParseException(String text, Statement statement) { + super("Error On line " + statement.getLineNumber() + " : " + statement.getLine() + "\n->" + text); + } + + /** + * creates a MatParseException + * + * @param expected the expected value + * @param got the actual value + * @param statement the read statement + * @param cause the embed exception that occured + */ + public MatParseException(String expected, String got, Statement statement, Throwable cause) { + super("Error On line " + statement.getLineNumber() + " : " + statement.getLine() + "\n->Expected " + (expected == null ? "a statement" : expected) + ", got '" + got + "'!", cause); + + } + + /** + * creates a MatParseException + * + * @param text the error message + * @param statement the statement where the error occur + * @param cause the embed exception that occured + */ + public MatParseException(String text, Statement statement, Throwable cause) { + super("Error On line " + statement.getLineNumber() + " : " + statement.getLine() + "\n->" + text, cause); + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeDefinitionLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeDefinitionLoader.java new file mode 100644 index 000000000..a14937f82 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeDefinitionLoader.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLoadException; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.ShaderNodeDefinitionKey; +import com.jme3.util.blockparser.BlockLanguageParser; +import com.jme3.util.blockparser.Statement; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +/** + * ShaderNodeDefnition file loader (.j3sn) + * + * a j3sn file is a block style file like j3md or j3m. It must contain one + * ShaderNodeDefinition{} block that contains several ShaderNodeDefinition{} + * blocks + * + * @author Nehon + */ +public class ShaderNodeDefinitionLoader implements AssetLoader { + + private ShaderNodeLoaderDelegate loaderDelegate; + + @Override + public Object load(AssetInfo assetInfo) throws IOException { + AssetKey k = assetInfo.getKey(); + if (!(k instanceof ShaderNodeDefinitionKey)) { + throw new IOException("ShaderNodeDefinition file must be loaded via ShaderNodeDefinitionKey"); + } + ShaderNodeDefinitionKey key = (ShaderNodeDefinitionKey) k; + loaderDelegate = new ShaderNodeLoaderDelegate(); + + InputStream in = assetInfo.openStream(); + List roots = BlockLanguageParser.parse(in); + + if (roots.size() == 2) { + Statement exception = roots.get(0); + String line = exception.getLine(); + if (line.startsWith("Exception")) { + throw new AssetLoadException(line.substring("Exception ".length())); + } else { + throw new MatParseException("In multiroot shader node definition, expected first statement to be 'Exception'", exception); + } + } else if (roots.size() != 1) { + throw new MatParseException("Too many roots in J3SN file", roots.get(0)); + } + + return loaderDelegate.readNodesDefinitions(roots.get(0).getContents(), key); + + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java new file mode 100644 index 000000000..dd1bc6dcd --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java @@ -0,0 +1,1098 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material.plugins; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.asset.ShaderNodeDefinitionKey; +import com.jme3.material.MatParam; +import com.jme3.material.MaterialDef; +import com.jme3.material.ShaderGenerationInfo; +import com.jme3.material.TechniqueDef; +import com.jme3.shader.Shader; +import com.jme3.shader.ShaderNode; +import com.jme3.shader.ShaderNodeDefinition; +import com.jme3.shader.ShaderNodeVariable; +import com.jme3.shader.ShaderUtils; +import com.jme3.shader.UniformBinding; +import com.jme3.shader.VariableMapping; +import com.jme3.util.blockparser.Statement; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class is here to be able to load shaderNodeDefinition from both the + * J3MLoader and ShaderNodeDefinitionLoader. + * + * Also it allows to load the ShaderNodes from a j3md file and build the + * ShaderNodes list of each technique and the ShaderGenerationInfo needed to + * generate the sahders + * + * @author Nehon + */ +public class ShaderNodeLoaderDelegate { + + protected Map nodeDefinitions; + protected Map nodes; + protected ShaderNodeDefinition shaderNodeDefinition; + protected ShaderNode shaderNode; + protected TechniqueDef techniqueDef; + protected Map attributes = new HashMap(); + protected Map vertexDeclaredUniforms = new HashMap(); + protected Map fragmentDeclaredUniforms = new HashMap(); + protected Map varyings = new HashMap(); + protected MaterialDef materialDef; + protected String shaderLanguage; + protected String shaderName; + protected String varNames = ""; + protected AssetManager assetManager; + protected ConditionParser conditionParser = new ConditionParser(); + protected List nulledConditions = new ArrayList(); + + protected class DeclaredVariable { + + ShaderNodeVariable var; + List nodes = new ArrayList(); + + public DeclaredVariable(ShaderNodeVariable var) { + this.var = var; + } + + public void makeCondition() { + var.setCondition(null); + + for (ShaderNode node : nodes) { + String condition = null; + for (VariableMapping mapping : node.getInputMapping()) { + if (mapping.getRightVariable().equals(var)) { + if (mapping.getCondition() == null) { + condition = null; + break; + } + if (condition == null) { + condition = "(" + mapping.getCondition() + ")"; + } else { + if (!condition.contains(mapping.getCondition())) { + condition = condition + " || (" + mapping.getCondition() + ")"; + } + } + } + } + if (node.getCondition() == null && condition == null) { + var.setCondition(null); + return; + } + if (node.getCondition() != null) { + if (condition == null) { + condition = node.getCondition(); + } else { + if (!condition.contains(node.getCondition())) { + condition = "(" + node.getCondition() + ") && (" + condition + ")"; + } + } + } + if (var.getCondition() == null) { + var.setCondition(condition); + } else { + if (!var.getCondition().contains(condition)) { + var.setCondition("(" + var.getCondition() + ") || (" + condition + ")"); + } + } + + } + } + + public final void addNode(ShaderNode c) { + if (!nodes.contains(c)) { + nodes.add(c); + } + } + } + + protected void computeConditions() { + + updateConditions(vertexDeclaredUniforms); + updateConditions(fragmentDeclaredUniforms); + updateConditions(varyings); + + for (DeclaredVariable v : varyings.values()) { + for (ShaderNode sn : techniqueDef.getShaderNodes()) { + if (sn.getDefinition().getType() == Shader.ShaderType.Vertex) { + for (VariableMapping mapping : sn.getInputMapping()) { + if (mapping.getLeftVariable().equals(v.var)) { + if (mapping.getCondition() == null || v.var.getCondition() == null) { + mapping.setCondition(v.var.getCondition()); + } else { + mapping.setCondition("(" + mapping.getCondition() + ") || (" + v.var.getCondition() + ")"); + } + } + } + } + + } + } + + updateConditions(attributes); +// updateConditions(fragmentGlobals); +// vertexGlobal.makeCondition(); + } + + /** + * Read the ShaderNodesDefinitions block and returns a list of + * ShaderNodesDefinition This method is used by the j3sn loader + * + * note that the order of the definitions in the list is not guaranteed. + * + * @param statements the list statements to parse + * @param key the ShaderNodeDefinitionKey + * @return a list of ShaderNodesDefinition + * @throws IOException + */ + public List readNodesDefinitions(List statements, ShaderNodeDefinitionKey key) throws IOException { + + for (Statement statement : statements) { + String[] split = statement.getLine().split("[ \\{]"); + if (statement.getLine().startsWith("ShaderNodeDefinition")) { + String name = statement.getLine().substring("ShaderNodeDefinition".length()).trim(); + + + if (!getNodeDefinitions().containsKey(name)) { + shaderNodeDefinition = new ShaderNodeDefinition(); + getNodeDefinitions().put(name, shaderNodeDefinition); + shaderNodeDefinition.setName(name); + readShaderNodeDefinition(statement.getContents(), key); + + } + } else { + throw new MatParseException("ShaderNodeDefinition", split[0], statement); + } + } + + return new ArrayList(getNodeDefinitions().values()); + } + + /** + * Read the ShaderNodesDefinitions block and internally stores a map of + * ShaderNodesDefinition This method is used by the j3m loader. + * + * When loaded in a material, the definitions are not stored as a list, but + * they are stores in Shadernodes based onthis definition. + * + * The map is here to map the defintion to the nodes, and ovoid reloading + * already loaded definitions + * + * @param statements the list of statements to parse + * @throws IOException + */ + public void readNodesDefinitions(List statements) throws IOException { + readNodesDefinitions(statements, new ShaderNodeDefinitionKey()); + } + + /** + * effectiveliy reads the ShaderNodesDefinitions block + * + * @param statements the list of statements to parse + * @param key the ShaderNodeDefinitionKey + * @throws IOException + */ + protected void readShaderNodeDefinition(List statements, ShaderNodeDefinitionKey key) throws IOException { + boolean isLoadDoc = key instanceof ShaderNodeDefinitionKey && ((ShaderNodeDefinitionKey) key).isLoadDocumentation(); + for (Statement statement : statements) { + String[] split = statement.getLine().split("[ \\{]"); + String line = statement.getLine(); + + if (line.startsWith("Type")) { + String type = line.substring(line.lastIndexOf(':') + 1).trim(); + shaderNodeDefinition.setType(Shader.ShaderType.valueOf(type)); + } else if (line.startsWith("Shader ")) { + readShaderStatement(statement); + shaderNodeDefinition.getShadersLanguage().add(shaderLanguage); + shaderNodeDefinition.getShadersPath().add(shaderName); + } else if (line.startsWith("Documentation")) { + if (isLoadDoc) { + String doc = ""; + for (Statement statement1 : statement.getContents()) { + doc += "\n" + statement1.getLine(); + } + shaderNodeDefinition.setDocumentation(doc); + } + } else if (line.startsWith("Input")) { + varNames = ""; + for (Statement statement1 : statement.getContents()) { + shaderNodeDefinition.getInputs().add(readVariable(statement1)); + } + } else if (line.startsWith("Output")) { + varNames = ""; + for (Statement statement1 : statement.getContents()) { + if(statement1.getLine().trim().equals("None")){ + shaderNodeDefinition.setNoOutput(true); + }else{ + shaderNodeDefinition.getOutputs().add(readVariable(statement1)); + } + } + } else { + throw new MatParseException("one of Type, Shader, Documentation, Input, Output", split[0], statement); + } + } + } + + /** + * reads a variable declaration statement + * + * @param statement the statement to parse + * @return a ShaderNodeVariable axtracted from the statement + * @throws IOException + */ + protected ShaderNodeVariable readVariable(Statement statement) throws IOException { + String line = statement.getLine().trim().replaceAll("\\s*\\[", "["); + String[] splitVar = line.split("\\s"); + String varName = splitVar[1]; + String varType = splitVar[0]; + String multiplicity = null; + + if (varName.contains("[")) { + //we have an array + String[] arr = splitVar[1].split("\\["); + varName = arr[0].trim(); + multiplicity = arr[1].replaceAll("\\]", "").trim(); + } + if (varNames.contains(varName + ";")) { + throw new MatParseException("Duplicate variable name " + varName, statement); + } + varNames += varName + ";"; + return new ShaderNodeVariable(varType, "", varName, multiplicity); + } + + /** + * reads the VertexShaderNodes{} block + * + * @param statements the list of statements to parse + * @throws IOException + */ + public void readVertexShaderNodes(List statements) throws IOException { + attributes.clear(); + readNodes(statements); + } + + /** + * reads a list of ShaderNode{} blocks + * + * @param statements the list of statements to parse + * @throws IOException + */ + protected void readShaderNode(List statements) throws IOException { + for (Statement statement : statements) { + String line = statement.getLine(); + String[] split = statement.getLine().split("[ \\{]"); + if (line.startsWith("Definition")) { + ShaderNodeDefinition def = findDefinition(statement); + shaderNode.setDefinition(def); + if(def.isNoOutput()){ + techniqueDef.getShaderGenerationInfo().getUnusedNodes().remove(shaderNode.getName()); + } + } else if (line.startsWith("Condition")) { + String condition = line.substring(line.lastIndexOf(":") + 1).trim(); + extractCondition(condition, statement); + shaderNode.setCondition(conditionParser.getFormattedExpression()); + } else if (line.startsWith("InputMapping")) { + for (Statement statement1 : statement.getContents()) { + VariableMapping mapping = readInputMapping(statement1); + techniqueDef.getShaderGenerationInfo().getUnusedNodes().remove(mapping.getRightVariable().getNameSpace()); + shaderNode.getInputMapping().add(mapping); + } + } else if (line.startsWith("OutputMapping")) { + for (Statement statement1 : statement.getContents()) { + VariableMapping mapping = readOutputMapping(statement1); + techniqueDef.getShaderGenerationInfo().getUnusedNodes().remove(shaderNode.getName()); + shaderNode.getOutputMapping().add(mapping); + } + } else { + throw new MatParseException("ShaderNodeDefinition", split[0], statement); + } + } + + } + + /** + * reads a mapping statement. Sets the nameSpace, name and swizzling of the + * left variable. Sets the name, nameSpace and swizzling of the right + * variable types will be determined later. + * + * Format : .[.] = + * .[.][:Condition] + * + * @param statement the statement to read + * @return the read mapping + */ + protected VariableMapping parseMapping(Statement statement, boolean[] hasNameSpace) throws IOException { + VariableMapping mapping = new VariableMapping(); + String[] cond = statement.getLine().split(":"); + + String[] vars = cond[0].split("="); + checkMappingFormat(vars, statement); + ShaderNodeVariable[] variables = new ShaderNodeVariable[2]; + String[] swizzle = new String[2]; + for (int i = 0; i < vars.length; i++) { + String[] expression = vars[i].trim().split("\\."); + if (hasNameSpace[i]) { + if (expression.length <= 3) { + variables[i] = new ShaderNodeVariable("", expression[0].trim(), expression[1].trim()); + } + if (expression.length == 3) { + swizzle[i] = expression[2].trim(); + } + } else { + if (expression.length <= 2) { + variables[i] = new ShaderNodeVariable("", expression[0].trim()); + } + if (expression.length == 2) { + swizzle[i] = expression[1].trim(); + } + } + + } + + mapping.setLeftVariable(variables[0]); + mapping.setLeftSwizzling(swizzle[0] != null ? swizzle[0] : ""); + mapping.setRightVariable(variables[1]); + mapping.setRightSwizzling(swizzle[1] != null ? swizzle[1] : ""); + + if (cond.length > 1) { + extractCondition(cond[1], statement); + mapping.setCondition(conditionParser.getFormattedExpression()); + } + + return mapping; + } + + /** + * reads the FragmentShaderNodes{} block + * + * @param statements the list of statements to parse + * @throws IOException + */ + public void readFragmentShaderNodes(List statements) throws IOException { + readNodes(statements); + } + + /** + * Reads a Shader statement of this form : + * + * @param statement + * @throws IOException + */ + protected void readShaderStatement(Statement statement) throws IOException { + String[] split = statement.getLine().split(":"); + if (split.length != 2) { + throw new MatParseException("Shader statement syntax incorrect", statement); + } + String[] typeAndLang = split[0].split("\\p{javaWhitespace}+"); + if (typeAndLang.length != 2) { + throw new MatParseException("Shader statement syntax incorrect", statement); + } + shaderName = split[1].trim(); + shaderLanguage = typeAndLang[1]; + } + + /** + * Sets the technique definition currently being loaded + * + * @param techniqueDef the technique def + */ + public void setTechniqueDef(TechniqueDef techniqueDef) { + this.techniqueDef = techniqueDef; + } + + /** + * sets the material def currently being loaded + * + * @param materialDef + */ + public void setMaterialDef(MaterialDef materialDef) { + this.materialDef = materialDef; + } + + /** + * searcha variable in the given list and updates its type and namespace + * + * @param var the variable to update + * @param list the variables list + * @return true if the variable has been found and updated + */ + protected boolean updateVariableFromList(ShaderNodeVariable var, List list) { + for (ShaderNodeVariable shaderNodeVariable : list) { + if (shaderNodeVariable.getName().equals(var.getName())) { + var.setType(shaderNodeVariable.getType()); + var.setMultiplicity(shaderNodeVariable.getMultiplicity()); + var.setNameSpace(shaderNode.getName()); + return true; + } + } + return false; + } + + /** + * updates the type of the right variable of a mapping from the type of the + * left variable + * + * @param mapping the mapping to consider + */ + protected void updateRightTypeFromLeftType(VariableMapping mapping) { + String type = mapping.getLeftVariable().getType(); + int card = ShaderUtils.getCardinality(type, mapping.getRightSwizzling()); + if (card > 0) { + if (card == 1) { + type = "float"; + } else { + type = "vec" + card; + } + } + mapping.getRightVariable().setType(type); + } + + /** + * check if once a mapping expression is split by "=" the resulting array + * have 2 elements + * + * @param vars the array + * @param statement the statement + * @throws IOException + */ + protected void checkMappingFormat(String[] vars, Statement statement) throws IOException { + if (vars.length != 2) { + throw new MatParseException("Not a valid expression should be '[.] = .[.][:Condition]'", statement); + } + } + + /** + * finds a MatParam in the materialDef from the given name + * + * @param varName the matparam name + * @return the MatParam + */ + protected MatParam findMatParam(String varName) { + for (MatParam matParam : materialDef.getMaterialParams()) { + if (varName.equals(matParam.getName())) { + return matParam; + } + } + return null; + } + + /** + * finds an UniformBinding representing a WorldParam from the techniqueDef + * + * @param varName the name of the WorldParam + * @return the corresponding UniformBinding to the WorldParam + */ + protected UniformBinding findWorldParam(String varName) { + for (UniformBinding worldParam : techniqueDef.getWorldBindings()) { + if (varName.equals(worldParam.toString())) { + return worldParam; + } + } + return null; + } + + /** + * updates the right variable of the given mapping from a UniformBinding (a + * WorldParam) it checks if the unifrom hasn't already been loaded, add it + * to the maps if not. + * + * @param param the WorldParam UniformBinding + * @param mapping the mapping + * @param map the map of uniforms to search into + * @return true if the param was added to the map + */ + protected boolean updateRightFromUniforms(UniformBinding param, VariableMapping mapping, Map map) { + ShaderNodeVariable right = mapping.getRightVariable(); + String name = "g_" + param.toString(); + DeclaredVariable dv = map.get(name); + if (dv == null) { + right.setType(param.getGlslType()); + right.setName(name); + dv = new DeclaredVariable(right); + map.put(right.getName(), dv); + dv.addNode(shaderNode); + mapping.setRightVariable(right); + return true; + } + dv.addNode(shaderNode); + mapping.setRightVariable(dv.var); + return false; + } + + /** + * updates the right variable of the given mapping from a MatParam (a + * WorldParam) it checks if the unifrom hasn't already been loaded, add it + * to the maps if not. + * + * @param param the MatParam + * @param mapping the mapping + * @param map the map of uniforms to search into + * @return true if the param was added to the map + */ + public boolean updateRightFromUniforms(MatParam param, VariableMapping mapping, Map map, Statement statement) throws MatParseException { + ShaderNodeVariable right = mapping.getRightVariable(); + DeclaredVariable dv = map.get(param.getPrefixedName()); + if (dv == null) { + right.setType(param.getVarType().getGlslType()); + right.setName(param.getPrefixedName()); + if(mapping.getLeftVariable().getMultiplicity() != null){ + if(!param.getVarType().name().endsWith("Array")){ + throw new MatParseException(param.getName() + " is not of Array type", statement); + } + String multiplicity = mapping.getLeftVariable().getMultiplicity(); + try { + Integer.parseInt(multiplicity); + } catch (NumberFormatException nfe) { + //multiplicity is not an int attempting to find for a material parameter. + MatParam mp = findMatParam(multiplicity); + if (mp != null) { + addDefine(multiplicity); + multiplicity = multiplicity.toUpperCase(); + } else { + throw new MatParseException("Wrong multiplicity for variable" + mapping.getLeftVariable().getName() + ". " + multiplicity + " should be an int or a declared material parameter.", statement); + } + } + right.setMultiplicity(multiplicity); + } + dv = new DeclaredVariable(right); + map.put(right.getName(), dv); + dv.addNode(shaderNode); + mapping.setRightVariable(right); + return true; + } + dv.addNode(shaderNode); + mapping.setRightVariable(dv.var); + return false; + } + + /** + * updates a variable from the Attribute list + * + * @param right the variable + * @param mapping the mapping + */ + public void updateVarFromAttributes(ShaderNodeVariable right, VariableMapping mapping) { + DeclaredVariable dv = attributes.get(right.getName()); + if (dv == null) { + dv = new DeclaredVariable(right); + attributes.put(right.getName(), dv); + updateRightTypeFromLeftType(mapping); + } else { + mapping.setRightVariable(dv.var); + } + dv.addNode(shaderNode); + } + + /** + * Adds a define to the techniquedef + * + * @param paramName + */ + public void addDefine(String paramName) { + if (techniqueDef.getShaderParamDefine(paramName) == null) { + techniqueDef.addShaderParamDefine(paramName, paramName.toUpperCase()); + } + } + + /** + * find a variable with the given name from the list of variable + * + * @param vars a list of shaderNodeVariables + * @param rightVarName the variable name to search for + * @return the found variable or null is not found + */ + public ShaderNodeVariable findNodeOutput(List vars, String rightVarName) { + ShaderNodeVariable var = null; + for (ShaderNodeVariable variable : vars) { + if (variable.getName().equals(rightVarName)) { + var = variable; + } + } + return var; + } + + /** + * extract and check a condition expression + * + * @param cond the condition expression + * @param statement the statement being read + * @throws IOException + */ + public void extractCondition(String cond, Statement statement) throws IOException { + List defines = conditionParser.extractDefines(cond); + for (String string : defines) { + MatParam param = findMatParam(string); + if (param != null) { + addDefine(param.getName()); + } else { + throw new MatParseException("Invalid condition, condition must match a Material Parameter named " + cond, statement); + } + } + } + + /** + * reads an input mapping + * + * @param statement1 the statement being read + * @return the mapping + * @throws IOException + */ + public VariableMapping readInputMapping(Statement statement1) throws IOException { + VariableMapping mapping = null; + try { + mapping = parseMapping(statement1, new boolean[]{false, true}); + } catch (Exception e) { + throw new MatParseException("Unexpected mapping format", statement1, e); + } + ShaderNodeVariable left = mapping.getLeftVariable(); + ShaderNodeVariable right = mapping.getRightVariable(); + if (!updateVariableFromList(left, shaderNode.getDefinition().getInputs())) { + throw new MatParseException(left.getName() + " is not an input variable of " + shaderNode.getDefinition().getName(), statement1); + } + + if (left.getType().startsWith("sampler") && !right.getNameSpace().equals("MatParam")) { + throw new MatParseException("Samplers can only be assigned to MatParams", statement1); + } + + if (right.getNameSpace().equals("Global")) { + right.setType("vec4");//Globals are all vec4 for now (maybe forever...) + // updateCondition(right, mapping); + storeGlobal(right, statement1); + + } else if (right.getNameSpace().equals("Attr")) { + if (shaderNode.getDefinition().getType() == Shader.ShaderType.Fragment) { + throw new MatParseException("Cannot have an attribute as input in a fragment shader" + right.getName(), statement1); + } + updateVarFromAttributes(mapping.getRightVariable(), mapping); + // updateCondition(mapping.getRightVariable(), mapping); + storeAttribute(mapping.getRightVariable()); + } else if (right.getNameSpace().equals("MatParam")) { + MatParam param = findMatParam(right.getName()); + if (param == null) { + throw new MatParseException("Could not find a Material Parameter named " + right.getName(), statement1); + } + if (shaderNode.getDefinition().getType() == Shader.ShaderType.Vertex) { + if (updateRightFromUniforms(param, mapping, vertexDeclaredUniforms, statement1)) { + storeVertexUniform(mapping.getRightVariable()); + } + } else { + if (updateRightFromUniforms(param, mapping, fragmentDeclaredUniforms, statement1)) { + if (mapping.getRightVariable().getType().contains("|")) { + String type = fixSamplerType(left.getType(), mapping.getRightVariable().getType()); + if (type != null) { + mapping.getRightVariable().setType(type); + } else { + throw new MatParseException(param.getVarType().toString() + " can only be matched to one of " + param.getVarType().getGlslType().replaceAll("\\|", ",") + " found " + left.getType(), statement1); + } + } + storeFragmentUniform(mapping.getRightVariable()); + } + } + + } else if (right.getNameSpace().equals("WorldParam")) { + UniformBinding worldParam = findWorldParam(right.getName()); + if (worldParam == null) { + throw new MatParseException("Could not find a World Parameter named " + right.getName(), statement1); + } + if (shaderNode.getDefinition().getType() == Shader.ShaderType.Vertex) { + if (updateRightFromUniforms(worldParam, mapping, vertexDeclaredUniforms)) { + storeVertexUniform(mapping.getRightVariable()); + } + } else { + if (updateRightFromUniforms(worldParam, mapping, fragmentDeclaredUniforms)) { + storeFragmentUniform(mapping.getRightVariable()); + } + } + + } else { + ShaderNode node = nodes.get(right.getNameSpace()); + if (node == null) { + throw new MatParseException("Undeclared node" + right.getNameSpace() + ". Make sure this node is declared before the current node", statement1); + } + ShaderNodeVariable var = findNodeOutput(node.getDefinition().getOutputs(), right.getName()); + if (var == null) { + throw new MatParseException("Cannot find output variable" + right.getName() + " form ShaderNode " + node.getName(), statement1); + } + right.setNameSpace(node.getName()); + right.setType(var.getType()); + mapping.setRightVariable(right); + storeVaryings(node, mapping.getRightVariable()); + + } + + checkTypes(mapping, statement1); + + return mapping; + } + + /** + * reads an output mapping + * + * @param statement1 the staement being read + * @return the mapping + * @throws IOException + */ + public VariableMapping readOutputMapping(Statement statement1) throws IOException { + VariableMapping mapping = null; + try { + mapping = parseMapping(statement1, new boolean[]{true, false}); + } catch (Exception e) { + throw new MatParseException("Unexpected mapping format", statement1, e); + } + ShaderNodeVariable left = mapping.getLeftVariable(); + ShaderNodeVariable right = mapping.getRightVariable(); + + + if (left.getType().startsWith("sampler") || right.getType().startsWith("sampler")) { + throw new MatParseException("Samplers can only be inputs", statement1); + } + + if (left.getNameSpace().equals("Global")) { + left.setType("vec4");//Globals are all vec4 for now (maybe forever...) + storeGlobal(left, statement1); + } else { + throw new MatParseException("Only Global nameSpace is allowed for outputMapping, got" + left.getNameSpace(), statement1); + } + + if (!updateVariableFromList(right, shaderNode.getDefinition().getOutputs())) { + throw new MatParseException(right.getName() + " is not an output variable of " + shaderNode.getDefinition().getName(), statement1); + } + + checkTypes(mapping, statement1); + + return mapping; + } + + /** + * Reads alist of ShaderNodes + * + * @param statements the list of statements to read + * @throws IOException + */ + public void readNodes(List statements) throws IOException { + if (techniqueDef.getShaderNodes() == null) { + techniqueDef.setShaderNodes(new ArrayList()); + techniqueDef.setShaderGenerationInfo(new ShaderGenerationInfo()); + } + + for (Statement statement : statements) { + String[] split = statement.getLine().split("[ \\{]"); + if (statement.getLine().startsWith("ShaderNode ")) { + String name = statement.getLine().substring("ShaderNode".length()).trim(); + if (nodes == null) { + nodes = new HashMap(); + } + if (!nodes.containsKey(name)) { + shaderNode = new ShaderNode(); + shaderNode.setName(name); + techniqueDef.getShaderGenerationInfo().getUnusedNodes().add(name); + + readShaderNode(statement.getContents()); + nodes.put(name, shaderNode); + techniqueDef.getShaderNodes().add(shaderNode); + } else { + throw new MatParseException("ShaderNode " + name + " is already defined", statement); + } + + } else { + throw new MatParseException("ShaderNode", split[0], statement); + } + } + } + + /** + * retrieve the leftType corresponding sampler type from the rightType + * + * @param leftType the left samplerType + * @param rightType the right sampler type (can be multiple types sparated + * by "|" + * @return the type or null if not found + */ + public String fixSamplerType(String leftType, String rightType) { + String[] types = rightType.split("\\|"); + for (String string : types) { + if (leftType.equals(string)) { + return string; + } + } + return null; + } + + /** + * stores a global output + * + * @param var the variable to store + * @param statement1 the statement being read + * @throws IOException + */ + public void storeGlobal(ShaderNodeVariable var, Statement statement1) throws IOException { + var.setShaderOutput(true); + if (shaderNode.getDefinition().getType() == Shader.ShaderType.Vertex) { + ShaderNodeVariable global = techniqueDef.getShaderGenerationInfo().getVertexGlobal(); + if (global != null) { +// global.setCondition(mergeConditions(global.getCondition(), var.getCondition(), "||")); +// var.setCondition(global.getCondition()); + if (!global.getName().equals(var.getName())) { + throw new MatParseException("A global output is already defined for the vertex shader: " + global.getName() + ". vertex shader can only have one global output", statement1); + } + } else { + techniqueDef.getShaderGenerationInfo().setVertexGlobal(var); + } + } else if (shaderNode.getDefinition().getType() == Shader.ShaderType.Fragment) { + storeVariable(var, techniqueDef.getShaderGenerationInfo().getFragmentGlobals()); + } + } + + /** + * store an attribute + * + * @param var the variable ot store + */ + public void storeAttribute(ShaderNodeVariable var) { + storeVariable(var, techniqueDef.getShaderGenerationInfo().getAttributes()); + } + + /** + * store a vertex uniform + * + * @param var the variable ot store + */ + public void storeVertexUniform(ShaderNodeVariable var) { + storeVariable(var, techniqueDef.getShaderGenerationInfo().getVertexUniforms()); + + } + + /** + * store a fragment uniform + * + * @param var the variable ot store + */ + public void storeFragmentUniform(ShaderNodeVariable var) { + storeVariable(var, techniqueDef.getShaderGenerationInfo().getFragmentUniforms()); + + } + + /** + * sets the assetManager + * + * @param assetManager + */ + public void setAssetManager(AssetManager assetManager) { + this.assetManager = assetManager; + } + + /** + * find the definiton from this statement (loads it if necessary) + * + * @param statement the statement being read + * @return the definition + * @throws IOException + */ + public ShaderNodeDefinition findDefinition(Statement statement) throws IOException { + String defLine[] = statement.getLine().split(":"); + String defName = defLine[1].trim(); + + ShaderNodeDefinition def = getNodeDefinitions().get(defName); + if (def == null) { + if (defLine.length == 3) { + List defs = null; + try { + defs = assetManager.loadAsset(new ShaderNodeDefinitionKey(defLine[2].trim())); + } catch (AssetNotFoundException e) { + throw new MatParseException("Couldn't find " + defLine[2].trim(), statement, e); + } + + for (ShaderNodeDefinition definition : defs) { + definition.setPath(defLine[2].trim()); + if (defName.equals(definition.getName())) { + def = definition; + } + if (!(getNodeDefinitions().containsKey(definition.getName()))) { + getNodeDefinitions().put(definition.getName(), definition); + } + } + } + if (def == null) { + throw new MatParseException(defName + " is not a declared as Shader Node Definition", statement); + } + } + return def; + } + + /** + * updates a variable condition form a mapping condition + * + * @param var the variable + * @param mapping the mapping + */ +// public void updateCondition(ShaderNodeVariable var, VariableMapping mapping) { +// +// String condition = mergeConditions(shaderNode.getCondition(), mapping.getCondition(), "&&"); +// +// if (var.getCondition() == null) { +// if (!nulledConditions.contains(var.getNameSpace() + "." + var.getName())) { +// var.setCondition(condition); +// } +// } else { +// var.setCondition(mergeConditions(var.getCondition(), condition, "||")); +// if (var.getCondition() == null) { +// nulledConditions.add(var.getNameSpace() + "." + var.getName()); +// } +// } +// } + /** + * store a varying + * + * @param node the shaderNode + * @param variable the variable to store + */ + public void storeVaryings(ShaderNode node, ShaderNodeVariable variable) { + variable.setShaderOutput(true); + if (node.getDefinition().getType() == Shader.ShaderType.Vertex && shaderNode.getDefinition().getType() == Shader.ShaderType.Fragment) { + DeclaredVariable dv = varyings.get(variable.getName()); + if (dv == null) { + techniqueDef.getShaderGenerationInfo().getVaryings().add(variable); + dv = new DeclaredVariable(variable); + + varyings.put(variable.getName(), dv); + } + dv.addNode(shaderNode); + //if a variable is declared with the same name as an input and an output and is a varying, set it as a shader output so it's declared as a varying only once. + for (VariableMapping variableMapping : node.getInputMapping()) { + if (variableMapping.getLeftVariable().getName().equals(variable.getName())) { + variableMapping.getLeftVariable().setShaderOutput(true); + } + } + } + + } + + /** + * merges 2 condition with the given operator + * + * @param condition1 the first condition + * @param condition2 the second condition + * @param operator the operator ("&&" or "||&) + * @return the merged condition + */ + public String mergeConditions(String condition1, String condition2, String operator) { + if (operator.equals("||") && (condition1 == null || condition2 == null)) { + return null; + } + if (condition1 != null) { + if (condition2 == null) { + return condition1; + } else { + String mergedCondition = "(" + condition1 + ") " + operator + " (" + condition2 + ")"; + return mergedCondition; + } + } else { + return condition2; + } + } + + /** + * search a variable in a list from its name and merge the conditions of the + * variables + * + * @param variable the variable + * @param varList the variable list + */ + public void storeVariable(ShaderNodeVariable variable, List varList) { + for (ShaderNodeVariable var : varList) { + if (var.getName().equals(variable.getName())) { +// var.setCondition(mergeConditions(var.getCondition(), variable.getCondition(), "||")); +// variable.setCondition(var.getCondition()); + return; + } + } + varList.add(variable); + } + + /** + * check the types of a mapping, left type must match right type tkae the + * swizzle into account + * + * @param mapping the mapping + * @param statement1 the statement being read + * @throws MatParseException + */ + protected void checkTypes(VariableMapping mapping, Statement statement1) throws MatParseException { + if (!ShaderUtils.typesMatch(mapping)) { + String ls = mapping.getLeftSwizzling().length() == 0 ? "" : "." + mapping.getLeftSwizzling(); + String rs = mapping.getRightSwizzling().length() == 0 ? "" : "." + mapping.getRightSwizzling(); + throw new MatParseException("Type mismatch, cannot convert" + mapping.getLeftVariable().getType() + ls + " to " + mapping.getRightVariable().getType() + rs, statement1); + } + if (!ShaderUtils.multiplicityMatch(mapping)) { + String type1 = mapping.getLeftVariable().getType() + "[" + mapping.getLeftVariable().getMultiplicity() + "]"; + String type2 = mapping.getRightVariable().getType() + "[" + mapping.getRightVariable().getMultiplicity() + "]"; + throw new MatParseException("Type mismatch, cannot convert" + type1 + " to " + type2, statement1); + } + } + + private Map getNodeDefinitions() { + if (nodeDefinitions == null) { + nodeDefinitions = new HashMap(); + } + return nodeDefinitions; + } + + private void updateConditions(Map map) { + for (DeclaredVariable declaredVariable : map.values()) { + declaredVariable.makeCondition(); + } + } + + public void clear() { + nodeDefinitions.clear(); + nodes.clear(); + shaderNodeDefinition = null; + shaderNode = null; + techniqueDef = null; + attributes.clear(); + vertexDeclaredUniforms.clear(); + fragmentDeclaredUniforms.clear(); + varyings.clear(); + materialDef = null; + shaderLanguage = ""; + shaderName = ""; + varNames = ""; + assetManager = null; + nulledConditions.clear(); + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/scene/plugins/MTLLoader.java b/jme3-core/src/plugins/java/com/jme3/scene/plugins/MTLLoader.java new file mode 100644 index 000000000..375405b7f --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/scene/plugins/MTLLoader.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins; + +import com.jme3.asset.*; +import com.jme3.material.Material; +import com.jme3.material.MaterialList; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.math.ColorRGBA; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.util.PlaceholderAssets; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; +import java.util.NoSuchElementException; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class MTLLoader implements AssetLoader { + + private static final Logger logger = Logger.getLogger(MTLLoader.class.getName()); + + protected Scanner scan; + protected MaterialList matList; + //protected Material material; + protected AssetManager assetManager; + protected String folderName; + protected AssetKey key; + + protected Texture diffuseMap, normalMap, specularMap, alphaMap; + protected ColorRGBA ambient = new ColorRGBA(); + protected ColorRGBA diffuse = new ColorRGBA(); + protected ColorRGBA specular = new ColorRGBA(); + protected float shininess = 16; + protected boolean shadeless; + protected String matName; + protected float alpha = 1; + protected boolean transparent = false; + protected boolean disallowAmbient = false; + protected boolean disallowSpecular = false; + + public void reset(){ + scan = null; + matList = null; +// material = null; + + resetMaterial(); + } + + protected ColorRGBA readColor(){ + ColorRGBA v = new ColorRGBA(); + v.set(scan.nextFloat(), scan.nextFloat(), scan.nextFloat(), 1.0f); + return v; + } + + protected String nextStatement(){ + scan.useDelimiter("\n"); + String result = scan.next(); + scan.useDelimiter("\\p{javaWhitespace}+"); + return result; + } + + protected boolean skipLine(){ + try { + scan.skip(".*\r{0,1}\n"); + return true; + } catch (NoSuchElementException ex){ + // EOF + return false; + } + } + + protected void resetMaterial(){ + ambient.set(ColorRGBA.DarkGray); + diffuse.set(ColorRGBA.LightGray); + specular.set(ColorRGBA.Black); + shininess = 16; + disallowAmbient = false; + disallowSpecular = false; + shadeless = false; + transparent = false; + matName = null; + diffuseMap = null; + specularMap = null; + normalMap = null; + alphaMap = null; + alpha = 1; + } + + protected void createMaterial(){ + Material material; + + if (alpha < 1f && transparent){ + diffuse.a = alpha; + } + + if (shadeless){ + material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + material.setColor("Color", diffuse.clone()); + material.setTexture("ColorMap", diffuseMap); + // TODO: Add handling for alpha map? + }else{ + material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + material.setBoolean("UseMaterialColors", true); + material.setColor("Ambient", ambient.clone()); + material.setColor("Diffuse", diffuse.clone()); + material.setColor("Specular", specular.clone()); + material.setFloat("Shininess", shininess); // prevents "premature culling" bug + + if (diffuseMap != null) material.setTexture("DiffuseMap", diffuseMap); + if (specularMap != null) material.setTexture("SpecularMap", specularMap); + if (normalMap != null) material.setTexture("NormalMap", normalMap); + if (alphaMap != null) material.setTexture("AlphaMap", alphaMap); + } + + if (transparent){ + material.setTransparent(true); + material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + material.getAdditionalRenderState().setAlphaTest(true); + material.getAdditionalRenderState().setAlphaFallOff(0.01f); + } + + matList.put(matName, material); + } + + protected void startMaterial(String name){ + if (matName != null){ + // material is already in cache, generate it + createMaterial(); + } + + // now, reset the params and set the name to start a new material + resetMaterial(); + matName = name; + } + + protected Texture loadTexture(String path){ + String[] split = path.trim().split("\\p{javaWhitespace}+"); + + // will crash if path is an empty string + path = split[split.length-1]; + + String name = new File(path).getName(); + TextureKey texKey = new TextureKey(folderName + name); + texKey.setGenerateMips(true); + Texture texture; + try { + texture = assetManager.loadTexture(texKey); + texture.setWrap(WrapMode.Repeat); + } catch (AssetNotFoundException ex){ + logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key}); + texture = new Texture2D(PlaceholderAssets.getPlaceholderImage()); + texture.setWrap(WrapMode.Repeat); + texture.setKey(key); + } + return texture; + } + + protected boolean readLine(){ + if (!scan.hasNext()){ + return false; + } + + String cmd = scan.next().toLowerCase(); + if (cmd.startsWith("#")){ + // skip entire comment until next line + return skipLine(); + }else if (cmd.equals("newmtl")){ + String name = scan.next(); + startMaterial(name); + }else if (cmd.equals("ka")){ + ambient.set(readColor()); + }else if (cmd.equals("kd")){ + diffuse.set(readColor()); + }else if (cmd.equals("ks")){ + specular.set(readColor()); + }else if (cmd.equals("ns")){ + float shiny = scan.nextFloat(); + if (shiny >= 1){ + shininess = shiny; /* (128f / 1000f)*/ + if (specular.equals(ColorRGBA.Black)){ + specular.set(ColorRGBA.White); + } + }else{ + // For some reason blender likes to export Ns 0 statements + // Ignore Ns 0 instead of setting it + } + + }else if (cmd.equals("d") || cmd.equals("tr")){ + float tempAlpha = scan.nextFloat(); + if (tempAlpha != 0){ + alpha = tempAlpha; + transparent = true; + } + }else if (cmd.equals("map_ka")){ + // ignore it for now + return skipLine(); + }else if (cmd.equals("map_kd")){ + String path = nextStatement(); + diffuseMap = loadTexture(path); + }else if (cmd.equals("map_bump") || cmd.equals("bump")){ + if (normalMap == null){ + String path = nextStatement(); + normalMap = loadTexture(path); + } + }else if (cmd.equals("map_ks")){ + String path = nextStatement(); + specularMap = loadTexture(path); + if (specularMap != null){ + // NOTE: since specular color is modulated with specmap + // make sure we have it set + if (specular.equals(ColorRGBA.Black)){ + specular.set(ColorRGBA.White); + } + } + }else if (cmd.equals("map_d")){ + String path = scan.next(); + alphaMap = loadTexture(path); + transparent = true; + }else if (cmd.equals("illum")){ + int mode = scan.nextInt(); + + switch (mode){ + case 0: + // no lighting + shadeless = true; + break; + case 1: + disallowSpecular = true; + break; + case 2: + case 3: + case 5: + case 8: + break; + case 4: + case 6: + case 7: + case 9: + // Enable transparency + // Works best if diffuse map has an alpha channel + transparent = true; + break; + } + }else if (cmd.equals("ke") || cmd.equals("ni")){ + // Ni: index of refraction - unsupported in jME + // Ke: emission color + return skipLine(); + }else{ + logger.log(Level.WARNING, "Unknown statement in MTL! {0}", cmd); + return skipLine(); + } + + return true; + } + + @SuppressWarnings("empty-statement") + public Object load(AssetInfo info) throws IOException{ + reset(); + + this.key = info.getKey(); + this.assetManager = info.getManager(); + folderName = info.getKey().getFolder(); + matList = new MaterialList(); + + InputStream in = null; + try { + in = info.openStream(); + scan = new Scanner(in); + scan.useLocale(Locale.US); + + while (readLine()); + } finally { + if (in != null){ + in.close(); + } + } + + if (matName != null){ + // still have a material in the vars + createMaterial(); + resetMaterial(); + } + + MaterialList list = matList; + + + + return list; + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/scene/plugins/OBJLoader.java b/jme3-core/src/plugins/java/com/jme3/scene/plugins/OBJLoader.java new file mode 100644 index 000000000..4d57567c4 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/scene/plugins/OBJLoader.java @@ -0,0 +1,606 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins; + +import com.jme3.asset.*; +import com.jme3.material.Material; +import com.jme3.material.MaterialList; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.*; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.scene.mesh.IndexIntBuffer; +import com.jme3.scene.mesh.IndexShortBuffer; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.*; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Reads OBJ format models. + */ +public final class OBJLoader implements AssetLoader { + + private static final Logger logger = Logger.getLogger(OBJLoader.class.getName()); + + protected final ArrayList 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 String currentObjectName; + + protected final HashMap vertIndexMap = new HashMap(100); + protected final IntMap indexVertMap = new IntMap(100); + protected int curIndex = 0; + protected int objectIndex = 0; + protected int geomIndex = 0; + + protected Scanner scan; + protected ModelKey key; + protected AssetManager assetManager; + protected MaterialList matList; + + protected String objName; + protected Node objNode; + + protected static class Vertex { + + Vector3f v; + Vector2f vt; + Vector3f vn; + int index; + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Vertex other = (Vertex) obj; + if (this.v != other.v && (this.v == null || !this.v.equals(other.v))) { + return false; + } + if (this.vt != other.vt && (this.vt == null || !this.vt.equals(other.vt))) { + return false; + } + if (this.vn != other.vn && (this.vn == null || !this.vn.equals(other.vn))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 53 * hash + (this.v != null ? this.v.hashCode() : 0); + hash = 53 * hash + (this.vt != null ? this.vt.hashCode() : 0); + hash = 53 * hash + (this.vn != null ? this.vn.hashCode() : 0); + return hash; + } + } + + protected static class Face { + Vertex[] verticies; + } + + protected class ObjectGroup { + + final String objectName; + + public ObjectGroup(String objectName){ + this.objectName = objectName; + } + + public Spatial createGeometry(){ + Node groupNode = new Node(objectName); + if (objectName == null) { + groupNode.setName("Model"); + } +// 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); +// } + + return groupNode; + } + } + + public void reset(){ + verts.clear(); + texCoords.clear(); + norms.clear(); + faces.clear(); + matFaces.clear(); + + vertIndexMap.clear(); + indexVertMap.clear(); + + currentMatName = null; + matList = null; + curIndex = 0; + geomIndex = 0; + scan = null; + } + + protected void findVertexIndex(Vertex vert){ + Integer index = vertIndexMap.get(vert); + if (index != null){ + vert.index = index.intValue(); + }else{ + vert.index = curIndex++; + vertIndexMap.put(vert, vert.index); + indexVertMap.put(vert.index, vert); + } + } + + protected Face[] quadToTriangle(Face f){ + assert f.verticies.length == 4; + + Face[] t = new Face[]{ new Face(), new Face() }; + t[0].verticies = new Vertex[3]; + t[1].verticies = new Vertex[3]; + + Vertex v0 = f.verticies[0]; + Vertex v1 = f.verticies[1]; + Vertex v2 = f.verticies[2]; + Vertex v3 = f.verticies[3]; + + // find the pair of verticies that is closest to each over + // v0 and v2 + // OR + // v1 and v3 + float d1 = v0.v.distanceSquared(v2.v); + float d2 = v1.v.distanceSquared(v3.v); + if (d1 < d2){ + // put an edge in v0, v2 + t[0].verticies[0] = v0; + t[0].verticies[1] = v1; + t[0].verticies[2] = v3; + + t[1].verticies[0] = v1; + t[1].verticies[1] = v2; + t[1].verticies[2] = v3; + }else{ + // put an edge in v1, v3 + t[0].verticies[0] = v0; + t[0].verticies[1] = v1; + t[0].verticies[2] = v2; + + t[1].verticies[0] = v0; + t[1].verticies[1] = v2; + t[1].verticies[2] = v3; + } + + return t; + } + + private ArrayList vertList = new ArrayList(); + + protected void readFace(){ + Face f = new Face(); + vertList.clear(); + + String line = scan.nextLine().trim(); + String[] verticies = line.split("\\s+"); + for (String vertex : verticies){ + int v = 0; + int vt = 0; + int vn = 0; + + String[] split = vertex.split("/"); + if (split.length == 1){ + v = Integer.parseInt(split[0].trim()); + }else if (split.length == 2){ + v = Integer.parseInt(split[0].trim()); + vt = Integer.parseInt(split[1].trim()); + }else if (split.length == 3 && !split[1].equals("")){ + v = Integer.parseInt(split[0].trim()); + vt = Integer.parseInt(split[1].trim()); + vn = Integer.parseInt(split[2].trim()); + }else if (split.length == 3){ + v = Integer.parseInt(split[0].trim()); + vn = Integer.parseInt(split[2].trim()); + } + + if (v < 0) { + v = verts.size() + v + 1; + } + if (vt < 0) { + vt = texCoords.size() + vt + 1; + } + if (vn < 0) { + vn = norms.size() + vn + 1; + } + + 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."); + return; + } + + f.verticies = new Vertex[vertList.size()]; + for (int i = 0; i < vertList.size(); i++){ + f.verticies[i] = vertList.get(i); + } + + if (matList != null && matFaces.containsKey(currentMatName)){ + matFaces.get(currentMatName).add(f); + }else{ + faces.add(f); // faces that belong to the default material + } + } + + protected Vector3f readVector3(){ + Vector3f v = new Vector3f(); + + v.set(Float.parseFloat(scan.next()), + Float.parseFloat(scan.next()), + Float.parseFloat(scan.next())); + + return v; + } + + protected Vector2f readVector2(){ + Vector2f v = new Vector2f(); + + String line = scan.nextLine().trim(); + String[] split = line.split("\\s+"); + v.setX( Float.parseFloat(split[0].trim()) ); + v.setY( Float.parseFloat(split[1].trim()) ); + +// v.setX(scan.nextFloat()); +// if (scan.hasNextFloat()){ +// v.setY(scan.nextFloat()); +// if (scan.hasNextFloat()){ +// scan.nextFloat(); // ignore +// } +// } + + return v; + } + + protected void loadMtlLib(String name) throws IOException{ + if (!name.toLowerCase().endsWith(".mtl")) + throw new IOException("Expected .mtl file! Got: " + name); + + // NOTE: Cut off any relative/absolute paths + name = new File(name).getName(); + AssetKey mtlKey = new AssetKey(key.getFolder() + name); + try { + matList = (MaterialList) assetManager.loadAsset(mtlKey); + } catch (AssetNotFoundException ex){ + logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{name, key}); + } + + if (matList != null){ + // create face lists for every material + for (String matName : matList.keySet()){ + matFaces.put(matName, new ArrayList()); + } + } + } + + protected boolean nextStatement(){ + try { + scan.skip(".*\r{0,1}\n"); + return true; + } catch (NoSuchElementException ex){ + // EOF + return false; + } + } + + protected boolean readLine() throws IOException{ + if (!scan.hasNext()){ + return false; + } + + String cmd = scan.next(); + if (cmd.startsWith("#")){ + // skip entire comment until next line + return nextStatement(); + }else if (cmd.equals("v")){ + // vertex position + verts.add(readVector3()); + }else if (cmd.equals("vn")){ + // vertex normal + norms.add(readVector3()); + }else if (cmd.equals("vt")){ + // texture coordinate + texCoords.add(readVector2()); + }else if (cmd.equals("f")){ + // face, can be triangle, quad, or polygon (unsupported) + readFace(); + }else if (cmd.equals("usemtl")){ + // use material from MTL lib for the following faces + currentMatName = scan.next(); +// if (!matList.containsKey(currentMatName)) +// throw new IOException("Cannot locate material " + currentMatName + " in MTL file!"); + + }else if (cmd.equals("mtllib")){ + // specify MTL lib to use for this OBJ file + String mtllib = scan.nextLine().trim(); + loadMtlLib(mtllib); + }else if (cmd.equals("s") || cmd.equals("g")){ + return nextStatement(); + }else{ + // skip entire command until next line + logger.log(Level.WARNING, "Unknown statement in OBJ! {0}", cmd); + return nextStatement(); + } + + return true; + } + + protected Geometry createGeometry(ArrayList faceList, String matName) throws IOException{ + if (faceList.isEmpty()) + throw new IOException("No geometry data to generate mesh"); + + // Create mesh from the faces + Mesh mesh = constructMesh(faceList); + + Geometry geom = new Geometry(objName + "-geom-" + (geomIndex++), mesh); + + Material material = null; + if (matName != null && matList != null){ + // Get material from material list + material = matList.get(matName); + } + if (material == null){ + // create default material + material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + material.setFloat("Shininess", 64); + } + geom.setMaterial(material); + if (material.isTransparent()) + geom.setQueueBucket(Bucket.Transparent); + else + geom.setQueueBucket(Bucket.Opaque); + + if (material.getMaterialDef().getName().contains("Lighting") + && mesh.getFloatBuffer(Type.Normal) == null){ + logger.log(Level.WARNING, "OBJ mesh {0} doesn't contain normals! " + + "It might not display correctly", geom.getName()); + } + + return geom; + } + + protected Mesh constructMesh(ArrayList 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); + m.setBuffer(VertexBuffer.Type.Normal, 3, normBuf); + } + if (hasTexCoord){ + tcBuf = BufferUtils.createFloatBuffer(vertIndexMap.size() * 2); + m.setBuffer(VertexBuffer.Type.TexCoord, 2, tcBuf); + } + + 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); + // index buffer and others were set on creation + + m.setStatic(); + m.updateBound(); + m.updateCounts(); + //m.setInterleaved(); + + // clear data generated face statements + // to prepare for next mesh + vertIndexMap.clear(); + indexVertMap.clear(); + curIndex = 0; + + return m; + } + + @SuppressWarnings("empty-statement") + public Object load(AssetInfo info) throws IOException{ + reset(); + + key = (ModelKey) info.getKey(); + assetManager = info.getManager(); + objName = key.getName(); + + String folderName = key.getFolder(); + String ext = key.getExtension(); + objName = objName.substring(0, objName.length() - ext.length() - 1); + if (folderName != null && folderName.length() > 0){ + objName = objName.substring(folderName.length()); + } + + objNode = new Node(objName + "-objnode"); + + if (!(info.getKey() instanceof ModelKey)) + throw new IllegalArgumentException("Model assets must be loaded using a ModelKey"); + + InputStream in = null; + try { + in = info.openStream(); + + scan = new Scanner(in); + scan.useLocale(Locale.US); + + while (readLine()); + } finally { + if (in != null){ + in.close(); + } + } + + if (matFaces.size() > 0){ + for (Entry> 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); + } + + if (objNode.getQuantity() == 1) + // only 1 geometry, so no need to send node + return objNode.getChild(0); + else + return objNode; + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java b/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java new file mode 100644 index 000000000..fbd4d4b95 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLoadException; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetManager; +import com.jme3.asset.cache.AssetCache; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.*; + +/** + * GLSL File parser that supports #import pre-processor statement + */ +public class GLSLLoader implements AssetLoader { + + private AssetManager assetManager; + private Map dependCache = new HashMap(); + + /** + * Used to load {@link ShaderDependencyNode}s. + * Asset caching is disabled. + */ + private class ShaderDependencyKey extends AssetKey { + + public ShaderDependencyKey(String name) { + super(name); + } + + @Override + public Class getCacheType() { + // Disallow caching here + return null; + } + } + + /** + * Creates a {@link ShaderDependencyNode} from a stream representing shader code. + * + * @param in The input stream containing shader code + * @param nodeName + * @return + * @throws IOException + */ + private ShaderDependencyNode loadNode(Reader reader, String nodeName) { + ShaderDependencyNode node = new ShaderDependencyNode(nodeName); + + StringBuilder sb = new StringBuilder(); + BufferedReader bufReader = new BufferedReader(reader); + try { + String ln; + while ((ln = bufReader.readLine()) != null) { + if (ln.trim().startsWith("#import ")) { + ln = ln.trim().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 + ShaderDependencyNode dependNode = dependCache.get(ln); + + if (dependNode == null) { + Reader dependNodeReader = assetManager.loadAsset(new ShaderDependencyKey(ln)); + dependNode = loadNode(dependNodeReader, ln); + } + + node.addDependency(sb.length(), dependNode); + } + } else { + sb.append(ln).append('\n'); + } + } + } catch (IOException ex) { + if (bufReader != null) { + try { + bufReader.close(); + } catch (IOException ex1) { + } + } + throw new AssetLoadException("Failed to load shader node: " + nodeName, ex); + } + + node.setSource(sb.toString()); + dependCache.put(nodeName, node); + return node; + } + + private ShaderDependencyNode nextIndependentNode() throws IOException { + Collection allNodes = dependCache.values(); + + if (allNodes == null || allNodes.isEmpty()) { + return null; + } + + for (ShaderDependencyNode node : allNodes) { + if (node.getDependOnMe().isEmpty()) { + return node; + } + } + + // Circular dependency found.. + for (ShaderDependencyNode node : allNodes){ + System.out.println(node.getName()); + } + + throw new IOException("Circular dependency."); + } + + private String resolveDependencies(ShaderDependencyNode node, Set alreadyInjectedSet) { + if (alreadyInjectedSet.contains(node)) { + return "// " + node.getName() + " was already injected at the top.\n"; + } else { + alreadyInjectedSet.add(node); + } + if (node.getDependencies().isEmpty()) { + return node.getSource(); + } else { + StringBuilder sb = new StringBuilder(node.getSource()); + List resolvedShaderNodes = new ArrayList(); + for (ShaderDependencyNode dependencyNode : node.getDependencies()) { + resolvedShaderNodes.add( resolveDependencies(dependencyNode, alreadyInjectedSet) ); + } + List injectIndices = node.getDependencyInjectIndices(); + for (int i = resolvedShaderNodes.size() - 1; i >= 0; i--) { + // Must insert them backwards .. + sb.insert(injectIndices.get(i), resolvedShaderNodes.get(i)); + } + return sb.toString(); + } + } + + 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.assetManager = info.getManager(); + Reader reader = new InputStreamReader(info.openStream()); + if (info.getKey().getExtension().equals("glsllib")) { + // NOTE: Loopback, GLSLLIB is loaded by this loader + // and needs data as InputStream + return reader; + } else { + ShaderDependencyNode rootNode = loadNode(reader, "[main]"); + String code = resolveDependencies(rootNode, new HashSet()); + dependCache.clear(); + return code; + } + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/shader/plugins/ShaderDependencyNode.java b/jme3-core/src/plugins/java/com/jme3/shader/plugins/ShaderDependencyNode.java new file mode 100644 index 000000000..6af93e30a --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/shader/plugins/ShaderDependencyNode.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader.plugins; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +class ShaderDependencyNode { + + private String shaderSource; + private String shaderName; + + private final List dependencies = new ArrayList(); + private final List dependencyInjectIndices = new ArrayList(); + private final List dependOnMe = new ArrayList(); + + public ShaderDependencyNode(String shaderName){ + this.shaderName = shaderName; + } + + public String getSource() { + return shaderSource; + } + + public void setSource(String shaderSource) { + this.shaderSource = shaderSource; + } + + public String getName() { + return shaderName; + } + + public void setName(String shaderName) { + this.shaderName = shaderName; + } + + public void addDependency(int index, ShaderDependencyNode node){ + if (this.dependencies.contains(node)) { + // already contains dependency .. + return; + } + + this.dependencies.add(node); + this.dependencyInjectIndices.add(index); + node.dependOnMe.add(this); + } + + public void removeDependency(ShaderDependencyNode node) { + int positionInList = this.dependencies.indexOf(node); + if (positionInList == -1) { + throw new IllegalArgumentException("The given node " + + node.getName() + + " is not in this node's (" + + getName() + + ") dependency list"); + } + + this.dependencies.remove(positionInList); + this.dependencyInjectIndices.remove(positionInList); + } + + public List getDependOnMe() { + return Collections.unmodifiableList(dependOnMe); + } + + public List getDependencies() { + return Collections.unmodifiableList(dependencies); + } + + public List getDependencyInjectIndices() { + return Collections.unmodifiableList(dependencyInjectIndices); + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java new file mode 100644 index 000000000..1feb42eb7 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java @@ -0,0 +1,827 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.TextureKey; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture.Type; +import com.jme3.util.BufferUtils; +import com.jme3.util.LittleEndien; +import java.io.DataInput; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * 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 texture3D; + private boolean grayscaleOrAlpha; + private boolean normal; + private Format pixelFormat; + private int bpp; + private int[] sizes; + private int redMask, greenMask, blueMask, alphaMask; + private DataInput in; + + public DDSLoader() { + } + + public Object load(AssetInfo info) throws IOException { + if (!(info.getKey() instanceof TextureKey)) { + throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey"); + } + + InputStream stream = null; + try { + stream = info.openStream(); + in = new LittleEndien(stream); + loadHeader(); + if (texture3D) { + ((TextureKey) info.getKey()).setTextureTypeHint(Type.ThreeDimensional); + } else if (depth > 1) { + ((TextureKey) info.getKey()).setTextureTypeHint(Type.CubeMap); + } + ArrayList data = readData(((TextureKey) info.getKey()).isFlipY()); + return new Image(pixelFormat, width, height, depth, data, sizes); + } finally { + if (stream != null){ + stream.close(); + } + } + } + + public Image load(InputStream stream) throws IOException { + in = new LittleEndien(stream); + loadHeader(); + ArrayList data = readData(false); + return new Image(pixelFormat, width, height, depth, data, sizes); + } + + private void loadDX10Header() throws IOException { + int dxgiFormat = in.readInt(); + if (dxgiFormat != 83) { + throw new IOException("Only DXGI_FORMAT_BC5_UNORM " + + "is supported for DirectX10 DDS! Got: " + dxgiFormat); + } + pixelFormat = Format.LATC; + bpp = 8; + compressed = true; + + int resDim = in.readInt(); + if (resDim == DX10DIM_TEXTURE3D) { + texture3D = true; + } + int miscFlag = in.readInt(); + int arraySize = in.readInt(); + if (is(miscFlag, DX10MISC_TEXTURECUBE)) { + // mark texture as cube + if (arraySize != 6) { + throw new IOException("Cubemaps should consist of 6 images!"); + } + } + + in.skipBytes(4); // skip reserved value + } + + /** + * Reads the header (first 128 bytes) of a DDS File + */ + private void loadHeader() throws IOException { + if (in.readInt() != 0x20534444 || in.readInt() != 124) { + throw new IOException("Not a DDS file"); + } + + flags = in.readInt(); + + if (!is(flags, DDSD_MANDATORY) && !is(flags, DDSD_MANDATORY_DX10)) { + throw new IOException("Mandatory flags missing"); + } + + height = in.readInt(); + width = in.readInt(); + pitchOrSize = in.readInt(); + depth = in.readInt(); + mipMapCount = in.readInt(); + in.skipBytes(44); + pixelFormat = null; + directx10 = false; + readPixelFormat(); + caps1 = in.readInt(); + caps2 = in.readInt(); + in.skipBytes(12); + texture3D = false; + + if (!directx10) { + if (!is(caps1, DDSCAPS_TEXTURE)) { + throw new IOException("File is not a texture"); + } + + if (depth <= 0) { + depth = 1; + } + + if (is(caps2, DDSCAPS2_CUBEMAP)) { + depth = 6; // somewhat of a hack, force loading 6 textures if a cubemap + } + + if (is(caps2, DDSCAPS2_VOLUME)) { + texture3D = true; + } + } + + int expectedMipmaps = 1 + (int) Math.ceil(Math.log(Math.max(height, width)) / LOG2); + + if (is(caps1, DDSCAPS_MIPMAP)) { + if (!is(flags, DDSD_MIPMAPCOUNT)) { + mipMapCount = expectedMipmaps; + } else if (mipMapCount != expectedMipmaps) { + // changed to warning- images often do not have the required amount, + // or specify that they have mipmaps but include only the top level.. + logger.log(Level.WARNING, "Got {0} mipmaps, expected {1}", + new Object[]{mipMapCount, expectedMipmaps}); + } + } else { + mipMapCount = 1; + } + + if (directx10) { + loadDX10Header(); + } + + loadSizes(); + } + + /** + * Reads the PixelFormat structure in a DDS file + */ + private void readPixelFormat() throws IOException { + int pfSize = in.readInt(); + if (pfSize != 32) { + throw new IOException("Pixel format size is " + pfSize + ", not 32"); + } + + int pfFlags = in.readInt(); + normal = is(pfFlags, DDPF_NORMAL); + + if (is(pfFlags, DDPF_FOURCC)) { + compressed = true; + int fourcc = in.readInt(); + int swizzle = in.readInt(); + in.skipBytes(16); + + switch (fourcc) { + case PF_DXT1: + bpp = 4; + if (is(pfFlags, DDPF_ALPHAPIXELS)) { + pixelFormat = Image.Format.DXT1A; + } else { + pixelFormat = Image.Format.DXT1; + } + break; + case PF_DXT3: + bpp = 8; + pixelFormat = Image.Format.DXT3; + break; + case PF_DXT5: + bpp = 8; + pixelFormat = Image.Format.DXT5; + if (swizzle == SWIZZLE_xGxR) { + normal = true; + } + break; + case PF_ATI1: + bpp = 4; + pixelFormat = Image.Format.LTC; + break; + case PF_ATI2: + bpp = 8; + pixelFormat = Image.Format.LATC; + break; + case PF_DX10: + compressed = false; + directx10 = true; + // exit here, the rest of the structure is not valid + // the real format will be available in the DX10 header + return; + default: + throw new IOException("Unknown fourcc: " + string(fourcc) + ", " + Integer.toHexString(fourcc)); + } + + int size = ((width + 3) / 4) * ((height + 3) / 4) * bpp * 2; + + if (is(flags, DDSD_LINEARSIZE)) { + if (pitchOrSize == 0) { + logger.warning("Must use linear size with fourcc"); + pitchOrSize = size; + } else if (pitchOrSize != size) { + logger.log(Level.WARNING, "Expected size = {0}, real = {1}", + new Object[]{size, pitchOrSize}); + } + } else { + pitchOrSize = size; + } + } else { + compressed = false; + + // skip fourCC + in.readInt(); + + bpp = in.readInt(); + redMask = in.readInt(); + greenMask = in.readInt(); + blueMask = in.readInt(); + alphaMask = in.readInt(); + + if (is(pfFlags, DDPF_RGB)) { + if (is(pfFlags, DDPF_ALPHAPIXELS)) { + pixelFormat = Format.RGBA8; + } else { + pixelFormat = Format.RGB8; + } + } else if (is(pfFlags, DDPF_GRAYSCALE) && is(pfFlags, DDPF_ALPHAPIXELS)) { + switch (bpp) { + case 16: + pixelFormat = Format.Luminance8Alpha8; + break; + case 32: + pixelFormat = Format.Luminance16Alpha16; + break; + default: + throw new IOException("Unsupported GrayscaleAlpha BPP: " + bpp); + } + grayscaleOrAlpha = true; + } else if (is(pfFlags, DDPF_GRAYSCALE)) { + switch (bpp) { + case 8: + pixelFormat = Format.Luminance8; + break; + case 16: + pixelFormat = Format.Luminance16; + break; + default: + throw new IOException("Unsupported Grayscale BPP: " + bpp); + } + grayscaleOrAlpha = true; + } else if (is(pfFlags, DDPF_ALPHA)) { + switch (bpp) { + case 8: + pixelFormat = Format.Alpha8; + break; + case 16: + pixelFormat = Format.Alpha16; + break; + default: + throw new IOException("Unsupported Alpha BPP: " + bpp); + } + grayscaleOrAlpha = true; + } else { + throw new IOException("Unknown PixelFormat in DDS file"); + } + + int size = (bpp / 8 * width); + + if (is(flags, DDSD_LINEARSIZE)) { + if (pitchOrSize == 0) { + logger.warning("Linear size said to contain valid value but does not"); + pitchOrSize = size; + } else if (pitchOrSize != size) { + logger.log(Level.WARNING, "Expected size = {0}, real = {1}", + new Object[]{size, pitchOrSize}); + } + } else { + pitchOrSize = size; + } + } + } + + /** + * Computes the sizes of each mipmap level in bytes, and stores it in sizes_[]. + */ + private void loadSizes() { + int mipWidth = width; + int mipHeight = height; + + sizes = new int[mipMapCount]; + int outBpp = pixelFormat.getBitsPerPixel(); + for (int i = 0; i < mipMapCount; i++) { + int size; + if (compressed) { + size = ((mipWidth + 3) / 4) * ((mipHeight + 3) / 4) * outBpp * 2; + } else { + size = mipWidth * mipHeight * outBpp / 8; + } + + sizes[i] = ((size + 3) / 4) * 4; + + mipWidth = Math.max(mipWidth / 2, 1); + mipHeight = Math.max(mipHeight / 2, 1); + } + } + + /** + * Flips the given image data on the Y axis. + * @param data Data array containing image data (without mipmaps) + * @param scanlineSize Size of a single scanline = width * bytesPerPixel + * @param height Height of the image in pixels + * @return The new data flipped by the Y axis + */ + public byte[] flipData(byte[] data, int scanlineSize, int height) { + byte[] newData = new byte[data.length]; + + for (int y = 0; y < height; y++) { + System.arraycopy(data, y * scanlineSize, + newData, (height - y - 1) * scanlineSize, + scanlineSize); + } + + return newData; + } + + /** + * Reads a grayscale image with mipmaps from the InputStream + * @param flip Flip the loaded image by Y axis + * @param totalSize Total size of the image in bytes including the mipmaps + * @return A ByteBuffer containing the grayscale image data with mips. + * @throws java.io.IOException If an error occured while reading from InputStream + */ + public ByteBuffer readGrayscale2D(boolean flip, int totalSize) throws IOException { + ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize); + + if (bpp == 8) { + logger.finest("Source image format: R8"); + } + + assert bpp == pixelFormat.getBitsPerPixel(); + + int mipWidth = width; + int mipHeight = height; + + for (int mip = 0; mip < mipMapCount; mip++) { + byte[] data = new byte[sizes[mip]]; + in.readFully(data); + if (flip) { + data = flipData(data, mipWidth * bpp / 8, mipHeight); + } + buffer.put(data); + + mipWidth = Math.max(mipWidth / 2, 1); + mipHeight = Math.max(mipHeight / 2, 1); + } + + return buffer; + } + + /** + * Reads an uncompressed RGB or RGBA image. + * + * @param flip Flip the image on the Y axis + * @param totalSize Size of the image in bytes including mipmaps + * @return ByteBuffer containing image data with mipmaps in the format specified by pixelFormat_ + * @throws java.io.IOException If an error occured while reading from InputStream + */ + public ByteBuffer readRGB2D(boolean flip, int totalSize) throws IOException { + int redCount = count(redMask), + blueCount = count(blueMask), + greenCount = count(greenMask), + alphaCount = count(alphaMask); + + if (redMask == 0x00FF0000 && greenMask == 0x0000FF00 && blueMask == 0x000000FF) { + if (alphaMask == 0xFF000000 && bpp == 32) { + logger.finest("Data source format: BGRA8"); + } else if (bpp == 24) { + logger.finest("Data source format: BGR8"); + } + } + + int sourcebytesPP = bpp / 8; + int targetBytesPP = pixelFormat.getBitsPerPixel() / 8; + + ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize); + + int mipWidth = width; + int mipHeight = height; + + int offset = 0; + byte[] b = new byte[sourcebytesPP]; + for (int mip = 0; mip < mipMapCount; mip++) { + for (int y = 0; y < mipHeight; y++) { + for (int x = 0; x < mipWidth; x++) { + in.readFully(b); + + int i = byte2int(b); + + byte red = (byte) (((i & redMask) >> redCount)); + byte green = (byte) (((i & greenMask) >> greenCount)); + byte blue = (byte) (((i & blueMask) >> blueCount)); + byte alpha = (byte) (((i & alphaMask) >> alphaCount)); + + if (flip) { + dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP); + } + //else + // dataBuffer.position(offset + (y * width + x) * targetBytesPP); + + if (alphaMask == 0) { + dataBuffer.put(red).put(green).put(blue); + } else { + dataBuffer.put(red).put(green).put(blue).put(alpha); + } + } + } + + offset += mipWidth * mipHeight * targetBytesPP; + + mipWidth = Math.max(mipWidth / 2, 1); + mipHeight = Math.max(mipHeight / 2, 1); + } + + return dataBuffer; + } + + /** + * Reads a DXT compressed image from the InputStream + * + * @param totalSize Total size of the image in bytes, including mipmaps + * @return ByteBuffer containing compressed DXT image in the format specified by pixelFormat_ + * @throws java.io.IOException If an error occured while reading from InputStream + */ + public ByteBuffer readDXT2D(boolean flip, int totalSize) throws IOException { + logger.finest("Source image format: DXT"); + + ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize); + + int mipWidth = width; + int mipHeight = height; + + for (int mip = 0; mip < mipMapCount; mip++) { + if (flip) { + byte[] data = new byte[sizes[mip]]; + in.readFully(data); + ByteBuffer wrapped = ByteBuffer.wrap(data); + wrapped.rewind(); + ByteBuffer flipped = DXTFlipper.flipDXT(wrapped, mipWidth, mipHeight, pixelFormat); + buffer.put(flipped); + } else { + byte[] data = new byte[sizes[mip]]; + in.readFully(data); + buffer.put(data); + } + + mipWidth = Math.max(mipWidth / 2, 1); + mipHeight = Math.max(mipHeight / 2, 1); + } + buffer.rewind(); + + return buffer; + } + + /** + * Reads a grayscale image with mipmaps from the InputStream + * @param flip Flip the loaded image by Y axis + * @param totalSize Total size of the image in bytes including the mipmaps + * @return A ByteBuffer containing the grayscale image data with mips. + * @throws java.io.IOException If an error occured while reading from InputStream + */ + public ByteBuffer readGrayscale3D(boolean flip, int totalSize) throws IOException { + ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize * depth); + + if (bpp == 8) { + logger.finest("Source image format: R8"); + } + + assert bpp == pixelFormat.getBitsPerPixel(); + + + for (int i = 0; i < depth; i++) { + int mipWidth = width; + int mipHeight = height; + + for (int mip = 0; mip < mipMapCount; mip++) { + byte[] data = new byte[sizes[mip]]; + in.readFully(data); + if (flip) { + data = flipData(data, mipWidth * bpp / 8, mipHeight); + } + buffer.put(data); + + mipWidth = Math.max(mipWidth / 2, 1); + mipHeight = Math.max(mipHeight / 2, 1); + } + } + buffer.rewind(); + return buffer; + } + + /** + * Reads an uncompressed RGB or RGBA image. + * + * @param flip Flip the image on the Y axis + * @param totalSize Size of the image in bytes including mipmaps + * @return ByteBuffer containing image data with mipmaps in the format specified by pixelFormat_ + * @throws java.io.IOException If an error occured while reading from InputStream + */ + public ByteBuffer readRGB3D(boolean flip, int totalSize) throws IOException { + int redCount = count(redMask), + blueCount = count(blueMask), + greenCount = count(greenMask), + alphaCount = count(alphaMask); + + if (redMask == 0x00FF0000 && greenMask == 0x0000FF00 && blueMask == 0x000000FF) { + if (alphaMask == 0xFF000000 && bpp == 32) { + logger.finest("Data source format: BGRA8"); + } else if (bpp == 24) { + logger.finest("Data source format: BGR8"); + } + } + + int sourcebytesPP = bpp / 8; + int targetBytesPP = pixelFormat.getBitsPerPixel() / 8; + + ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize * depth); + + for (int k = 0; k < depth; k++) { + // ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize); + int mipWidth = width; + int mipHeight = height; + int offset = k * totalSize; + byte[] b = new byte[sourcebytesPP]; + for (int mip = 0; mip < mipMapCount; mip++) { + for (int y = 0; y < mipHeight; y++) { + for (int x = 0; x < mipWidth; x++) { + in.readFully(b); + + int i = byte2int(b); + + byte red = (byte) (((i & redMask) >> redCount)); + byte green = (byte) (((i & greenMask) >> greenCount)); + byte blue = (byte) (((i & blueMask) >> blueCount)); + byte alpha = (byte) (((i & alphaMask) >> alphaCount)); + + if (flip) { + dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP); + } + //else + // dataBuffer.position(offset + (y * width + x) * targetBytesPP); + + if (alphaMask == 0) { + dataBuffer.put(red).put(green).put(blue); + } else { + dataBuffer.put(red).put(green).put(blue).put(alpha); + } + } + } + + offset += (mipWidth * mipHeight * targetBytesPP); + + mipWidth = Math.max(mipWidth / 2, 1); + mipHeight = Math.max(mipHeight / 2, 1); + } + } + dataBuffer.rewind(); + return dataBuffer; + } + + /** + * Reads a DXT compressed image from the InputStream + * + * @param totalSize Total size of the image in bytes, including mipmaps + * @return ByteBuffer containing compressed DXT image in the format specified by pixelFormat_ + * @throws java.io.IOException If an error occured while reading from InputStream + */ + public ByteBuffer readDXT3D(boolean flip, int totalSize) throws IOException { + logger.finest("Source image format: DXT"); + + ByteBuffer bufferAll = BufferUtils.createByteBuffer(totalSize * depth); + + for (int i = 0; i < depth; i++) { + ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize); + int mipWidth = width; + int mipHeight = height; + for (int mip = 0; mip < mipMapCount; mip++) { + if (flip) { + byte[] data = new byte[sizes[mip]]; + in.readFully(data); + ByteBuffer wrapped = ByteBuffer.wrap(data); + wrapped.rewind(); + ByteBuffer flipped = DXTFlipper.flipDXT(wrapped, mipWidth, mipHeight, pixelFormat); + flipped.rewind(); + buffer.put(flipped); + } else { + byte[] data = new byte[sizes[mip]]; + in.readFully(data); + buffer.put(data); + } + + mipWidth = Math.max(mipWidth / 2, 1); + mipHeight = Math.max(mipHeight / 2, 1); + } + buffer.rewind(); + bufferAll.put(buffer); + } + + return bufferAll; + } + + /** + * Reads the image data from the InputStream in the required format. + * If the file contains a cubemap image, it is loaded as 6 ByteBuffers + * (potentially containing mipmaps if they were specified), otherwise + * a single ByteBuffer is returned for a 2D image. + * + * @param flip Flip the image data or not. + * For cubemaps, each of the cubemap faces is flipped individually. + * If the image is DXT compressed, no flipping is done. + * @return An ArrayList containing a single ByteBuffer for a 2D image, or 6 ByteBuffers for a cubemap. + * The cubemap ByteBuffer order is PositiveX, NegativeX, PositiveY, NegativeY, PositiveZ, NegativeZ. + * + * @throws java.io.IOException If an error occured while reading from the stream. + */ + public ArrayList 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 && !texture3D) { + for (int i = 0; i < depth; i++) { + if (compressed) { + allMaps.add(readDXT2D(flip, totalSize)); + } else if (grayscaleOrAlpha) { + allMaps.add(readGrayscale2D(flip, totalSize)); + } else { + allMaps.add(readRGB2D(flip, totalSize)); + } + } + } else if (texture3D) { + if (compressed) { + allMaps.add(readDXT3D(flip, totalSize)); + } else if (grayscaleOrAlpha) { + allMaps.add(readGrayscale3D(flip, totalSize)); + } else { + allMaps.add(readRGB3D(flip, totalSize)); + } + + } else { + if (compressed) { + allMaps.add(readDXT2D(flip, totalSize)); + } else if (grayscaleOrAlpha) { + allMaps.add(readGrayscale2D(flip, totalSize)); + } else { + allMaps.add(readRGB2D(flip, totalSize)); + } + } + + return allMaps; + } + + /** + * Checks if flags contains the specified mask + */ + private static boolean is(int flags, int mask) { + return (flags & mask) == mask; + } + + /** + * Counts the amount of bits needed to shift till bitmask n is at zero + * @param n Bitmask to test + */ + private static int count(int n) { + if (n == 0) { + return 0; + } + + int i = 0; + while ((n & 0x1) == 0) { + n = n >> 1; + i++; + if (i > 32) { + throw new RuntimeException(Integer.toHexString(n)); + } + } + + return i; + } + + /** + * Converts a 1 to 4 sized byte array to an integer + */ + private static int byte2int(byte[] b) { + if (b.length == 1) { + return b[0] & 0xFF; + } else if (b.length == 2) { + return (b[0] & 0xFF) | ((b[1] & 0xFF) << 8); + } else if (b.length == 3) { + return (b[0] & 0xFF) | ((b[1] & 0xFF) << 8) | ((b[2] & 0xFF) << 16); + } else if (b.length == 4) { + return (b[0] & 0xFF) | ((b[1] & 0xFF) << 8) | ((b[2] & 0xFF) << 16) | ((b[3] & 0xFF) << 24); + } else { + return 0; + } + } + + /** + * Converts a int representing a FourCC into a String + */ + private static String string(int value) { + StringBuilder buf = new StringBuilder(); + + buf.append((char) (value & 0xFF)); + buf.append((char) ((value & 0xFF00) >> 8)); + buf.append((char) ((value & 0xFF0000) >> 16)); + buf.append((char) ((value & 0xFF00000) >> 24)); + + return buf.toString(); + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java new file mode 100644 index 000000000..90a8f3a72 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.plugins; + +import com.jme3.math.FastMath; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * DXTFlipper is a utility class used to flip along Y axis DXT compressed textures. + * + * @author Kirill Vainer + */ +public class DXTFlipper { + + private static final ByteBuffer bb = ByteBuffer.allocate(8); + + static { + bb.order(ByteOrder.LITTLE_ENDIAN); + } + + private static long readCode5(long data, int x, int y){ + long shift = (4 * y + x) * 3; + long mask = 0x7; + mask <<= shift; + long code = data & mask; + code >>= shift; + return code; + } + + private static long writeCode5(long data, int x, int y, long code){ + long shift = (4 * y + x) * 3; + long mask = 0x7; + code = (code & mask) << shift; + mask <<= shift; + mask = ~mask; + data &= mask; + data |= code; // write new code + return data; + } + + private static void flipDXT5Block(byte[] block, int h){ + if (h == 1) + return; + + byte c0 = block[0]; + byte c1 = block[1]; + + bb.clear(); + bb.put(block, 2, 6).flip(); + bb.clear(); + long l = bb.getLong(); + long n = l; + + if (h == 2){ + n = writeCode5(n, 0, 0, readCode5(l, 0, 1)); + n = writeCode5(n, 1, 0, readCode5(l, 1, 1)); + n = writeCode5(n, 2, 0, readCode5(l, 2, 1)); + n = writeCode5(n, 3, 0, readCode5(l, 3, 1)); + + n = writeCode5(n, 0, 1, readCode5(l, 0, 0)); + n = writeCode5(n, 1, 1, readCode5(l, 1, 0)); + n = writeCode5(n, 2, 1, readCode5(l, 2, 0)); + n = writeCode5(n, 3, 1, readCode5(l, 3, 0)); + }else{ + n = writeCode5(n, 0, 0, readCode5(l, 0, 3)); + n = writeCode5(n, 1, 0, readCode5(l, 1, 3)); + n = writeCode5(n, 2, 0, readCode5(l, 2, 3)); + n = writeCode5(n, 3, 0, readCode5(l, 3, 3)); + + n = writeCode5(n, 0, 1, readCode5(l, 0, 2)); + n = writeCode5(n, 1, 1, readCode5(l, 1, 2)); + n = writeCode5(n, 2, 1, readCode5(l, 2, 2)); + n = writeCode5(n, 3, 1, readCode5(l, 3, 2)); + + n = writeCode5(n, 0, 2, readCode5(l, 0, 1)); + n = writeCode5(n, 1, 2, readCode5(l, 1, 1)); + n = writeCode5(n, 2, 2, readCode5(l, 2, 1)); + n = writeCode5(n, 3, 2, readCode5(l, 3, 1)); + + n = writeCode5(n, 0, 3, readCode5(l, 0, 0)); + n = writeCode5(n, 1, 3, readCode5(l, 1, 0)); + n = writeCode5(n, 2, 3, readCode5(l, 2, 0)); + n = writeCode5(n, 3, 3, readCode5(l, 3, 0)); + } + + bb.clear(); + bb.putLong(n); + bb.clear(); + bb.get(block, 2, 6).flip(); + + assert c0 == block[0] && c1 == block[1]; + } + + private static void flipDXT3Block(byte[] block, int h){ + if (h == 1) + return; + + // first row + byte tmp0 = block[0]; + byte tmp1 = block[1]; + + if (h == 2){ + block[0] = block[2]; + block[1] = block[3]; + + block[2] = tmp0; + block[3] = tmp1; + }else{ + // write last row to first row + block[0] = block[6]; + block[1] = block[7]; + + // write first row to last row + block[6] = tmp0; + block[7] = tmp1; + + // 2nd row + tmp0 = block[2]; + tmp1 = block[3]; + + // write 3rd row to 2nd + block[2] = block[4]; + block[3] = block[5]; + + // write 2nd row to 3rd + block[4] = tmp0; + block[5] = tmp1; + } + } + + /** + * Flips a DXT color block or a DXT3 alpha block + * @param block + * @param h + */ + private static void flipDXT1orDXTA3Block(byte[] block, int h){ + byte tmp; + switch (h){ + case 1: + return; + case 2: + // keep header intact (the two colors) + // header takes 4 bytes + + // flip only two top rows + tmp = block[4+1]; + block[4+1] = block[4+0]; + block[4+0] = tmp; + return; + default: + // keep header intact (the two colors) + // header takes 4 bytes + + // flip first & fourth row + tmp = block[4+3]; + block[4+3] = block[4+0]; + block[4+0] = tmp; + + // flip second and third row + tmp = block[4+2]; + block[4+2] = block[4+1]; + block[4+1] = tmp; + return; + } + } + + public static ByteBuffer flipDXT(ByteBuffer img, int w, int h, Format format){ + int originalLimit = img.limit(); + 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(); + }else if (h == 2){ + byte[] colorBlock = new byte[8]; + byte[] alphaBlock = type != 1 && type != 5 ? new byte[8] : null; + for (int x = 0; x < blocksX; x++){ + // prepeare for block reading + int blockByteOffset = x * bpb; + img.position(blockByteOffset); + img.limit(blockByteOffset + bpb); + + img.get(colorBlock); + if (type == 4 || type == 5) + flipDXT5Block(colorBlock, h); + else + flipDXT1orDXTA3Block(colorBlock, h); + + // write block (no need to flip block indexes, only pixels + // inside block + retImg.put(colorBlock); + + if (alphaBlock != null){ + img.get(alphaBlock); + switch (type){ + case 2: + flipDXT3Block(alphaBlock, h); break; + case 3: + case 4: + flipDXT5Block(alphaBlock, h); + break; + } + retImg.put(alphaBlock); + } + } + retImg.rewind(); + }else if (h >= 4){ + byte[] colorBlock = new byte[8]; + byte[] alphaBlock = type != 1 && type != 5 ? new byte[8] : null; + for (int y = 0; y < blocksY; y++){ + for (int x = 0; x < blocksX; x++){ + // prepeare for block reading + int blockIdx = y * blocksX + x; + int blockByteOffset = blockIdx * bpb; + + img.position(blockByteOffset); + img.limit(blockByteOffset + bpb); + + blockIdx = (blocksY - y - 1) * blocksX + x; + blockByteOffset = blockIdx * bpb; + + retImg.position(blockByteOffset); + retImg.limit(blockByteOffset + bpb); + + if (alphaBlock != null){ + img.get(alphaBlock); + switch (type){ + case 2: + flipDXT3Block(alphaBlock, h); + break; + case 3: + case 4: + flipDXT5Block(alphaBlock, h); + break; + } + retImg.put(alphaBlock); + } + + img.get(colorBlock); + if (type == 4 || type == 5) + flipDXT5Block(colorBlock, h); + else + flipDXT1orDXTA3Block(colorBlock, h); + + retImg.put(colorBlock); + } + } + retImg.limit(retImg.capacity()); + retImg.position(0); + } else { + return null; + } + img.limit(originalLimit); // make sure to restore original limit. + return retImg; + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java new file mode 100644 index 000000000..8a88e2d7f --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.TextureKey; +import com.jme3.math.FastMath; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class HDRLoader implements AssetLoader { + + private static final Logger logger = Logger.getLogger(HDRLoader.class.getName()); + + private boolean writeRGBE = false; + private ByteBuffer rleTempBuffer; + private ByteBuffer dataStore; + private final float[] tempF = new float[3]; + + public HDRLoader(boolean writeRGBE){ + this.writeRGBE = writeRGBE; + } + + public HDRLoader(){ + } + + public static void convertFloatToRGBE(byte[] rgbe, float red, float green, float blue){ + double max = red; + if (green > max) max = green; + if (blue > max) max = blue; + if (max < 1.0e-32){ + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + }else{ + double exp = Math.ceil( Math.log10(max) / Math.log10(2) ); + double divider = Math.pow(2.0, exp); + rgbe[0] = (byte) ((red / divider) * 255.0); + rgbe[1] = (byte) ((green / divider) * 255.0); + rgbe[2] = (byte) ((blue / divider) * 255.0); + rgbe[3] = (byte) (exp + 128.0); + } + } + + public static void convertRGBEtoFloat(byte[] rgbe, float[] rgbf){ + int R = rgbe[0] & 0xFF, + G = rgbe[1] & 0xFF, + B = rgbe[2] & 0xFF, + E = rgbe[3] & 0xFF; + + float e = (float) Math.pow(2f, E - (128 + 8) ); + rgbf[0] = R * e; + rgbf[1] = G * e; + rgbf[2] = B * e; + } + + public static void convertRGBEtoFloat2(byte[] rgbe, float[] rgbf){ + int R = rgbe[0] & 0xFF, + G = rgbe[1] & 0xFF, + B = rgbe[2] & 0xFF, + E = rgbe[3] & 0xFF; + + float e = (float) Math.pow(2f, E - 128); + rgbf[0] = (R / 256.0f) * e; + rgbf[1] = (G / 256.0f) * e; + rgbf[2] = (B / 256.0f) * e; + } + + public static void convertRGBEtoFloat3(byte[] rgbe, float[] rgbf){ + int R = rgbe[0] & 0xFF, + G = rgbe[1] & 0xFF, + B = rgbe[2] & 0xFF, + E = rgbe[3] & 0xFF; + + float e = (float) Math.pow(2f, E - (128 + 8) ); + rgbf[0] = R * e; + rgbf[1] = G * e; + rgbf[2] = B * e; + } + + private short flip(int in){ + return (short) ((in << 8 & 0xFF00) | (in >> 8)); + } + + private void writeRGBE(byte[] rgbe){ + if (writeRGBE){ + dataStore.put(rgbe); + }else{ + convertRGBEtoFloat(rgbe, tempF); + dataStore.putShort(FastMath.convertFloatToHalf(tempF[0])) + .putShort(FastMath.convertFloatToHalf(tempF[1])). + putShort(FastMath.convertFloatToHalf(tempF[2])); + } + } + + private String readString(InputStream is) throws IOException{ + StringBuilder sb = new StringBuilder(); + while (true){ + int i = is.read(); + if (i == 0x0a || i == -1) // new line or EOF + return sb.toString(); + + sb.append((char)i); + } + } + + private boolean decodeScanlineRLE(InputStream in, int width) throws IOException{ + // must deocde RLE data into temp buffer before converting to float + if (rleTempBuffer == null){ + rleTempBuffer = BufferUtils.createByteBuffer(width * 4); + }else{ + rleTempBuffer.clear(); + if (rleTempBuffer.remaining() < width * 4) + rleTempBuffer = BufferUtils.createByteBuffer(width * 4); + } + + // read each component seperately + for (int i = 0; i < 4; i++) { + // read WIDTH bytes for the channel + for (int j = 0; j < width;) { + int code = in.read(); + if (code > 128) { // run + code -= 128; + int val = in.read(); + while ((code--) != 0) { + rleTempBuffer.put( (j++) * 4 + i , (byte)val); + //scanline[j++][i] = val; + } + } else { // non-run + while ((code--) != 0) { + int val = in.read(); + rleTempBuffer.put( (j++) * 4 + i, (byte)val); + //scanline[j++][i] = in.read(); + } + } + } + } + + rleTempBuffer.rewind(); + byte[] rgbe = new byte[4]; +// float[] temp = new float[3]; + + // decode temp buffer into float data + for (int i = 0; i < width; i++){ + rleTempBuffer.get(rgbe); + writeRGBE(rgbe); + } + + return true; + } + + private boolean decodeScanlineUncompressed(InputStream in, int width) throws IOException{ + byte[] rgbe = new byte[4]; + + for (int i = 0; i < width; i+=3){ + if (in.read(rgbe) < 1) + return false; + + writeRGBE(rgbe); + } + return true; + } + + private void decodeScanline(InputStream in, int width) throws IOException{ + if (width < 8 || width > 0x7fff){ + // too short/long for RLE compression + decodeScanlineUncompressed(in, width); + } + + // check format + byte[] data = new byte[4]; + in.read(data); + if (data[0] != 0x02 || data[1] != 0x02 || (data[2] & 0x80) != 0){ + // not RLE data + decodeScanlineUncompressed(in, width-1); + }else{ + // check scanline width + int readWidth = (data[2] & 0xFF) << 8 | (data[3] & 0xFF); + if (readWidth != width) + throw new IOException("Illegal scanline width in HDR file: "+width+" != "+readWidth); + + // RLE data + decodeScanlineRLE(in, width); + } + } + + public Image load(InputStream in, boolean flipY) throws IOException{ + float gamma = -1f; + float exposure = -1f; + float[] colorcorr = new float[]{ -1f, -1f, -1f }; + + int width = -1, height = -1; + boolean verifiedFormat = false; + + while (true){ + String ln = readString(in); + ln = ln.trim(); + if (ln.startsWith("#") || ln.equals("")){ + if (ln.equals("#?RADIANCE") || ln.equals("#?RGBE")) + verifiedFormat = true; + + continue; // comment or empty statement + } else if (ln.startsWith("+") || ln.startsWith("-")){ + // + or - mark image resolution and start of data + String[] resData = ln.split("\\s"); + if (resData.length != 4){ + throw new IOException("Invalid resolution string in HDR file"); + } + + if (!resData[0].equals("-Y") || !resData[2].equals("+X")){ + logger.warning("Flipping/Rotating attributes ignored!"); + } + + //if (resData[0].endsWith("X")){ + // first width then height + // width = Integer.parseInt(resData[1]); + // height = Integer.parseInt(resData[3]); + //}else{ + width = Integer.parseInt(resData[3]); + height = Integer.parseInt(resData[1]); + //} + + break; + } else { + // regular command + int index = ln.indexOf("="); + if (index < 1){ + logger.log(Level.FINE, "Ignored string: {0}", ln); + continue; + } + + String var = ln.substring(0, index).trim().toLowerCase(); + String value = ln.substring(index+1).trim().toLowerCase(); + if (var.equals("format")){ + if (!value.equals("32-bit_rle_rgbe") && !value.equals("32-bit_rle_xyze")){ + throw new IOException("Unsupported format in HDR picture"); + } + }else if (var.equals("exposure")){ + exposure = Float.parseFloat(value); + }else if (var.equals("gamma")){ + gamma = Float.parseFloat(value); + }else{ + logger.log(Level.WARNING, "HDR Command ignored: {0}", ln); + } + } + } + + assert width != -1 && height != -1; + + if (!verifiedFormat) + logger.warning("Unsure if specified image is Radiance HDR"); + + // some HDR images can get pretty big + System.gc(); + + // each pixel times size of component times # of components + Format pixelFormat; + if (writeRGBE){ + pixelFormat = Format.RGBA8; + }else{ + pixelFormat = Format.RGB16F; + } + + dataStore = BufferUtils.createByteBuffer(width * height * pixelFormat.getBitsPerPixel()); + + int bytesPerPixel = pixelFormat.getBitsPerPixel() / 8; + int scanLineBytes = bytesPerPixel * width; + for (int y = height - 1; y >= 0; y--) { + if (flipY) + dataStore.position(scanLineBytes * y); + + decodeScanline(in, width); + } + in.close(); + + dataStore.rewind(); + return new Image(pixelFormat, width, height, dataStore); + } + + public Object load(AssetInfo info) throws IOException { + if (!(info.getKey() instanceof TextureKey)) + throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey"); + + boolean flip = ((TextureKey) info.getKey()).isFlipY(); + InputStream in = null; + try { + in = info.openStream(); + Image img = load(in, flip); + return img; + } finally { + if (in != null){ + in.close(); + } + } + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ImageFlipper.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ImageFlipper.java new file mode 100644 index 000000000..e74a70783 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ImageFlipper.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.plugins; + +import com.jme3.texture.Image; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; + +/** + * ImageFlipper is a utility class used to flip images across the Y axis. + * Due to the standard of where the image origin is between OpenGL and + * other software, this class is required. + * + * @author Kirill Vainer + */ +public class ImageFlipper { + + public static void flipImage(Image img, int index){ + if (img.getFormat().isCompressed()) + throw new UnsupportedOperationException("Flipping compressed " + + "images is unsupported."); + + int w = img.getWidth(); + int h = img.getHeight(); + int halfH = h / 2; + + // bytes per pixel + int bpp = img.getFormat().getBitsPerPixel() / 8; + int scanline = w * bpp; + + ByteBuffer data = img.getData(index); + ByteBuffer temp = BufferUtils.createByteBuffer(scanline); + + data.rewind(); + for (int y = 0; y < halfH; y++){ + int oppY = h - y - 1; + // read in scanline + data.position(y * scanline); + data.limit(data.position() + scanline); + + temp.rewind(); + temp.put(data); + + } + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/PFMLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/PFMLoader.java new file mode 100644 index 000000000..a2a454206 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/PFMLoader.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.TextureKey; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.logging.Logger; + +public class PFMLoader implements AssetLoader { + + private static final Logger logger = Logger.getLogger(PFMLoader.class.getName()); + + private String readString(InputStream is) throws IOException{ + StringBuilder sb = new StringBuilder(); + while (true){ + int i = is.read(); + if (i == 0x0a || i == -1) // new line or EOF + return sb.toString(); + + sb.append((char)i); + } + } + + private void flipScanline(byte[] scanline){ + for (int i = 0; i < scanline.length; i+=4){ + // flip first and fourth bytes + byte tmp = scanline[i+3]; + scanline[i+3] = scanline[i+0]; + scanline[i+0] = tmp; + + // flip second and third bytes + tmp = scanline[i+2]; + scanline[i+2] = scanline[i+1]; + scanline[i+1] = tmp; + } + } + + private Image load(InputStream in, boolean needYFlip) throws IOException{ + Format format = null; + + String fmtStr = readString(in); + if (fmtStr.equals("PF")){ + format = Format.RGB32F; + }else if (fmtStr.equals("Pf")){ + format = Format.Luminance32F; + }else{ + throw new IOException("File is not PFM format"); + } + + String sizeStr = readString(in); + int spaceIdx = sizeStr.indexOf(" "); + if (spaceIdx <= 0 || spaceIdx >= sizeStr.length() - 1) + throw new IOException("Invalid size syntax in PFM file"); + + int width = Integer.parseInt(sizeStr.substring(0,spaceIdx)); + int height = Integer.parseInt(sizeStr.substring(spaceIdx+1)); + + if (width <= 0 || height <= 0) + throw new IOException("Invalid size specified in PFM file"); + + String scaleStr = readString(in); + float scale = Float.parseFloat(scaleStr); + ByteOrder order = scale < 0 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; + boolean needEndienFlip = order != ByteOrder.nativeOrder(); + + // make sure all unneccessary stuff gets deleted from heap + // before allocating large amount of memory + System.gc(); + + int bytesPerPixel = format.getBitsPerPixel() / 8; + int scanLineBytes = bytesPerPixel * width; + + ByteBuffer imageData = BufferUtils.createByteBuffer(width * height * bytesPerPixel); + byte[] scanline = new byte[width * bytesPerPixel]; + + for (int y = height - 1; y >= 0; y--) { + if (!needYFlip) + imageData.position(scanLineBytes * y); + + int read = 0; + int off = 0; + do { + read = in.read(scanline, off, scanline.length - off); + off += read; + } while (read > 0); + + if (needEndienFlip){ + flipScanline(scanline); + } + + imageData.put(scanline); + } + imageData.rewind(); + + return new Image(format, width, height, imageData); + } + + public Object load(AssetInfo info) throws IOException { + if (!(info.getKey() instanceof TextureKey)) + throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey"); + + InputStream in = null; + try { + in = info.openStream(); + return load(in, ((TextureKey)info.getKey()).isFlipY()); + } finally { + if (in != null){ + in.close(); + } + } + + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/TGALoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/TGALoader.java new file mode 100644 index 000000000..20f50df12 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/TGALoader.java @@ -0,0 +1,528 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.TextureKey; +import com.jme3.math.FastMath; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * 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 = null; + try { + in = info.openStream(); + Image img = load(in, flip); + return img; + } finally { + if (in != null) { + in.close(); + } + } + } + + /** + * loadImage is a manual image loader which is entirely + * independent of AWT. OUT: RGB888 or RGBA8888 Image object + * + * + + * @param in + * InputStream of an uncompressed 24b RGB or 32b RGBA TGA + * @param flip + * Flip the image vertically + * @return Image object that contains the + * image, either as a RGB888 or RGBA8888 + * @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) { + dis.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]; + dis.read(cMapData); + + // Only go to the trouble of constructing the color map + // table if this is declared a color mapped image. + if (imageType == TYPE_COLORMAPPED || imageType == TYPE_COLORMAPPED_RLE) { + cMapEntries = new ColorMapEntry[cMapLength]; + int alphaSize = cMapDepth - (3 * bitsPerColor); + float scalar = 255f / (FastMath.pow(2, bitsPerColor) - 1); + float alphaScalar = 255f / (FastMath.pow(2, alphaSize) - 1); + for (int i = 0; i < cMapLength; i++) { + ColorMapEntry entry = new ColorMapEntry(); + int offset = cMapDepth * i; + entry.red = (byte) (int) (getBitsAsByte(cMapData, offset, bitsPerColor) * scalar); + entry.green = (byte) (int) (getBitsAsByte(cMapData, offset + bitsPerColor, bitsPerColor) * scalar); + entry.blue = (byte) (int) (getBitsAsByte(cMapData, offset + (2 * bitsPerColor), bitsPerColor) * scalar); + if (alphaSize <= 0) { + entry.alpha = (byte) 255; + } else { + entry.alpha = (byte) (int) (getBitsAsByte(cMapData, offset + (3 * bitsPerColor), alphaSize) * alphaScalar); + } + + cMapEntries[i] = entry; + } + } + } + + + // Allocate image data array + Format format; + byte[] rawData = null; + int dl; + if (pixelDepth == 32) { + rawData = new byte[width * height * 4]; + dl = 4; + } else { + rawData = new byte[width * height * 3]; + dl = 3; + } + int rawDataIndex = 0; + + if (imageType == TYPE_TRUECOLOR) { + byte red = 0; + byte green = 0; + byte blue = 0; + byte alpha = 0; + + // Faster than doing a 16-or-24-or-32 check on each individual pixel, + // just make a seperate loop for each. + if (pixelDepth == 16) { + byte[] data = new byte[2]; + float scalar = 255f / 31f; + for (int i = 0; i <= (height - 1); i++) { + if (!flip) { + rawDataIndex = (height - 1 - i) * width * dl; + } + for (int j = 0; j < width; j++) { + data[1] = dis.readByte(); + data[0] = dis.readByte(); + rawData[rawDataIndex++] = (byte) (int) (getBitsAsByte(data, 1, 5) * scalar); + rawData[rawDataIndex++] = (byte) (int) (getBitsAsByte(data, 6, 5) * scalar); + rawData[rawDataIndex++] = (byte) (int) (getBitsAsByte(data, 11, 5) * scalar); + if (dl == 4) { + // create an alpha channel + alpha = getBitsAsByte(data, 0, 1); + if (alpha == 1) { + alpha = (byte) 255; + } + rawData[rawDataIndex++] = alpha; + } + } + } + + format = dl == 4 ? Format.RGBA8 : Format.RGB8; + } else if (pixelDepth == 24) { + for (int y = 0; y < height; y++) { + if (!flip) { + rawDataIndex = (height - 1 - y) * width * dl; + } else { + rawDataIndex = y * width * dl; + } + + dis.readFully(rawData, rawDataIndex, width * dl); +// for (int x = 0; x < width; x++) { + //read scanline +// blue = dis.readByte(); +// green = dis.readByte(); +// red = dis.readByte(); +// rawData[rawDataIndex++] = red; +// rawData[rawDataIndex++] = green; +// rawData[rawDataIndex++] = blue; +// } + } + format = Format.BGR8; + } else if (pixelDepth == 32) { + for (int i = 0; i <= (height - 1); i++) { + if (!flip) { + rawDataIndex = (height - 1 - i) * width * dl; + } + + for (int j = 0; j < width; j++) { + blue = dis.readByte(); + green = dis.readByte(); + red = dis.readByte(); + alpha = dis.readByte(); + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + rawData[rawDataIndex++] = alpha; + } + } + format = Format.RGBA8; + } else { + throw new IOException("Unsupported TGA true color depth: " + pixelDepth); + } + } else if (imageType == TYPE_TRUECOLOR_RLE) { + byte red = 0; + byte green = 0; + byte blue = 0; + byte alpha = 0; + // Faster than doing a 16-or-24-or-32 check on each individual pixel, + // just make a seperate loop for each. + if (pixelDepth == 32) { + for (int i = 0; i <= (height - 1); ++i) { + if (!flip) { + rawDataIndex = (height - 1 - i) * width * dl; + } + + for (int j = 0; j < width; ++j) { + // Get the number of pixels the next chunk covers (either packed or unpacked) + int count = dis.readByte(); + if ((count & 0x80) != 0) { + // Its an RLE packed block - use the following 1 pixel for the next 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.blue; + rawData[rawDataIndex++] = entry.green; + rawData[rawDataIndex++] = entry.red; + 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.blue; + rawData[rawDataIndex++] = entry.green; + rawData[rawDataIndex++] = entry.red; + if (dl == 4) { + rawData[rawDataIndex++] = entry.alpha; + } + } + } + } else { + throw new IOException("TGA: unknown colormap indexing size used: " + bytesPerIndex); + } + + format = dl == 4 ? Format.RGBA8 : Format.RGB8; + } else { + throw new IOException("Monochrome and RLE colormapped images are not supported"); + } + + + in.close(); + // Get a pointer to the image memory + ByteBuffer scratch = BufferUtils.createByteBuffer(rawData.length); + scratch.clear(); + scratch.put(rawData); + scratch.rewind(); + // Create the Image object + Image textureImage = new Image(); + textureImage.setFormat(format); + textureImage.setWidth(width); + textureImage.setHeight(height); + textureImage.setData(scratch); + return textureImage; + } + + private static byte getBitsAsByte(byte[] data, int offset, int length) { + int offsetBytes = offset / 8; + int indexBits = offset % 8; + int rVal = 0; + + // start at data[offsetBytes]... spill into next byte as needed. + for (int i = length; --i >= 0;) { + byte b = data[offsetBytes]; + int test = indexBits == 7 ? 1 : 2 << (6 - indexBits); + if ((b & test) != 0) { + if (i == 0) { + rVal++; + } else { + rVal += (2 << i - 1); + } + } + indexBits++; + if (indexBits == 8) { + indexBits = 0; + offsetBytes++; + } + } + + return (byte) rVal; + } + + /** + * 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/jme3-core/src/tools/java/jme3tools/converters/Converter.java b/jme3-core/src/tools/java/jme3tools/converters/Converter.java new file mode 100644 index 000000000..4b3f5cd94 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/Converter.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.converters; + +import java.util.Map; + +public interface Converter { + public T convert(T input, Map params); +} diff --git a/jme3-core/src/tools/java/jme3tools/converters/FolderConverter.java b/jme3-core/src/tools/java/jme3tools/converters/FolderConverter.java new file mode 100644 index 000000000..a2edda488 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/FolderConverter.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/converters/RGB565.java b/jme3-core/src/tools/java/jme3tools/converters/RGB565.java new file mode 100644 index 000000000..557ab06b1 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/RGB565.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/converters/model/FloatToFixed.java b/jme3-core/src/tools/java/jme3tools/converters/model/FloatToFixed.java new file mode 100644 index 000000000..7e2b83e14 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/FloatToFixed.java @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.converters.model; + +import com.jme3.bounding.BoundingBox; +import com.jme3.math.Transform; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.BufferUtils; +import java.nio.*; + +@Deprecated +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; + + @Deprecated + 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/jme3-core/src/tools/java/jme3tools/converters/model/ModelConverter.java b/jme3-core/src/tools/java/jme3tools/converters/model/ModelConverter.java new file mode 100644 index 000000000..26c6cb135 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/ModelConverter.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.converters.model; + +import com.jme3.scene.*; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.nio.Buffer; +import java.util.Arrays; +import java.util.Comparator; +import jme3tools.converters.model.strip.PrimitiveGroup; +import jme3tools.converters.model.strip.TriStrip; + +public class ModelConverter { + + private static final class PrimComparator + implements Comparator { + + 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.getIndicesAsList(); + 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/jme3-core/src/tools/java/jme3tools/converters/model/strip/EdgeInfo.java b/jme3-core/src/tools/java/jme3tools/converters/model/strip/EdgeInfo.java new file mode 100644 index 000000000..e7b8700ac --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/strip/EdgeInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/converters/model/strip/EdgeInfoVec.java b/jme3-core/src/tools/java/jme3tools/converters/model/strip/EdgeInfoVec.java new file mode 100644 index 000000000..44324bab4 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/strip/EdgeInfoVec.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/converters/model/strip/FaceInfo.java b/jme3-core/src/tools/java/jme3tools/converters/model/strip/FaceInfo.java new file mode 100644 index 000000000..a1162033e --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/strip/FaceInfo.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/converters/model/strip/FaceInfoVec.java b/jme3-core/src/tools/java/jme3tools/converters/model/strip/FaceInfoVec.java new file mode 100644 index 000000000..96bd612f0 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/strip/FaceInfoVec.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/converters/model/strip/IntVec.java b/jme3-core/src/tools/java/jme3tools/converters/model/strip/IntVec.java new file mode 100644 index 000000000..95aeb8426 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/strip/IntVec.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/converters/model/strip/PrimitiveGroup.java b/jme3-core/src/tools/java/jme3tools/converters/model/strip/PrimitiveGroup.java new file mode 100644 index 000000000..235e61b17 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/strip/PrimitiveGroup.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/converters/model/strip/StripInfo.java b/jme3-core/src/tools/java/jme3tools/converters/model/strip/StripInfo.java new file mode 100644 index 000000000..24d7d9b97 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/strip/StripInfo.java @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/converters/model/strip/StripInfoVec.java b/jme3-core/src/tools/java/jme3tools/converters/model/strip/StripInfoVec.java new file mode 100644 index 000000000..76915c245 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/strip/StripInfoVec.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/converters/model/strip/StripStartInfo.java b/jme3-core/src/tools/java/jme3tools/converters/model/strip/StripStartInfo.java new file mode 100644 index 000000000..27f8dad79 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/strip/StripStartInfo.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/converters/model/strip/Stripifier.java b/jme3-core/src/tools/java/jme3tools/converters/model/strip/Stripifier.java new file mode 100644 index 000000000..9b076e498 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/strip/Stripifier.java @@ -0,0 +1,1365 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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.fine("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.fine("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.fine("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.fine("GetNextIndex: Triangle doesn't have all of its vertices\n"); + logger.fine("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.fine("GetNextIndex: Triangle doesn't have all of its vertices\n"); + logger.fine("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.fine("GetNextIndex: Triangle doesn't have all of its vertices\n"); + logger.fine("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/jme3-core/src/tools/java/jme3tools/converters/model/strip/TriStrip.java b/jme3-core/src/tools/java/jme3tools/converters/model/strip/TriStrip.java new file mode 100644 index 000000000..939339947 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/strip/TriStrip.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/converters/model/strip/VertexCache.java b/jme3-core/src/tools/java/jme3tools/converters/model/strip/VertexCache.java new file mode 100644 index 000000000..bf4b4546f --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/converters/model/strip/VertexCache.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.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/jme3-core/src/tools/java/jme3tools/optimize/FastOctnode.java b/jme3-core/src/tools/java/jme3tools/optimize/FastOctnode.java new file mode 100644 index 000000000..f28cc9494 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/optimize/FastOctnode.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.optimize; + +import com.jme3.bounding.BoundingBox; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import java.util.Set; + +public class FastOctnode { + + int offset; + int length; + FastOctnode child; + FastOctnode next; + + private static final BoundingBox tempBox = new BoundingBox(); + + public int getSide(){ + return ((offset & 0xE0000000) >> 29) & 0x7; + } + + public void setSide(int side){ + offset &= 0x1FFFFFFF; + offset |= (side << 29); + } + + public void setOffset(int offset){ + if (offset < 0 || offset > 20000000){ + throw new IllegalArgumentException(); + } + + this.offset &= 0xE0000000; + this.offset |= offset; + } + + public int getOffset(){ + return this.offset & 0x1FFFFFFF; + } + + private void generateRenderSetNoCheck(Geometry[] globalGeomList, Set 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/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java b/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java new file mode 100644 index 000000000..fa5747cf7 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java @@ -0,0 +1,453 @@ +package jme3tools.optimize; + +import com.jme3.material.Material; +import com.jme3.math.Matrix4f; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.*; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.BufferUtils; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.*; +import java.util.logging.Logger; + +public class GeometryBatchFactory { + + private static final Logger logger = Logger.getLogger(GeometryBatchFactory.class.getName()); + + private static void doTransformVerts(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) { + Vector3f pos = new Vector3f(); + + // offset is given in element units + // convert to be in component units + offset *= 3; + + for (int i = 0; i < inBuf.limit() / 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.limit() / 3; i++) { + norm.x = inBuf.get(i * 3 + 0); + norm.y = inBuf.get(i * 3 + 1); + norm.z = inBuf.get(i * 3 + 2); + + transform.multNormal(norm, norm); + + outBuf.put(offset + i * 3 + 0, norm.x); + outBuf.put(offset + i * 3 + 1, norm.y); + outBuf.put(offset + i * 3 + 2, norm.z); + } + } + + private static void doTransformTangents(FloatBuffer inBuf, int offset, int components, FloatBuffer outBuf, Matrix4f transform) { + Vector3f tan = new Vector3f(); + + // offset is given in element units + // convert to be in component units + offset *= components; + + for (int i = 0; i < inBuf.limit() / components; i++) { + tan.x = inBuf.get(i * components + 0); + tan.y = inBuf.get(i * components + 1); + tan.z = inBuf.get(i * components + 2); + + transform.multNormal(tan, tan); + + outBuf.put(offset + i * components + 0, tan.x); + outBuf.put(offset + i * components + 1, tan.y); + outBuf.put(offset + i * components + 2, tan.z); + + if (components == 4) { + outBuf.put(offset + i * components + 3, inBuf.get(i * components + 3)); + } + } + } + + /** + * Merges all geometries in the collection into + * the output mesh. Creates a new material using the TextureAtlas. + * + * @param geometries + * @param outMesh + */ + public static void mergeGeometries(Collection geometries, Mesh outMesh) { + int[] compsForBuf = new int[VertexBuffer.Type.values().length]; + Format[] formatForBuf = new Format[compsForBuf.length]; + + int totalVerts = 0; + int totalTris = 0; + int totalLodLevels = 0; + int maxWeights = -1; + + Mode mode = null; + for (Geometry geom : geometries) { + totalVerts += geom.getVertexCount(); + totalTris += geom.getTriangleCount(); + totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels()); + + Mode listMode; + int components; + switch (geom.getMesh().getMode()) { + case Points: + listMode = Mode.Points; + components = 0; + 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 (VertexBuffer vb : geom.getMesh().getBufferList().getArray()) { + int currentCompsForBuf = compsForBuf[vb.getBufferType().ordinal()]; + if (vb.getBufferType() != Type.Index && currentCompsForBuf != 0 && currentCompsForBuf != vb.getNumComponents()) { + throw new UnsupportedOperationException("The geometry " + geom + " buffer " + vb.getBufferType() + + " has different number of components than the rest of the meshes " + + "(this: " + vb.getNumComponents() + ", expected: " + currentCompsForBuf + ")"); + } + compsForBuf[vb.getBufferType().ordinal()] = vb.getNumComponents(); + formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat(); + } + + maxWeights = Math.max(maxWeights, geom.getMesh().getMaxNumWeights()); + + if (mode != null && mode != listMode) { + throw new UnsupportedOperationException("Cannot combine different" + + " primitive types: " + mode + " != " + listMode); + } + mode = listMode; + compsForBuf[Type.Index.ordinal()] = components; + } + + outMesh.setMaxNumWeights(maxWeights); + outMesh.setMode(mode); + if (totalVerts >= 65536) { + // make sure we create an UnsignedInt buffer so + // we can fit all of the meshes + formatForBuf[Type.Index.ordinal()] = Format.UnsignedInt; + } else { + formatForBuf[Type.Index.ordinal()] = Format.UnsignedShort; + } + + // generate output buffers based on retrieved info + for (int i = 0; i < compsForBuf.length; i++) { + if (compsForBuf[i] == 0) { + continue; + } + + Buffer data; + if (i == Type.Index.ordinal()) { + data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris); + } else { + data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts); + } + + VertexBuffer vb = new VertexBuffer(Type.values()[i]); + vb.setupData(Usage.Static, compsForBuf[i], formatForBuf[i], data); + outMesh.setBuffer(vb); + } + + int globalVertIndex = 0; + int globalTriIndex = 0; + + for (Geometry geom : geometries) { + Mesh inMesh = geom.getMesh(); + geom.computeWorldMatrix(); + Matrix4f worldMatrix = geom.getWorldMatrix(); + + int geomVertCount = inMesh.getVertexCount(); + int geomTriCount = inMesh.getTriangleCount(); + + for (int bufType = 0; bufType < compsForBuf.length; bufType++) { + VertexBuffer inBuf = inMesh.getBuffer(Type.values()[bufType]); + VertexBuffer outBuf = outMesh.getBuffer(Type.values()[bufType]); + + if (inBuf == null || outBuf == null) { + continue; + } + + if (Type.Index.ordinal() == bufType) { + int components = compsForBuf[bufType]; + + IndexBuffer inIdx = inMesh.getIndicesAsList(); + IndexBuffer outIdx = outMesh.getIndexBuffer(); + + for (int tri = 0; tri < geomTriCount; tri++) { + for (int comp = 0; comp < components; comp++) { + int idx = inIdx.get(tri * components + comp) + globalVertIndex; + outIdx.put((globalTriIndex + tri) * components + comp, idx); + } + } + } else if (Type.Position.ordinal() == bufType) { + FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly(); + FloatBuffer outPos = (FloatBuffer) outBuf.getData(); + doTransformVerts(inPos, globalVertIndex, outPos, worldMatrix); + } else if (Type.Normal.ordinal() == bufType) { + FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly(); + FloatBuffer outPos = (FloatBuffer) outBuf.getData(); + doTransformNorms(inPos, globalVertIndex, outPos, worldMatrix); + } else if (Type.Tangent.ordinal() == bufType) { + FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly(); + FloatBuffer outPos = (FloatBuffer) outBuf.getData(); + int components = inBuf.getNumComponents(); + doTransformTangents(inPos, globalVertIndex, components, outPos, worldMatrix); + } else { + inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount); + } + } + + globalVertIndex += geomVertCount; + globalTriIndex += geomTriCount; + } + } + + public static void makeLods(Collection geometries, Mesh outMesh) { + // Determine number of LOD levels required. + int lodLevels = Integer.MAX_VALUE; + for (Geometry g : geometries) { + lodLevels = Math.min(lodLevels, g.getMesh().getNumLodLevels()); + } + if (lodLevels == Integer.MAX_VALUE || lodLevels == 0) { + // No LOD on any of the meshes. + return; + } + + // Sizes of the final LOD index buffers for each level. + int[] lodSize = null; + for (Geometry g : geometries) { + if (lodLevels == 0) { + lodLevels = g.getMesh().getNumLodLevels(); + } + if (lodSize == null) { + lodSize = new int[lodLevels]; + } + for (int i = 0; i < lodLevels; i++) { + lodSize[i] += g.getMesh().getLodLevel(i).getData().limit(); + } + } + + // final LOD buffers for each LOD level. + int[][] lodData = new int[lodLevels][]; + for (int i = 0; i < lodLevels; i++) { + lodData[i] = new int[lodSize[i]]; + } + VertexBuffer[] lods = new VertexBuffer[lodLevels]; + int bufferPos[] = new int[lodLevels]; + int numOfVertices = 0; + + + int[] indexPos = new int[lodLevels]; + for (Geometry g : geometries) { + numOfVertices = g.getVertexCount(); + for (int i = 0; i < lodLevels; i++) { + boolean isShortBuffer = g.getMesh().getLodLevel(i).getFormat() == VertexBuffer.Format.UnsignedShort; + if(isShortBuffer){ + ShortBuffer buffer = (ShortBuffer) g.getMesh().getLodLevel(i).getDataReadOnly(); + for (int j = 0; j < buffer.limit(); j++) { + lodData[i][bufferPos[i]] = (buffer.get()& 0xffff) + indexPos[i]; + bufferPos[i]++; + } + }else{ + IntBuffer buffer = (IntBuffer) g.getMesh().getLodLevel(i).getDataReadOnly(); + for (int j = 0; j < buffer.limit(); j++) { + lodData[i][bufferPos[i]] = buffer.get() + indexPos[i]; + bufferPos[i]++; + } + } + indexPos[i] += numOfVertices; + } + + } + for (int i = 0; i < lodLevels; i++) { + lods[i] = new VertexBuffer(Type.Index); + lods[i].setupData(Usage.Dynamic, 1, Format.UnsignedInt, BufferUtils.createIntBuffer(lodData[i])); + } + outMesh.setLodLevels(lods); + } + + public static List makeBatches(Collection geometries) { + return makeBatches(geometries, false); + } + + /** + * Batches a collection of Geometries so that all with the same material get combined. + * @param geometries The Geometries to combine + * @return A List of newly created Geometries, each with a distinct material + */ + public static List makeBatches(Collection geometries, boolean useLods) { + ArrayList retVal = new ArrayList(); + HashMap> matToGeom = new HashMap>(); + + for (Geometry geom : geometries) { + List outList = matToGeom.get(geom.getMaterial()); + if (outList == null) { + //trying to compare materials with the contentEquals method + for (Material mat : matToGeom.keySet()) { + if (geom.getMaterial().contentEquals(mat)){ + outList = matToGeom.get(mat); + } + } + } + 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); + // lods + if (useLods) { + makeLods(geomsForMat, mesh); + } + mesh.updateCounts(); + + Geometry out = new Geometry("batch[" + (batchNum++) + "]", mesh); + out.setMaterial(mat); + out.updateModelBound(); + retVal.add(out); + } + + return retVal; + } + + public 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); + } + } + + /** + * Optimizes a scene by combining Geometry with the same material. + * All Geometries found in the scene are detached from their parent and + * a new Node containing the optimized Geometries is attached. + * @param scene The scene to optimize + * @return The newly created optimized geometries attached to a node + */ + public static Spatial optimize(Node scene) { + return optimize(scene, false); + } + + /** + * Optimizes a scene by combining Geometry with the same material. + * All Geometries found in the scene are detached from their parent and + * a new Node containing the optimized Geometries is attached. + * @param scene The scene to optimize + * @param useLods true if you want the resulting geometry to keep lod information + * @return The newly created optimized geometries attached to a node + */ + public static Node optimize(Node scene, boolean useLods) { + ArrayList geoms = new ArrayList(); + + gatherGeoms(scene, geoms); + + List batchedGeoms = makeBatches(geoms, useLods); + for (Geometry geom : batchedGeoms) { + scene.attachChild(geom); + } + + for (Iterator it = geoms.iterator(); it.hasNext();) { + Geometry geometry = it.next(); + geometry.removeFromParent(); + } + + // Since the scene is returned unaltered the transform must be reset + scene.setLocalTransform(Transform.IDENTITY); + + return scene; + } + + public static void printMesh(Mesh mesh) { + for (int bufType = 0; bufType < Type.values().length; bufType++) { + VertexBuffer outBuf = mesh.getBuffer(Type.values()[bufType]); + if (outBuf == null) { + continue; + } + + System.out.println(outBuf.getBufferType() + ": "); + for (int vert = 0; vert < outBuf.getNumElements(); vert++) { + String str = "["; + for (int comp = 0; comp < outBuf.getNumComponents(); comp++) { + Object val = outBuf.getElementComponent(vert, comp); + outBuf.setElementComponent(vert, comp, val); + val = outBuf.getElementComponent(vert, comp); + str += val; + if (comp != outBuf.getNumComponents() - 1) { + str += ", "; + } + } + str += "]"; + System.out.println(str); + } + System.out.println("------"); + } + } + + public static void main(String[] args) { + Mesh mesh = new Mesh(); + mesh.setBuffer(Type.Position, 3, new float[]{ + 0, 0, 0, + 1, 0, 0, + 1, 1, 0, + 0, 1, 0 + }); + mesh.setBuffer(Type.Index, 2, new short[]{ + 0, 1, + 1, 2, + 2, 3, + 3, 0 + }); + + Geometry g1 = new Geometry("g1", mesh); + + ArrayList geoms = new ArrayList(); + geoms.add(g1); + + Mesh outMesh = new Mesh(); + mergeGeometries(geoms, outMesh); + printMesh(outMesh); + } +} diff --git a/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java b/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java new file mode 100644 index 000000000..41ee727a9 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java @@ -0,0 +1,1034 @@ +/* + * Copyright (c) 2009-2013 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This class is the java implementation of + * the enhanced version of Ogre engine Lod generator, by Péter Szücs, originally + * based on Stan Melax "easy mesh simplification". The MIT licenced C++ source + * code can be found here + * https://github.com/worldforge/ember/tree/master/src/components/ogre/lod + * The licencing for the original code is : + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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.BoundingSphere; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.util.BufferUtils; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This is an utility class that allows to generated the lod levels for an + * arbitrary mesh. It computes a collapse cost for each vertex and each edges. + * The higher the cost the most likely collapsing the edge or the vertex will + * produce artifacts on the mesh.

      This class is the java implementation of + * the enhanced version of Ogre engine Lod generator, by Péter Szücs, originally + * based on Stan Melax "easy mesh simplification". The MIT licenced C++ source + * code can be found here + * https://github.com/worldforge/ember/tree/master/src/components/ogre/lod more + * informations can be found here http://www.melax.com/polychop + * http://sajty.elementfx.com/progressivemesh/GSoC2012.pdf

      + * + *

      The algorithm sort the vertice according to their collapsse cost + * ascending. It collapse from the "cheapest" vertex to the more expensive.
      + * Usage :
      + *

      + *      LodGenerator lODGenerator = new LodGenerator(geometry);
      + *      lODGenerator.bakeLods(reductionMethod,reductionvalue);
      + * 
      redutionMethod type is VertexReductionMethod described here + * {@link TriangleReductionMethod} reductionvalue depends on the + * reductionMethod

      + * + * + * @author Nehon + */ +public class LodGenerator { + + private static final Logger logger = Logger.getLogger(LodGenerator.class.getName()); + private static final float NEVER_COLLAPSE_COST = Float.MAX_VALUE; + private static final float UNINITIALIZED_COLLAPSE_COST = Float.POSITIVE_INFINITY; + private Vector3f tmpV1 = new Vector3f(); + private Vector3f tmpV2 = new Vector3f(); + private boolean bestQuality = true; + private int indexCount = 0; + private List collapseCostSet = new ArrayList(); + private float collapseCostLimit; + private List triangleList; + private List vertexList = new ArrayList(); + private float meshBoundingSphereRadius; + private Mesh mesh; + + /** + * Describe the way trinagles will be removed.
      PROPORTIONAL : + * Percentage of triangles to be removed from the mesh. Valid range is a + * number between 0.0 and 1.0
      CONSTANT : Triangle count to be removed + * from the mesh. Pass only integers or it will be rounded.
      + * COLLAPSE_COST : Reduces the vertices, until the cost is bigger then the + * given value. Collapse cost is equal to the amount of artifact the + * reduction causes. This generates the best Lod output, but the collapse + * cost depends on implementation. + */ + public enum TriangleReductionMethod { + + /** + * Percentage of triangles to be removed from the mesh. + * + * Valid range is a number between 0.0 and 1.0 + */ + PROPORTIONAL, + /** + * Triangle count to be removed from the mesh. + * + * Pass only integers or it will be rounded. + */ + CONSTANT, + /** + * Reduces the vertices, until the cost is bigger then the given value. + * + * Collapse cost is equal to the amount of artifact the reduction + * causes. This generates the best Lod output, but the collapse cost + * depends on implementation. + */ + COLLAPSE_COST + }; + + private class Edge { + + Vertex destination; + float collapseCost = UNINITIALIZED_COLLAPSE_COST; + int refCount; + + public Edge(Vertex destination) { + this.destination = destination; + } + + public void set(Edge other) { + destination = other.destination; + collapseCost = other.collapseCost; + refCount = other.refCount; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Edge)) { + return false; + } + return destination == ((Edge) obj).destination; + } + + @Override + public int hashCode() { + return destination.hashCode(); + } + + @Override + public String toString() { + return "Edge{" + "collapsTo " + destination.index + '}'; + } + } + + private class Vertex { + + Vector3f position = new Vector3f(); + float collapseCost = UNINITIALIZED_COLLAPSE_COST; + List edges = new ArrayList(); + Set triangles = new HashSet(); + Vertex collapseTo; + boolean isSeam; + int index;//index in the buffer for debugging + + @Override + public String toString() { + return index + " : " + position.toString(); + } + } + + private class Triangle { + + Vertex[] vertex = new Vertex[3]; + Vector3f normal; + boolean isRemoved; + //indices of the vertices in the vertex buffer + int[] vertexId = new int[3]; + + void computeNormal() { + // Cross-product 2 edges + tmpV1.set(vertex[1].position).subtractLocal(vertex[0].position); + tmpV2.set(vertex[2].position).subtractLocal(vertex[1].position); + + normal = tmpV1.cross(tmpV2); + normal.normalizeLocal(); + } + + boolean hasVertex(Vertex v) { + return (v == vertex[0] || v == vertex[1] || v == vertex[2]); + } + + int getVertexIndex(Vertex v) { + for (int i = 0; i < 3; i++) { + if (vertex[i] == v) { + return vertexId[i]; + } + } + throw new IllegalArgumentException("Vertex " + v + "is not part of triangle" + this); + } + + boolean isMalformed() { + return vertex[0] == vertex[1] || vertex[0] == vertex[2] || vertex[1] == vertex[2]; + } + + @Override + public String toString() { + String out = "Triangle{\n"; + for (int i = 0; i < 3; i++) { + out += vertexId[i] + " : " + vertex[i].toString() + "\n"; + } + out += '}'; + return out; + } + } + /** + * Compartator used to sort vertices according to their collapse cost + */ + private Comparator collapseComparator = new Comparator() { + public int compare(Vertex o1, Vertex o2) { + if (Float.compare(o1.collapseCost, o2.collapseCost) == 0) { + return 0; + } + if (o1.collapseCost < o2.collapseCost) { + return -1; + } + return 1; + } + }; + + /** + * Construct a LodGenerator for the given geometry + * + * @param geom the geometry to consider to generate de Lods. + */ + public LodGenerator(Geometry geom) { + mesh = geom.getMesh(); + build(); + } + + private void build() { + BoundingSphere bs = new BoundingSphere(); + bs.computeFromPoints(mesh.getFloatBuffer(VertexBuffer.Type.Position)); + meshBoundingSphereRadius = bs.getRadius(); + List vertexLookup = new ArrayList(); + initialize(); + + gatherVertexData(mesh, vertexLookup); + gatherIndexData(mesh, vertexLookup); + computeCosts(); + // assert (assertValidMesh()); + + } + + private void gatherVertexData(Mesh mesh, List vertexLookup) { + + //in case the model is currently animating with software animation + //attempting to retrieve the bind position instead of the position. + VertexBuffer position = mesh.getBuffer(VertexBuffer.Type.BindPosePosition); + if (position == null) { + position = mesh.getBuffer(VertexBuffer.Type.Position); + } + FloatBuffer pos = (FloatBuffer) position.getDataReadOnly(); + pos.rewind(); + + while (pos.remaining() != 0) { + Vertex v = new Vertex(); + v.position.setX(pos.get()); + v.position.setY(pos.get()); + v.position.setZ(pos.get()); + v.isSeam = false; + Vertex existingV = findSimilar(v); + if (existingV != null) { + //vertex position already exists + existingV.isSeam = true; + v.isSeam = true; + } else { + vertexList.add(v); + } + vertexLookup.add(v); + } + pos.rewind(); + } + + private Vertex findSimilar(Vertex v) { + for (Vertex vertex : vertexList) { + if (vertex.position.equals(v.position)) { + return vertex; + } + } + return null; + } + + private void gatherIndexData(Mesh mesh, List vertexLookup) { + VertexBuffer indexBuffer = mesh.getBuffer(VertexBuffer.Type.Index); + indexCount = indexBuffer.getNumElements() * 3; + Buffer b = indexBuffer.getDataReadOnly(); + b.rewind(); + + while (b.remaining() != 0) { + Triangle tri = new Triangle(); + tri.isRemoved = false; + triangleList.add(tri); + for (int i = 0; i < 3; i++) { + if (b instanceof IntBuffer) { + tri.vertexId[i] = ((IntBuffer) b).get(); + } else { + //bit shift to avoid negative values due to conversion form short to int. + //we need an unsigned int here. + tri.vertexId[i] = ((ShortBuffer) b).get()& 0xffff; + } + // assert (tri.vertexId[i] < vertexLookup.size()); + tri.vertex[i] = vertexLookup.get(tri.vertexId[i]); + //debug only; + tri.vertex[i].index = tri.vertexId[i]; + } + if (tri.isMalformed()) { + if (!tri.isRemoved) { + logger.log(Level.FINE, "malformed triangle found with ID:{0}\n{1} It will be excluded from Lod level calculations.", new Object[]{triangleList.indexOf(tri), tri.toString()}); + tri.isRemoved = true; + indexCount -= 3; + } + + } else { + tri.computeNormal(); + addTriangleToEdges(tri); + } + } + b.rewind(); + } + + private void computeCosts() { + collapseCostSet.clear(); + + for (Vertex vertex : vertexList) { + + if (!vertex.edges.isEmpty()) { + computeVertexCollapseCost(vertex); + } else { + logger.log(Level.FINE, "Found isolated vertex {0} It will be excluded from Lod level calculations.", vertex); + } + } +// assert (vertexList.size() == collapseCostSet.size()); +// assert (checkCosts()); + } + + //Debug only + private boolean checkCosts() { + for (Vertex vertex : vertexList) { + boolean test = find(collapseCostSet, vertex); + if (!test) { + System.out.println("vertex " + vertex.index + " not present in collapse costs"); + return false; + } + } + return true; + } + + private void computeVertexCollapseCost(Vertex vertex) { + + vertex.collapseCost = UNINITIALIZED_COLLAPSE_COST; + // assert (!vertex.edges.isEmpty()); + for (Edge edge : vertex.edges) { + edge.collapseCost = computeEdgeCollapseCost(vertex, edge); + // assert (edge.collapseCost != UNINITIALIZED_COLLAPSE_COST); + if (vertex.collapseCost > edge.collapseCost) { + vertex.collapseCost = edge.collapseCost; + vertex.collapseTo = edge.destination; + } + } + // assert (vertex.collapseCost != UNINITIALIZED_COLLAPSE_COST); + collapseCostSet.add(vertex); + } + + float computeEdgeCollapseCost(Vertex src, Edge dstEdge) { + // This is based on Ogre's collapse cost calculation algorithm. + + Vertex dest = dstEdge.destination; + + // Check for singular triangle destruction + // If src and dest both only have 1 triangle (and it must be a shared one) + // then this would destroy the shape, so don't do this + if (src.triangles.size() == 1 && dest.triangles.size() == 1) { + return NEVER_COLLAPSE_COST; + } + + // Degenerate case check + // Are we going to invert a face normal of one of the neighbouring faces? + // Can occur when we have a very small remaining edge and collapse crosses it + // Look for a face normal changing by > 90 degrees + for (Triangle triangle : src.triangles) { + // Ignore the deleted faces (those including src & dest) + if (!triangle.hasVertex(dest)) { + // Test the new face normal + Vertex pv0, pv1, pv2; + + // Replace src with dest wherever it is + pv0 = (triangle.vertex[0] == src) ? dest : triangle.vertex[0]; + pv1 = (triangle.vertex[1] == src) ? dest : triangle.vertex[1]; + pv2 = (triangle.vertex[2] == src) ? dest : triangle.vertex[2]; + + // Cross-product 2 edges + tmpV1.set(pv1.position).subtractLocal(pv0.position); + tmpV2.set(pv2.position).subtractLocal(pv1.position); + + //computing the normal + Vector3f newNormal = tmpV1.crossLocal(tmpV2); + newNormal.normalizeLocal(); + + // Dot old and new face normal + // If < 0 then more than 90 degree difference + if (newNormal.dot(triangle.normal) < 0.0f) { + // Don't do it! + return NEVER_COLLAPSE_COST; + } + } + } + + float cost; + + // Special cases + // If we're looking at a border vertex + if (isBorderVertex(src)) { + if (dstEdge.refCount > 1) { + // src is on a border, but the src-dest edge has more than one tri on it + // So it must be collapsing inwards + // Mark as very high-value cost + // curvature = 1.0f; + cost = 1.0f; + } else { + // Collapsing ALONG a border + // We can't use curvature to measure the effect on the model + // Instead, see what effect it has on 'pulling' the other border edges + // The more colinear, the less effect it will have + // So measure the 'kinkiness' (for want of a better term) + + // Find the only triangle using this edge. + // PMTriangle* triangle = findSideTriangle(src, dst); + + cost = 0.0f; + Vector3f collapseEdge = tmpV1.set(src.position).subtractLocal(dest.position); + collapseEdge.normalizeLocal(); + + for (Edge edge : src.edges) { + + Vertex neighbor = edge.destination; + //reference check intended + if (neighbor != dest && edge.refCount == 1) { + Vector3f otherBorderEdge = tmpV2.set(src.position).subtractLocal(neighbor.position); + otherBorderEdge.normalizeLocal(); + // This time, the nearer the dot is to -1, the better, because that means + // the edges are opposite each other, therefore less kinkiness + // Scale into [0..1] + float kinkiness = (otherBorderEdge.dot(collapseEdge) + 1.002f) * 0.5f; + cost = Math.max(cost, kinkiness); + } + } + } + } else { // not a border + + // Standard inner vertex + // Calculate curvature + // use the triangle facing most away from the sides + // to determine our curvature term + // Iterate over src's faces again + cost = 0.001f; + + for (Triangle triangle : src.triangles) { + float mincurv = 1.0f; // curve for face i and closer side to it + + for (Triangle triangle2 : src.triangles) { + if (triangle2.hasVertex(dest)) { + + // Dot product of face normal gives a good delta angle + float dotprod = triangle.normal.dot(triangle2.normal); + // NB we do (1-..) to invert curvature where 1 is high curvature [0..1] + // Whilst dot product is high when angle difference is low + mincurv = Math.min(mincurv, (1.002f - dotprod) * 0.5f); + } + } + cost = Math.max(cost, mincurv); + } + } + + // check for texture seam ripping + if (src.isSeam) { + if (!dest.isSeam) { + cost += meshBoundingSphereRadius; + } else { + cost += meshBoundingSphereRadius * 0.5; + } + } + + // assert (cost >= 0); + + return cost * src.position.distanceSquared(dest.position); + } + int nbCollapsedTri = 0; + + /** + * Computes the lod and return a list of VertexBuffers that can then be used + * for lod (use Mesg.setLodLevels(VertexBuffer[]))
      + * + * This method must be fed with the reduction method + * {@link TriangleReductionMethod} and a list of reduction values.
      for + * each value a lod will be generated.
      The resulting array will always + * contain at index 0 the original index buffer of the mesh.

      + * Important note : some meshes cannot be decimated, so the + * result of this method can varry depending of the given mesh. Also the + * reduction values are indicative and the produces mesh will not always + * meet the required reduction. + * + * @param reductionMethod the reduction method to use + * @param reductionValues the reduction value to use for each lod level. + * @return an array of VertexBuffers containing the different index buffers + * representing the lod levels. + */ + public VertexBuffer[] computeLods(TriangleReductionMethod reductionMethod, float... reductionValues) { + int tricount = triangleList.size(); + int lastBakeVertexCount = tricount; + int lodCount = reductionValues.length; + VertexBuffer[] lods = new VertexBuffer[lodCount + 1]; + int numBakedLods = 1; + lods[0] = mesh.getBuffer(VertexBuffer.Type.Index); + for (int curLod = 0; curLod < lodCount; curLod++) { + int neededTriCount = calcLodTriCount(reductionMethod, reductionValues[curLod]); + while (neededTriCount < tricount) { + Collections.sort(collapseCostSet, collapseComparator); + Iterator it = collapseCostSet.iterator(); + + if (it.hasNext()) { + Vertex v = it.next(); + if (v.collapseCost < collapseCostLimit) { + if (!collapse(v)) { + logger.log(Level.FINE, "Couldn''t collapse vertex{0}", v.index); + } + Iterator it2 = collapseCostSet.iterator(); + if (it2.hasNext()) { + it2.next(); + it2.remove();// Remove src from collapse costs. + } + + } else { + break; + } + } else { + break; + } + tricount = triangleList.size() - nbCollapsedTri; + } + logger.log(Level.FINE, "collapsed {0} tris", nbCollapsedTri); + boolean outSkipped = (lastBakeVertexCount == tricount); + if (!outSkipped) { + lastBakeVertexCount = tricount; + lods[curLod + 1] = makeLod(mesh); + numBakedLods++; + } + } + if (numBakedLods <= lodCount) { + VertexBuffer[] bakedLods = new VertexBuffer[numBakedLods]; + System.arraycopy(lods, 0, bakedLods, 0, numBakedLods); + return bakedLods; + } else { + return lods; + } + } + + /** + * Computes the lods and bake them into the mesh
      + * + * This method must be fed with the reduction method + * {@link TriangleReductionMethod} and a list of reduction values.
      for + * each value a lod will be generated.

      Important note : + * some meshes cannot be decimated, so the result of this method can varry + * depending of the given mesh. Also the reduction values are indicative and + * the produces mesh will not always meet the required reduction. + * + * @param reductionMethod the reduction method to use + * @param reductionValues the reduction value to use for each lod level. + */ + public void bakeLods(TriangleReductionMethod reductionMethod, float... reductionValues) { + mesh.setLodLevels(computeLods(reductionMethod, reductionValues)); + } + + private VertexBuffer makeLod(Mesh mesh) { + VertexBuffer indexBuffer = mesh.getBuffer(VertexBuffer.Type.Index); + + boolean isShortBuffer = indexBuffer.getFormat() == VertexBuffer.Format.UnsignedShort; + // Create buffers. + VertexBuffer lodBuffer = new VertexBuffer(VertexBuffer.Type.Index); + int bufsize = indexCount == 0 ? 3 : indexCount; + + if (isShortBuffer) { + lodBuffer.setupData(VertexBuffer.Usage.Static, 3, VertexBuffer.Format.UnsignedShort, BufferUtils.createShortBuffer(bufsize)); + } else { + lodBuffer.setupData(VertexBuffer.Usage.Static, 3, VertexBuffer.Format.UnsignedInt, BufferUtils.createIntBuffer(bufsize)); + } + + + + lodBuffer.getData().rewind(); + //Check if we should fill it with a "dummy" triangle. + if (indexCount == 0) { + if (isShortBuffer) { + for (int m = 0; m < 3; m++) { + ((ShortBuffer) lodBuffer.getData()).put((short) 0); + } + } else { + for (int m = 0; m < 3; m++) { + ((IntBuffer) lodBuffer.getData()).put(0); + } + } + } + + // Fill buffers. + Buffer buf = lodBuffer.getData(); + buf.rewind(); + for (Triangle triangle : triangleList) { + if (!triangle.isRemoved) { + // assert (indexCount != 0); + if (isShortBuffer) { + for (int m = 0; m < 3; m++) { + ((ShortBuffer) buf).put((short) triangle.vertexId[m]); + + } + } else { + for (int m = 0; m < 3; m++) { + ((IntBuffer) buf).put(triangle.vertexId[m]); + } + + } + } + } + buf.clear(); + lodBuffer.updateData(buf); + return lodBuffer; + } + + private int calcLodTriCount(TriangleReductionMethod reductionMethod, float reductionValue) { + int nbTris = mesh.getTriangleCount(); + switch (reductionMethod) { + case PROPORTIONAL: + collapseCostLimit = NEVER_COLLAPSE_COST; + return (int) (nbTris - (nbTris * (reductionValue))); + + case CONSTANT: + collapseCostLimit = NEVER_COLLAPSE_COST; + if (reductionValue < nbTris) { + return nbTris - (int) reductionValue; + } + return 0; + + case COLLAPSE_COST: + collapseCostLimit = reductionValue; + return 0; + + default: + return nbTris; + } + } + + private int findDstID(int srcId, List tmpCollapsedEdges) { + int i = 0; + for (CollapsedEdge collapsedEdge : tmpCollapsedEdges) { + if (collapsedEdge.srcID == srcId) { + return i; + } + i++; + } + return Integer.MAX_VALUE; + } + + private class CollapsedEdge { + + int srcID; + int dstID; + }; + + private void removeTriangleFromEdges(Triangle triangle, Vertex skip) { + // skip is needed if we are iterating on the vertex's edges or triangles. + for (int i = 0; i < 3; i++) { + if (triangle.vertex[i] != skip) { + triangle.vertex[i].triangles.remove(triangle); + } + } + for (int i = 0; i < 3; i++) { + for (int n = 0; n < 3; n++) { + if (i != n) { + removeEdge(triangle.vertex[i], new Edge(triangle.vertex[n])); + } + } + } + } + + private void removeEdge(Vertex v, Edge edge) { + Edge ed = null; + for (Edge edge1 : v.edges) { + if (edge1.equals(edge)) { + ed = edge1; + break; + } + } + + if (ed.refCount == 1) { + v.edges.remove(ed); + } else { + ed.refCount--; + } + + } + + boolean isBorderVertex(Vertex vertex) { + for (Edge edge : vertex.edges) { + if (edge.refCount == 1) { + return true; + } + } + return false; + } + + private void addTriangleToEdges(Triangle tri) { + if (bestQuality) { + Triangle duplicate = getDuplicate(tri); + if (duplicate != null) { + if (!tri.isRemoved) { + tri.isRemoved = true; + indexCount -= 3; + logger.log(Level.FINE, "duplicate triangle found{0}{1} It will be excluded from Lod level calculations.", new Object[]{tri, duplicate}); + } + } + } + for (int i = 0; i < 3; i++) { + tri.vertex[i].triangles.add(tri); + } + for (int i = 0; i < 3; i++) { + for (int n = 0; n < 3; n++) { + if (i != n) { + addEdge(tri.vertex[i], new Edge(tri.vertex[n])); + } + } + } + } + + private void addEdge(Vertex v, Edge edge) { + // assert (edge.destination != v); + + for (Edge ed : v.edges) { + if (ed.equals(edge)) { + ed.refCount++; + return; + } + } + + v.edges.add(edge); + edge.refCount = 1; + + } + + private void initialize() { + triangleList = new ArrayList(); + } + + private Triangle getDuplicate(Triangle triangle) { + // duplicate triangle detection (where all vertices has the same position) + for (Triangle tri : triangle.vertex[0].triangles) { + if (isDuplicateTriangle(triangle, tri)) { + return tri; + } + } + return null; + } + + private boolean isDuplicateTriangle(Triangle triangle, Triangle triangle2) { + for (int i = 0; i < 3; i++) { + if (triangle.vertex[i] != triangle2.vertex[0] + || triangle.vertex[i] != triangle2.vertex[1] + || triangle.vertex[i] != triangle2.vertex[2]) { + return false; + } + } + return true; + } + + private void replaceVertexID(Triangle triangle, int oldID, int newID, Vertex dst) { + dst.triangles.add(triangle); + // NOTE: triangle is not removed from src. This is implementation specific optimization. + + // Its up to the compiler to unroll everything. + for (int i = 0; i < 3; i++) { + if (triangle.vertexId[i] == oldID) { + for (int n = 0; n < 3; n++) { + if (i != n) { + // This is implementation specific optimization to remove following line. + //removeEdge(triangle.vertex[i], new Edge(triangle.vertex[n])); + + removeEdge(triangle.vertex[n], new Edge(triangle.vertex[i])); + addEdge(triangle.vertex[n], new Edge(dst)); + addEdge(dst, new Edge(triangle.vertex[n])); + } + } + triangle.vertex[i] = dst; + triangle.vertexId[i] = newID; + return; + } + } + // assert (false); + } + + private void updateVertexCollapseCost(Vertex vertex) { + float collapseCost = UNINITIALIZED_COLLAPSE_COST; + Vertex collapseTo = null; + + for (Edge edge : vertex.edges) { + edge.collapseCost = computeEdgeCollapseCost(vertex, edge); + // assert (edge.collapseCost != UNINITIALIZED_COLLAPSE_COST); + if (collapseCost > edge.collapseCost) { + collapseCost = edge.collapseCost; + collapseTo = edge.destination; + } + } + if (collapseCost != vertex.collapseCost || vertex.collapseTo != collapseTo) { +// assert (vertex.collapseTo != null); +// assert (find(collapseCostSet, vertex)); + collapseCostSet.remove(vertex); + if (collapseCost != UNINITIALIZED_COLLAPSE_COST) { + vertex.collapseCost = collapseCost; + vertex.collapseTo = collapseTo; + collapseCostSet.add(vertex); + } + } + // assert (vertex.collapseCost != UNINITIALIZED_COLLAPSE_COST); + } + + private boolean hasSrcID(int srcID, List cEdges) { + // This will only return exact matches. + for (CollapsedEdge collapsedEdge : cEdges) { + if (collapsedEdge.srcID == srcID) { + return true; + } + } + + return false; // Not found + } + + private boolean collapse(Vertex src) { + Vertex dest = src.collapseTo; + if (src.edges.isEmpty()) { + return false; + } +// assert (assertValidVertex(dest)); +// assert (assertValidVertex(src)); + +// assert (src.collapseCost != NEVER_COLLAPSE_COST); +// assert (src.collapseCost != UNINITIALIZED_COLLAPSE_COST); +// assert (!src.edges.isEmpty()); +// assert (!src.triangles.isEmpty()); +// assert (src.edges.contains(new Edge(dest))); + + // It may have vertexIDs and triangles from different submeshes(different vertex buffers), + // so we need to connect them correctly based on deleted triangle's edge. + // mCollapsedEdgeIDs will be used, when looking up the connections for replacement. + List tmpCollapsedEdges = new ArrayList(); + for (Iterator it = src.triangles.iterator(); it.hasNext();) { + Triangle triangle = it.next(); + if (triangle.hasVertex(dest)) { + // Remove a triangle + // Tasks: + // 1. Add it to the collapsed edges list. + // 2. Reduce index count for the Lods, which will not have this triangle. + // 3. Mark as removed, so it will not be added in upcoming Lod levels. + // 4. Remove references/pointers to this triangle. + + // 1. task + int srcID = triangle.getVertexIndex(src); + if (!hasSrcID(srcID, tmpCollapsedEdges)) { + CollapsedEdge cEdge = new CollapsedEdge(); + cEdge.srcID = srcID; + cEdge.dstID = triangle.getVertexIndex(dest); + tmpCollapsedEdges.add(cEdge); + } + + // 2. task + indexCount -= 3; + + // 3. task + triangle.isRemoved = true; + nbCollapsedTri++; + + // 4. task + removeTriangleFromEdges(triangle, src); + it.remove(); + + } + } +// assert (!tmpCollapsedEdges.isEmpty()); +// assert (!dest.edges.contains(new Edge(src))); + + + for (Iterator it = src.triangles.iterator(); it.hasNext();) { + Triangle triangle = it.next(); + if (!triangle.hasVertex(dest)) { + // Replace a triangle + // Tasks: + // 1. Determine the edge which we will move along. (we need to modify single vertex only) + // 2. Move along the selected edge. + + // 1. task + int srcID = triangle.getVertexIndex(src); + int id = findDstID(srcID, tmpCollapsedEdges); + if (id == Integer.MAX_VALUE) { + // Not found any edge to move along. + // Destroy the triangle. + // if (!triangle.isRemoved) { + triangle.isRemoved = true; + indexCount -= 3; + removeTriangleFromEdges(triangle, src); + it.remove(); + nbCollapsedTri++; + continue; + } + int dstID = tmpCollapsedEdges.get(id).dstID; + + // 2. task + replaceVertexID(triangle, srcID, dstID, dest); + + + if (bestQuality) { + triangle.computeNormal(); + } + + } + } + + if (bestQuality) { + for (Edge edge : src.edges) { + updateVertexCollapseCost(edge.destination); + } + updateVertexCollapseCost(dest); + for (Edge edge : dest.edges) { + updateVertexCollapseCost(edge.destination); + } + + } else { + // TODO: Find out why is this needed. assertOutdatedCollapseCost() fails on some + // rare situations without this. For example goblin.mesh fails. + //Treeset to have an ordered list with unique values + SortedSet updatable = new TreeSet(collapseComparator); + + for (Edge edge : src.edges) { + updatable.add(edge.destination); + for (Edge edge1 : edge.destination.edges) { + updatable.add(edge1.destination); + } + } + + + for (Vertex vertex : updatable) { + updateVertexCollapseCost(vertex); + } + + } + return true; + } + + private boolean assertValidMesh() { + // Allows to find bugs in collapsing. + for (Vertex vertex : collapseCostSet) { + assertValidVertex(vertex); + } + return true; + + } + + private boolean assertValidVertex(Vertex v) { + // Allows to find bugs in collapsing. + // System.out.println("Asserting " + v.index); + for (Triangle t : v.triangles) { + for (int i = 0; i < 3; i++) { + // System.out.println("check " + t.vertex[i].index); + + //assert (collapseCostSet.contains(t.vertex[i])); + assert (find(collapseCostSet, t.vertex[i])); + + assert (t.vertex[i].edges.contains(new Edge(t.vertex[i].collapseTo))); + for (int n = 0; n < 3; n++) { + if (i != n) { + + int id = t.vertex[i].edges.indexOf(new Edge(t.vertex[n])); + Edge ed = t.vertex[i].edges.get(id); + //assert (ed.collapseCost != UNINITIALIZED_COLLAPSE_COST); + } else { + assert (!t.vertex[i].edges.contains(new Edge(t.vertex[n]))); + } + } + } + } + return true; + } + + private boolean find(List set, Vertex v) { + for (Vertex vertex : set) { + if (v == vertex) { + return true; + } + } + return false; + } +} diff --git a/jme3-core/src/tools/java/jme3tools/optimize/OCTTriangle.java b/jme3-core/src/tools/java/jme3tools/optimize/OCTTriangle.java new file mode 100644 index 000000000..e3daeeba6 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/optimize/OCTTriangle.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.optimize; + +import com.jme3.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/jme3-core/src/tools/java/jme3tools/optimize/Octnode.java b/jme3-core/src/tools/java/jme3tools/optimize/Octnode.java new file mode 100644 index 000000000..147e1c737 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/optimize/Octnode.java @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.optimize; + +import com.jme3.bounding.BoundingBox; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.material.Material; +import com.jme3.math.Matrix4f; +import com.jme3.math.Ray; +import com.jme3.math.Triangle; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.Geometry; +import com.jme3.scene.debug.WireBox; +import java.util.*; + +public class Octnode { + + static final Vector3f[] extentMult = new Vector3f[] + { + new Vector3f( 1, 1, 1), // right top forw + new Vector3f(-1, 1, 1), // left top forw + new Vector3f( 1,-1, 1), // right bot forw + new Vector3f(-1,-1, 1), // left bot forw + new Vector3f( 1, 1,-1), // right top back + new Vector3f(-1, 1,-1), // left top back + new Vector3f( 1,-1,-1), // right bot back + new Vector3f(-1,-1,-1) // left bot back + }; + + final BoundingBox bbox; + final ArrayList 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 maxDepth, float maxVolume, int minTrisPerNode){ + if (tris == null || depth > maxDepth || bbox.getVolume() < maxVolume || 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, maxDepth, maxVolume, minTrisPerNode); + } + } + } + + public void subdivide(int maxDepth, float maxVolume, int minTrisPerNode){ + subdivide(0, maxDepth, maxVolume, 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/jme3-core/src/tools/java/jme3tools/optimize/Octree.java b/jme3-core/src/tools/java/jme3tools/optimize/Octree.java new file mode 100644 index 000000000..62ec9ef92 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/optimize/Octree.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package 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 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){ + 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); + + 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 void construct(int maxDepth, float maxVolume, int minTrisPerNode){ + root = new Octnode(bbox, allTris); + root.subdivide(maxDepth, maxVolume, 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/jme3-core/src/tools/java/jme3tools/optimize/TestCollector.java b/jme3-core/src/tools/java/jme3tools/optimize/TestCollector.java new file mode 100644 index 000000000..fc77e9c63 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/optimize/TestCollector.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.optimize; + +import com.jme3.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/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java b/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java new file mode 100644 index 000000000..c7a032414 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java @@ -0,0 +1,686 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.optimize; + +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetManager; +import com.jme3.material.MatParamTexture; +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; +import java.lang.reflect.InvocationTargetException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * TextureAtlas allows combining multiple textures to one texture atlas. + * + *

      After the TextureAtlas has been created with a certain size, textures can be added for + * freely chosen "map names". The textures are automatically placed on the atlas map and the + * image data is stored in a byte array for each map name. Later each map can be retrieved as + * a Texture to be used further in materials.

      + * + *

      The first map name used is the "master map" that defines new locations on the atlas. Secondary + * textures (other map names) have to reference a texture of the master map to position the texture + * on the secondary map. This is necessary as the maps share texture coordinates and thus need to be + * placed at the same location on both maps.

      + * + *

      The helper methods that work with Geometry objects handle the DiffuseMap or ColorMap as the master map and + * additionally handle NormalMap and SpecularMap as secondary maps.

      + * + *

      The textures are referenced by their asset key name and for each texture the location + * inside the atlas is stored. A texture with an existing key name is never added more than once + * to the atlas. You can access the information for each texture or geometry texture via helper methods.

      + * + *

      The TextureAtlas also allows you to change the texture coordinates of a mesh or geometry + * to point at the new locations of its texture inside the atlas (if the texture exists inside the atlas).

      + * + *

      Note that models that use texture coordinates outside the 0-1 range (repeating/wrapping textures) + * will not work correctly as their new coordinates leak into other parts of the atlas and thus display + * other textures instead of repeating the texture.

      + * + *

      Also note that textures are not scaled and the atlas needs to be large enough to hold all textures. + * All methods that allow adding textures return false if the texture could not be added due to the + * atlas being full. Furthermore secondary textures (normal, spcular maps etc.) have to be the same size + * as the main (e.g. DiffuseMap) texture.

      + * + *

      Usage examples

      + * Create one geometry out of several geometries that are loaded from a j3o file: + *
      + * Node scene = assetManager.loadModel("Scenes/MyScene.j3o");
      + * Geometry geom = TextureAtlas.makeAtlasBatch(scene);
      + * rootNode.attachChild(geom);
      + * 
      + * Create a texture atlas and change the texture coordinates of one geometry: + *
      + * Node scene = assetManager.loadModel("Scenes/MyScene.j3o");
      + * //either auto-create from node:
      + * TextureAtlas atlas = TextureAtlas.createAtlas(scene);
      + * //or create manually by adding textures or geometries with textures
      + * TextureAtlas atlas = new TextureAtlas(1024,1024);
      + * atlas.addTexture(myTexture, "DiffuseMap");
      + * atlas.addGeometry(myGeometry);
      + * //create material and set texture
      + * Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md");
      + * mat.setTexture("DiffuseMap", atlas.getAtlasTexture("DiffuseMap"));
      + * //change one geometry to use atlas, apply texture coordinates and replace material.
      + * Geometry geom = scene.getChild("MyGeometry");
      + * atlas.applyCoords(geom);
      + * geom.setMaterial(mat);
      + * 
      + * + * @author normenhansen, Lukasz Bruun - lukasz.dk + */ +public class TextureAtlas { + + private static final Logger logger = Logger.getLogger(TextureAtlas.class.getName()); + private Map images; + private int atlasWidth, atlasHeight; + private Format format = Format.ABGR8; + private Node root; + private Map locationMap; + private Map mapNameMap; + private String rootMapName; + + public TextureAtlas(int width, int height) { + this.atlasWidth = width; + this.atlasHeight = height; + root = new Node(0, 0, width, height); + locationMap = new TreeMap(); + mapNameMap = new HashMap(); + } + + /** + * Add a geometries DiffuseMap (or ColorMap), NormalMap and SpecularMap to the atlas. + * @param geometry + * @return false if the atlas is full. + */ + public boolean addGeometry(Geometry geometry) { + Texture diffuse = getMaterialTexture(geometry, "DiffuseMap"); + Texture normal = getMaterialTexture(geometry, "NormalMap"); + Texture specular = getMaterialTexture(geometry, "SpecularMap"); + if (diffuse == null) { + diffuse = getMaterialTexture(geometry, "ColorMap"); + + } + if (diffuse != null && diffuse.getKey() != null) { + String keyName = diffuse.getKey().toString(); + if (!addTexture(diffuse, "DiffuseMap")) { + return false; + } else { + if (normal != null && normal.getKey() != null) { + addTexture(diffuse, "NormalMap", keyName); + } + if (specular != null && specular.getKey() != null) { + addTexture(specular, "SpecularMap", keyName); + } + } + return true; + } + return true; + } + + /** + * Add a texture for a specific map name + * @param texture A texture to add to the atlas. + * @param mapName A freely chosen map name that can be later retrieved as a Texture. The first map name supplied will be the master map. + * @return false if the atlas is full. + */ + public boolean addTexture(Texture texture, String mapName) { + if (texture == null) { + throw new IllegalStateException("Texture cannot be null!"); + } + String name = textureName(texture); + if (texture.getImage() != null && name != null) { + return addImage(texture.getImage(), name, mapName, null); + } else { + throw new IllegalStateException("Texture has no asset key name!"); + } + } + + /** + * Add a texture for a specific map name at the location of another existing texture on the master map. + * @param texture A texture to add to the atlas. + * @param mapName A freely chosen map name that can be later retrieved as a Texture. + * @param masterTexture The master texture for determining the location, it has to exist in tha master map. + */ + public void addTexture(Texture texture, String mapName, Texture masterTexture) { + String sourceTextureName = textureName(masterTexture); + if (sourceTextureName == null) { + throw new IllegalStateException("Supplied master map texture has no asset key name!"); + } else { + addTexture(texture, mapName, sourceTextureName); + } + } + + /** + * Add a texture for a specific map name at the location of another existing texture (on the master map). + * @param texture A texture to add to the atlas. + * @param mapName A freely chosen map name that can be later retrieved as a Texture. + * @param sourceTextureName Name of the master map used for the location. + */ + public void addTexture(Texture texture, String mapName, String sourceTextureName) { + if (texture == null) { + throw new IllegalStateException("Texture cannot be null!"); + } + String name = textureName(texture); + if (texture.getImage() != null && name != null) { + addImage(texture.getImage(), name, mapName, sourceTextureName); + } else { + throw new IllegalStateException("Texture has no asset key name!"); + } + } + + private String textureName(Texture texture) { + if (texture == null) { + return null; + } + AssetKey key = texture.getKey(); + if (key != null) { + return key.toString(); + } else { + return null; + } + } + + private boolean addImage(Image image, String name, String mapName, String sourceTextureName) { + if (rootMapName == null) { + rootMapName = mapName; + } + if (sourceTextureName == null && !rootMapName.equals(mapName)) { + throw new IllegalStateException("Atlas already has a master map called " + rootMapName + "." + + " Textures for new maps have to use a texture from the master map for their location."); + } + TextureAtlasTile location = locationMap.get(name); + if (location != null) { + //have location for texture + if (!mapName.equals(mapNameMap.get(name))) { + logger.log(Level.WARNING, "Same texture " + name + " is used in different maps! (" + mapName + " and " + mapNameMap.get(name) + "). Location will be based on location in " + mapNameMap.get(name) + "!"); + drawImage(image, location.getX(), location.getY(), mapName); + return true; + } else { + return true; + } + } else if (sourceTextureName == null) { + //need to make new tile + Node node = root.insert(image); + if (node == null) { + return false; + } + location = node.location; + } else { + //got old tile to align to + location = locationMap.get(sourceTextureName); + if (location == null) { + throw new IllegalStateException("Cannot find master map texture for " + name + "."); + } else if (location.width != image.getWidth() || location.height != image.getHeight()) { + throw new IllegalStateException(mapName + " " + name + " does not fit " + rootMapName + " tile size. Make sure all textures (diffuse, normal, specular) for one model are the same size."); + } + } + mapNameMap.put(name, mapName); + locationMap.put(name, location); + drawImage(image, location.getX(), location.getY(), mapName); + return true; + } + + private void drawImage(Image source, int x, int y, String mapName) { + if (images == null) { + images = new HashMap(); + } + byte[] image = images.get(mapName); + if (image == null) { + image = new byte[atlasWidth * atlasHeight * 4]; + images.put(mapName, image); + } + //TODO: all buffers? + ByteBuffer sourceData = source.getData(0); + int height = source.getHeight(); + int width = source.getWidth(); + Image newImage = null; + for (int yPos = 0; yPos < height; yPos++) { + for (int xPos = 0; xPos < width; xPos++) { + int i = ((xPos + x) + (yPos + y) * atlasWidth) * 4; + if (source.getFormat() == Format.ABGR8) { + int j = (xPos + yPos * width) * 4; + image[i] = sourceData.get(j); //a + image[i + 1] = sourceData.get(j + 1); //b + image[i + 2] = sourceData.get(j + 2); //g + image[i + 3] = sourceData.get(j + 3); //r + } else if (source.getFormat() == Format.BGR8) { + int j = (xPos + yPos * width) * 3; + image[i] = 1; //a + image[i + 1] = sourceData.get(j); //b + image[i + 2] = sourceData.get(j + 1); //g + image[i + 3] = sourceData.get(j + 2); //r + } else if (source.getFormat() == Format.RGB8) { + int j = (xPos + yPos * width) * 3; + image[i] = 1; //a + image[i + 1] = sourceData.get(j + 2); //b + image[i + 2] = sourceData.get(j + 1); //g + image[i + 3] = sourceData.get(j); //r + } else if (source.getFormat() == Format.RGBA8) { + int j = (xPos + yPos * width) * 4; + image[i] = sourceData.get(j + 3); //a + image[i + 1] = sourceData.get(j + 2); //b + image[i + 2] = sourceData.get(j + 1); //g + image[i + 3] = sourceData.get(j); //r + } else if (source.getFormat() == Format.Luminance8) { + int j = (xPos + yPos * width) * 1; + image[i] = 1; //a + image[i + 1] = sourceData.get(j); //b + image[i + 2] = sourceData.get(j); //g + image[i + 3] = sourceData.get(j); //r + } else if (source.getFormat() == Format.Luminance8Alpha8) { + int j = (xPos + yPos * width) * 2; + image[i] = sourceData.get(j + 1); //a + image[i + 1] = sourceData.get(j); //b + image[i + 2] = sourceData.get(j); //g + image[i + 3] = sourceData.get(j); //r + } else { + //ImageToAwt conversion + if (newImage == null) { + newImage = convertImageToAwt(source); + if (newImage != null) { + source = newImage; + sourceData = source.getData(0); + int j = (xPos + yPos * width) * 4; + image[i] = sourceData.get(j); //a + image[i + 1] = sourceData.get(j + 1); //b + image[i + 2] = sourceData.get(j + 2); //g + image[i + 3] = sourceData.get(j + 3); //r + }else{ + throw new UnsupportedOperationException("Cannot draw or convert textures with format " + source.getFormat()); + } + } else { + throw new UnsupportedOperationException("Cannot draw textures with format " + source.getFormat()); + } + } + } + } + } + + private Image convertImageToAwt(Image source) { + //use awt dependent classes without actual dependency via reflection + try { + Class clazz = Class.forName("jme3tools.converters.ImageToAwt"); + if (clazz == null) { + return null; + } + Image newImage = new Image(format, source.getWidth(), source.getHeight(), BufferUtils.createByteBuffer(source.getWidth() * source.getHeight() * 4)); + clazz.getMethod("convert", Image.class, Image.class).invoke(clazz.newInstance(), source, newImage); + return newImage; + } catch (InstantiationException ex) { + } catch (IllegalAccessException ex) { + } catch (IllegalArgumentException ex) { + } catch (InvocationTargetException ex) { + } catch (NoSuchMethodException ex) { + } catch (SecurityException ex) { + } catch (ClassNotFoundException ex) { + } + return null; + } + + /** + * Get the TextureAtlasTile for the given Texture + * @param texture The texture to retrieve the TextureAtlasTile for. + * @return the atlas tile + */ + public TextureAtlasTile getAtlasTile(Texture texture) { + String sourceTextureName = textureName(texture); + if (sourceTextureName != null) { + return getAtlasTile(sourceTextureName); + } + return null; + } + + /** + * Get the TextureAtlasTile for the given Texture + * @param assetName The texture to retrieve the TextureAtlasTile for. + * @return + */ + private TextureAtlasTile getAtlasTile(String assetName) { + return locationMap.get(assetName); + } + + /** + * Creates a new atlas texture for the given map name. + * @param mapName + * @return the atlas texture + */ + public Texture getAtlasTexture(String mapName) { + if (images == null) { + return null; + } + byte[] image = images.get(mapName); + if (image != null) { + Texture2D tex = new Texture2D(new Image(format, atlasWidth, atlasHeight, BufferUtils.createByteBuffer(image))); + tex.setMagFilter(Texture.MagFilter.Bilinear); + tex.setMinFilter(Texture.MinFilter.BilinearNearestMipMap); + tex.setWrap(Texture.WrapMode.Clamp); + return tex; + } + return null; + } + + /** + * Applies the texture coordinates to the given geometry + * if its DiffuseMap or ColorMap exists in the atlas. + * @param geom The geometry to change the texture coordinate buffer on. + * @return true if texture has been found and coords have been changed, false otherwise. + */ + public boolean applyCoords(Geometry geom) { + return applyCoords(geom, 0, geom.getMesh()); + } + + /** + * Applies the texture coordinates to the given output mesh + * if the DiffuseMap or ColorMap of the input geometry exist in the atlas. + * @param geom The geometry to change the texture coordinate buffer on. + * @param offset Target buffer offset. + * @param outMesh The mesh to set the coords in (can be same as input). + * @return true if texture has been found and coords have been changed, false otherwise. + */ + public boolean applyCoords(Geometry geom, int offset, Mesh outMesh) { + Mesh inMesh = geom.getMesh(); + geom.computeWorldMatrix(); + + VertexBuffer inBuf = inMesh.getBuffer(Type.TexCoord); + VertexBuffer outBuf = outMesh.getBuffer(Type.TexCoord); + + if (inBuf == null || outBuf == null) { + throw new IllegalStateException("Geometry mesh has no texture coordinate buffer."); + } + + Texture tex = getMaterialTexture(geom, "DiffuseMap"); + if (tex == null) { + tex = getMaterialTexture(geom, "ColorMap"); + + } + if (tex != null) { + TextureAtlasTile tile = getAtlasTile(tex); + if (tile != null) { + FloatBuffer inPos = (FloatBuffer) inBuf.getData(); + FloatBuffer outPos = (FloatBuffer) outBuf.getData(); + tile.transformTextureCoords(inPos, offset, outPos); + return true; + } else { + return false; + } + } else { + throw new IllegalStateException("Geometry has no proper texture."); + } + } + + /** + * Create a texture atlas for the given root node, containing DiffuseMap, NormalMap and SpecularMap. + * @param root The rootNode to create the atlas for. + * @param atlasSize The size of the atlas (width and height). + * @return Null if the atlas cannot be created because not all textures fit. + */ + public static TextureAtlas createAtlas(Spatial root, int atlasSize) { + List geometries = new ArrayList(); + GeometryBatchFactory.gatherGeoms(root, geometries); + TextureAtlas atlas = new TextureAtlas(atlasSize, atlasSize); + for (Geometry geometry : geometries) { + if (!atlas.addGeometry(geometry)) { + logger.log(Level.WARNING, "Texture atlas size too small, cannot add all textures"); + return null; + } + } + return atlas; + } + + /** + * Creates one geometry out of the given root spatial and merges all single + * textures into one texture of the given size. + * @param spat The root spatial of the scene to batch + * @param mgr An assetmanager that can be used to create the material. + * @param atlasSize A size for the atlas texture, it has to be large enough to hold all single textures. + * @return A new geometry that uses the generated texture atlas and merges all meshes of the root spatial, null if the atlas cannot be created because not all textures fit. + */ + public static Geometry makeAtlasBatch(Spatial spat, AssetManager mgr, int atlasSize) { + List geometries = new ArrayList(); + GeometryBatchFactory.gatherGeoms(spat, geometries); + TextureAtlas atlas = createAtlas(spat, atlasSize); + if (atlas == null) { + return null; + } + Geometry geom = new Geometry(); + Mesh mesh = new Mesh(); + GeometryBatchFactory.mergeGeometries(geometries, mesh); + applyAtlasCoords(geometries, mesh, atlas); + mesh.updateCounts(); + mesh.updateBound(); + geom.setMesh(mesh); + + Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md"); + mat.getAdditionalRenderState().setAlphaTest(true); + Texture diffuseMap = atlas.getAtlasTexture("DiffuseMap"); + Texture normalMap = atlas.getAtlasTexture("NormalMap"); + Texture specularMap = atlas.getAtlasTexture("SpecularMap"); + if (diffuseMap != null) { + mat.setTexture("DiffuseMap", diffuseMap); + } + if (normalMap != null) { + mat.setTexture("NormalMap", normalMap); + } + if (specularMap != null) { + mat.setTexture("SpecularMap", specularMap); + } + mat.setFloat("Shininess", 16.0f); + + geom.setMaterial(mat); + return geom; + } + + private static void applyAtlasCoords(List geometries, Mesh outMesh, TextureAtlas atlas) { + int globalVertIndex = 0; + + for (Geometry geom : geometries) { + Mesh inMesh = geom.getMesh(); + geom.computeWorldMatrix(); + + int geomVertCount = inMesh.getVertexCount(); + + VertexBuffer inBuf = inMesh.getBuffer(Type.TexCoord); + VertexBuffer outBuf = outMesh.getBuffer(Type.TexCoord); + + if (inBuf == null || outBuf == null) { + continue; + } + + atlas.applyCoords(geom, globalVertIndex, outMesh); + + globalVertIndex += geomVertCount; + } + } + + private static Texture getMaterialTexture(Geometry geometry, String mapName) { + Material mat = geometry.getMaterial(); + if (mat == null || mat.getParam(mapName) == null || !(mat.getParam(mapName) instanceof MatParamTexture)) { + return null; + } + MatParamTexture param = (MatParamTexture) mat.getParam(mapName); + Texture texture = param.getTextureValue(); + if (texture == null) { + return null; + } + return texture; + + + } + + private class Node { + + public TextureAtlasTile location; + public Node child[]; + public boolean occupied; + + public Node(int x, int y, int width, int height) { + location = new TextureAtlasTile(x, y, width, height); + child = new Node[2]; + child[0] = null; + child[1] = null; + occupied = false; + } + + public boolean isLeaf() { + return child[0] == null && child[1] == null; + } + + // Algorithm from http://www.blackpawn.com/texts/lightmaps/ + public Node insert(Image image) { + if (!isLeaf()) { + Node newNode = child[0].insert(image); + + if (newNode != null) { + return newNode; + } + + return child[1].insert(image); + } else { + if (occupied) { + return null; // occupied + } + + if (image.getWidth() > location.getWidth() || image.getHeight() > location.getHeight()) { + return null; // does not fit + } + + if (image.getWidth() == location.getWidth() && image.getHeight() == location.getHeight()) { + occupied = true; // perfect fit + return this; + } + + int dw = location.getWidth() - image.getWidth(); + int dh = location.getHeight() - image.getHeight(); + + if (dw > dh) { + child[0] = new Node(location.getX(), location.getY(), image.getWidth(), location.getHeight()); + child[1] = new Node(location.getX() + image.getWidth(), location.getY(), location.getWidth() - image.getWidth(), location.getHeight()); + } else { + child[0] = new Node(location.getX(), location.getY(), location.getWidth(), image.getHeight()); + child[1] = new Node(location.getX(), location.getY() + image.getHeight(), location.getWidth(), location.getHeight() - image.getHeight()); + } + + return child[0].insert(image); + } + } + } + + public class TextureAtlasTile { + + private int x; + private int y; + private int width; + private int height; + + public TextureAtlasTile(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + /** + * Get the transformed texture coordinate for a given input location. + * @param previousLocation The old texture coordinate. + * @return The new texture coordinate inside the atlas. + */ + public Vector2f getLocation(Vector2f previousLocation) { + float x = (float) getX() / (float) atlasWidth; + float y = (float) getY() / (float) atlasHeight; + float w = (float) getWidth() / (float) atlasWidth; + float h = (float) getHeight() / (float) atlasHeight; + Vector2f location = new Vector2f(x, y); + float prevX = previousLocation.x; + float prevY = previousLocation.y; + location.addLocal(prevX * w, prevY * h); + return location; + } + + /** + * Transforms a whole texture coordinates buffer. + * @param inBuf The input texture buffer. + * @param offset The offset in the output buffer + * @param outBuf The output buffer. + */ + public void transformTextureCoords(FloatBuffer inBuf, int offset, FloatBuffer outBuf) { + Vector2f tex = new Vector2f(); + + // offset is given in element units + // convert to be in component units + offset *= 2; + + for (int i = 0; i < inBuf.limit() / 2; i++) { + tex.x = inBuf.get(i * 2 + 0); + tex.y = inBuf.get(i * 2 + 1); + Vector2f location = getLocation(tex); + //TODO: add proper texture wrapping for atlases.. + outBuf.put(offset + i * 2 + 0, location.x); + outBuf.put(offset + i * 2 + 1, location.y); + } + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + } +} diff --git a/jme3-core/src/tools/java/jme3tools/optimize/TriangleCollector.java b/jme3-core/src/tools/java/jme3tools/optimize/TriangleCollector.java new file mode 100644 index 000000000..3a2ccd1ba --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/optimize/TriangleCollector.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3tools.optimize; + +import com.jme3.light.Light; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.nio.Buffer; +import java.nio.ShortBuffer; +import java.util.*; + +public class TriangleCollector { + + private static final GeomTriComparator comparator = new GeomTriComparator(); + + private static class GeomTriComparator implements Comparator { + 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 inGeoms + * @param tris + * @return The optimized geometries + */ + 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/jme3-core/src/tools/java/jme3tools/optimize/pvsnotes b/jme3-core/src/tools/java/jme3tools/optimize/pvsnotes new file mode 100644 index 000000000..61d83a566 --- /dev/null +++ b/jme3-core/src/tools/java/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/jme3-core/src/tools/java/jme3tools/savegame/SaveGame.java b/jme3-core/src/tools/java/jme3tools/savegame/SaveGame.java new file mode 100644 index 000000000..4a116380b --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/savegame/SaveGame.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.savegame; + +import com.jme3.asset.AssetManager; +import com.jme3.export.Savable; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.system.JmeSystem; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * Tool for saving Savables as SaveGame entries in a system-dependent way. + * @author normenhansen + */ +public class SaveGame { + + /** + * Saves a savable in a system-dependent way. + * @param gamePath A unique path for this game, e.g. com/mycompany/mygame + * @param dataName A unique name for this savegame, e.g. "save_001" + * @param data The Savable to save + */ + public static void saveGame(String gamePath, String dataName, Savable data) { + saveGame(gamePath, dataName, data, JmeSystem.StorageFolderType.External); + } + + /** + * Saves a savable in a system-dependent way. + * @param gamePath A unique path for this game, e.g. com/mycompany/mygame + * @param dataName A unique name for this savegame, e.g. "save_001" + * @param data The Savable to save + * @param storageType The specific type of folder to use to save the data + */ + public static void saveGame(String gamePath, String dataName, Savable data, JmeSystem.StorageFolderType storageType) { + if (storageType == null) { + Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Base Storage Folder Type is null, using External!"); + storageType = JmeSystem.StorageFolderType.External; + } + + BinaryExporter ex = BinaryExporter.getInstance(); + OutputStream os = null; + try { + File baseFolder = JmeSystem.getStorageFolder(storageType); + if (baseFolder == null) { + Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error creating save file!"); + throw new IllegalStateException("SaveGame dataset cannot be created"); + } + File daveFolder = new File(baseFolder.getAbsolutePath() + File.separator + gamePath.replace('/', File.separatorChar)); + if (!daveFolder.exists() && !daveFolder.mkdirs()) { + Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error creating save file!"); + throw new IllegalStateException("SaveGame dataset cannot be created"); + } + File saveFile = new File(daveFolder.getAbsolutePath() + File.separator + dataName); + if (!saveFile.exists()) { + if (!saveFile.createNewFile()) { + Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error creating save file!"); + throw new IllegalStateException("SaveGame dataset cannot be created"); + } + } + os = new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(saveFile))); + ex.save(data, os); + Logger.getLogger(SaveGame.class.getName()).log(Level.FINE, "Saving data to: {0}", saveFile.getAbsolutePath()); + } catch (IOException ex1) { + Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error saving data: {0}", ex1); + ex1.printStackTrace(); + throw new IllegalStateException("SaveGame dataset cannot be saved"); + } finally { + try { + if (os != null) { + os.close(); + } + } catch (IOException ex1) { + Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error saving data: {0}", ex1); + ex1.printStackTrace(); + throw new IllegalStateException("SaveGame dataset cannot be saved"); + } + } + } + + /** + * Loads a savable that has been saved on this system with saveGame() before. + * @param gamePath A unique path for this game, e.g. com/mycompany/mygame + * @param dataName A unique name for this savegame, e.g. "save_001" + * @return The savable that was saved + */ + public static Savable loadGame(String gamePath, String dataName) { + return loadGame(gamePath, dataName, null, JmeSystem.StorageFolderType.External); + } + + /** + * Loads a savable that has been saved on this system with saveGame() before. + * @param gamePath A unique path for this game, e.g. com/mycompany/mygame + * @param dataName A unique name for this savegame, e.g. "save_001" + * @param storageType The specific type of folder to use to save the data + * @return The savable that was saved + */ + public static Savable loadGame(String gamePath, String dataName, JmeSystem.StorageFolderType storageType) { + return loadGame(gamePath, dataName, null, storageType); + } + + /** + * Loads a savable that has been saved on this system with saveGame() before. + * @param gamePath A unique path for this game, e.g. com/mycompany/mygame + * @param dataName A unique name for this savegame, e.g. "save_001" + * @param manager Link to an AssetManager if required for loading the data (e.g. models with textures) + * @return The savable that was saved or null if none was found + */ + public static Savable loadGame(String gamePath, String dataName, AssetManager manager) { + return loadGame(gamePath, dataName, manager, JmeSystem.StorageFolderType.External); + } + + /** + * Loads a savable that has been saved on this system with saveGame() before. + * @param gamePath A unique path for this game, e.g. com/mycompany/mygame + * @param dataName A unique name for this savegame, e.g. "save_001" + * @param manager Link to an AssetManager if required for loading the data (e.g. models with textures) + * @param storageType The specific type of folder to use to save the data + * @return The savable that was saved or null if none was found + */ + public static Savable loadGame(String gamePath, String dataName, AssetManager manager, JmeSystem.StorageFolderType storageType) { + if (storageType == null) { + Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Base Storage Folder Type is null, using External!"); + storageType = JmeSystem.StorageFolderType.External; + } + + InputStream is = null; + Savable sav = null; + try { + File baseFolder = JmeSystem.getStorageFolder(storageType); + if (baseFolder == null) { + Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error reading base storage folder!"); + return null; + } + File file = new File(baseFolder.getAbsolutePath() + File.separator + gamePath.replace('/', File.separatorChar) + File.separator + dataName); + if(!file.exists()){ + return null; + } + is = new GZIPInputStream(new BufferedInputStream(new FileInputStream(file))); + BinaryImporter imp = BinaryImporter.getInstance(); + if (manager != null) { + imp.setAssetManager(manager); + } + sav = imp.load(is); + Logger.getLogger(SaveGame.class.getName()).log(Level.FINE, "Loading data from: {0}", file.getAbsolutePath()); + } catch (IOException ex) { + Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error loading data: {0}", ex); + ex.printStackTrace(); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException ex) { + Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error loading data: {0}", ex); + ex.printStackTrace(); + } + } + } + return sav; + } +} diff --git a/jme3-core/src/tools/java/jme3tools/shader/ShaderDebug.java b/jme3-core/src/tools/java/jme3tools/shader/ShaderDebug.java new file mode 100644 index 000000000..4cd085c3f --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/shader/ShaderDebug.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.shader; + +/** + * Static tool box class for convenient methods to help debug shaders + * @author Nehon + */ +public class ShaderDebug { + + /** + * Append the line numbers to the source code of a shader to output it + * @param defines the defines + * @param source the source + * @return the formated source code + */ + public static String formatShaderSource(String defines, String source, String version) { + String[] versionLines = version.split("\n"); + String[] definesLines = defines.split("\n"); + String[] sourceLines = source.split("\n"); + int nblines = 0; + StringBuilder out = new StringBuilder(); + if (!version.equals("")) { + for (String string : versionLines) { + nblines++; + out.append(nblines).append("\t").append(string).append("\n"); + } + } + if (!defines.equals("")) { + for (String string : definesLines) { + nblines++; + out.append(nblines).append("\t").append(string).append("\n"); + } + } + for (String string : sourceLines) { + nblines++; + out.append(nblines).append("\t").append(string).append("\n"); + } + return out.toString(); + } +} diff --git a/jme3-core/src/tools/java/jme3tools/shadercheck/CgcValidator.java b/jme3-core/src/tools/java/jme3tools/shadercheck/CgcValidator.java new file mode 100644 index 000000000..c5f314711 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/shadercheck/CgcValidator.java @@ -0,0 +1,111 @@ +package jme3tools.shadercheck; + +import com.jme3.shader.Shader; +import com.jme3.shader.Shader.ShaderSource; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class CgcValidator implements Validator { + + private static final Logger logger = Logger.getLogger(CgcValidator.class.getName()); + private static String version; + + private static String checkCgCompilerVersion(){ + try { + ProcessBuilder pb = new ProcessBuilder("cgc", "--version"); + Process p = pb.start(); + + Scanner scan = new Scanner(p.getErrorStream()); + String ln = scan.nextLine(); + scan.close(); + + p.waitFor(); + + String versionNumber = ln.split("\\s")[2]; + return versionNumber.substring(0, versionNumber.length()-1); + } catch (IOException ex) { + logger.log(Level.SEVERE, "IOEx", ex); + } catch (InterruptedException ex){ + } + return null; + } + + public String getName() { + return "NVIDIA Cg Toolkit"; + } + + public boolean isInstalled() { + return getInstalledVersion() != null; + } + + public String getInstalledVersion() { + if (version == null){ + version = checkCgCompilerVersion(); + } + return version; + } + + private static void executeCg(String sourceCode, String language, String defines, String profile, StringBuilder output){ + try { + ProcessBuilder pb = new ProcessBuilder("cgc", "-oglsl", + "-nocode", + "-strict", + "-glslWerror", + "-profile", profile, + //"-po", "NumMathInstructionSlots=64",// math instruction slots + //"-po", "MaxTexIndirections=4", // texture indirections + "-po", "NumTemps=32", // temporary variables + //"-po", "NumInstructionSlots=1", // total instruction slots + //"-po", "NumTexInstructionSlots=32",// texture instruction slots + "-po", "MaxLocalParams=32"); // local parameters + + + Process p = pb.start(); + + String glslVer = language.substring(4); + + OutputStreamWriter writer = new OutputStreamWriter(p.getOutputStream()); + writer.append("#version ").append(glslVer).append('\n'); + writer.append("#extension all : warn").append('\n'); + writer.append(defines).append('\n'); + writer.write(sourceCode); + writer.close(); + + Scanner scan = new Scanner(p.getErrorStream()); + String ln = scan.nextLine(); + if (ln.contains("0 errors")){ + output.append(" - Success!").append('\n'); + }else{ + output.append(" - Failure!").append('\n'); + output.append(ln).append('\n'); + while (scan.hasNextLine()){ + output.append(scan.nextLine()).append('\n'); + } + } + scan.close(); + + p.waitFor(); + } catch (IOException ex) { + logger.log(Level.SEVERE, "IOEx", ex); + } catch (InterruptedException ex){ + } + } + + public void validate(Shader shader, StringBuilder results) { + for (ShaderSource source : shader.getSources()){ + results.append("Checking: ").append(source.getName()); + switch (source.getType()){ + case Fragment: + executeCg(source.getSource(), source.getLanguage(), source.getDefines(), "arbfp1", results); + break; + case Vertex: + executeCg(source.getSource(), source.getLanguage(), source.getDefines(), "arbvp1", results); + break; + } + } + } + +} diff --git a/jme3-core/src/tools/java/jme3tools/shadercheck/GpuAnalyzerValidator.java b/jme3-core/src/tools/java/jme3tools/shadercheck/GpuAnalyzerValidator.java new file mode 100644 index 000000000..d06f26c88 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/shadercheck/GpuAnalyzerValidator.java @@ -0,0 +1,121 @@ +package jme3tools.shadercheck; + +import com.jme3.shader.Shader; +import com.jme3.shader.Shader.ShaderSource; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Shader validator implementation for AMD's GPUShaderAnalyser. + * + * @author Kirill Vainer + */ +public class GpuAnalyzerValidator implements Validator { + + private static final Logger logger = Logger.getLogger(CgcValidator.class.getName()); + private static String version; + + private static String checkGpuAnalyzerVersion(){ + try { + ProcessBuilder pb = new ProcessBuilder("GPUShaderAnalyzer", "-ListModules"); + Process p = pb.start(); + + Scanner scan = new Scanner(p.getInputStream()); + String ln = scan.nextLine(); + scan.close(); + + p.destroy(); + + return ln; + } catch (IOException ex) { + logger.log(Level.SEVERE, "IOEx", ex); + } + return null; + } + + public String getName() { + return "AMD GPU Shader Analyzer"; + } + + public boolean isInstalled() { + return getInstalledVersion() != null; + } + + public String getInstalledVersion() { + if (version == null){ + version = checkGpuAnalyzerVersion(); + } + return version; + } + private static void executeAnalyzer(String sourceCode, String language, String defines, String asic, StringBuilder results){ + try { + // Export sourcecode to temporary file + File tempFile = File.createTempFile("test_shader", ".glsl"); + FileWriter writer = new FileWriter(tempFile); + + String glslVer = language.substring(4); + writer.append("#version ").append(glslVer).append('\n'); + writer.append("#extension all : warn").append('\n'); + writer.append(defines).append('\n'); + writer.write(sourceCode); + writer.close(); + + ProcessBuilder pb = new ProcessBuilder("GPUShaderAnalyzer", + tempFile.getAbsolutePath(), + "-I", + "-ASIC", asic); + + Process p = pb.start(); + + Scanner scan = new Scanner(p.getInputStream()); + + if (!scan.hasNextLine()){ + String x = scan.next(); + System.out.println(x); + } + + String ln = scan.nextLine(); + + if (ln.startsWith(";")){ + results.append(" - Success!").append('\n'); + }else{ + results.append(" - Failure!").append('\n'); + results.append(ln).append('\n'); + while (scan.hasNextLine()){ + results.append(scan.nextLine()).append('\n'); + } + } + + scan.close(); + p.getOutputStream().close(); + p.getErrorStream().close(); + + p.waitFor(); + p.destroy(); + + tempFile.delete(); + } catch (InterruptedException ex) { + } catch (IOException ex) { + logger.log(Level.SEVERE, "IOEx", ex); + } + } + + public void validate(Shader shader, StringBuilder results) { + for (ShaderSource source : shader.getSources()){ + results.append("Checking: ").append(source.getName()); + switch (source.getType()){ + case Fragment: + executeAnalyzer(source.getSource(), source.getLanguage(), source.getDefines(), "HD5770", results); + break; + case Vertex: + executeAnalyzer(source.getSource(), source.getLanguage(), source.getDefines(), "HD5770", results); + break; + } + } + } + +} diff --git a/jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java b/jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java new file mode 100644 index 000000000..24dfab3c4 --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java @@ -0,0 +1,98 @@ +package jme3tools.shadercheck; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.asset.plugins.FileLocator; +import com.jme3.material.MaterialDef; +import com.jme3.material.TechniqueDef; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.shader.DefineList; +import com.jme3.shader.Shader; +import com.jme3.shader.ShaderKey; +import com.jme3.shader.plugins.GLSLLoader; +import com.jme3.system.JmeSystem; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ShaderCheck { + + private static final Logger logger = Logger.getLogger(ShaderCheck.class.getName()); + private static AssetManager assetManager; + + private static Validator[] validators = new Validator[]{ + new CgcValidator(), +// new GpuAnalyzerValidator() + }; + + private static void initAssetManager(){ + assetManager = JmeSystem.newAssetManager(); + assetManager.registerLocator(".", FileLocator.class); + assetManager.registerLocator("/", ClasspathLocator.class); + assetManager.registerLoader(J3MLoader.class, "j3m"); + assetManager.registerLoader(J3MLoader.class, "j3md"); + assetManager.registerLoader(GLSLLoader.class, "vert", "frag", "glsllib"); + } + + private static void checkMatDef(String matdefName){ + MaterialDef def = (MaterialDef) assetManager.loadAsset(matdefName); + for (TechniqueDef techDef : def.getDefaultTechniques()){ + if (techDef.isUsingShaders()){ + DefineList dl = new DefineList(); + dl.addFrom(techDef.getShaderPresetDefines()); + ShaderKey shaderKey = new ShaderKey(techDef.getVertexShaderName(), + techDef.getFragmentShaderName(), + dl, + techDef.getVertexShaderLanguage(), + techDef.getFragmentShaderLanguage()); + Shader shader = assetManager.loadShader(shaderKey); + + for (Validator validator : validators){ + StringBuilder sb = new StringBuilder(); + validator.validate(shader, sb); + System.out.println("==== Validator: " + validator.getName() + " " + + validator.getInstalledVersion() + " ===="); + System.out.println(sb.toString()); + } + } + } + } + + public static void main(String[] args){ + Logger.getLogger(MaterialDef.class.getName()).setLevel(Level.OFF); + initAssetManager(); + checkMatDef("Common/MatDefs/Blur/HGaussianBlur.j3md"); + checkMatDef("Common/MatDefs/Blur/RadialBlur.j3md"); + checkMatDef("Common/MatDefs/Blur/VGaussianBlur.j3md"); + checkMatDef("Common/MatDefs/Gui/Gui.j3md"); + checkMatDef("Common/MatDefs/Hdr/LogLum.j3md"); + checkMatDef("Common/MatDefs/Hdr/ToneMap.j3md"); + checkMatDef("Common/MatDefs/Light/Lighting.j3md"); + checkMatDef("Common/MatDefs/Misc/ColoredTextured.j3md"); + checkMatDef("Common/MatDefs/Misc/Particle.j3md"); + checkMatDef("Common/MatDefs/Misc/ShowNormals.j3md"); + checkMatDef("Common/MatDefs/Misc/Sky.j3md"); + checkMatDef("Common/MatDefs/Misc/Unshaded.j3md"); + + checkMatDef("Common/MatDefs/Post/BloomExtract.j3md"); + checkMatDef("Common/MatDefs/Post/BloomFinal.j3md"); + checkMatDef("Common/MatDefs/Post/CartoonEdge.j3md"); + checkMatDef("Common/MatDefs/Post/CrossHatch.j3md"); + checkMatDef("Common/MatDefs/Post/DepthOfField.j3md"); + checkMatDef("Common/MatDefs/Post/FXAA.j3md"); + checkMatDef("Common/MatDefs/Post/Fade.j3md"); + checkMatDef("Common/MatDefs/Post/Fog.j3md"); + checkMatDef("Common/MatDefs/Post/GammaCorrection.j3md"); + checkMatDef("Common/MatDefs/Post/LightScattering.j3md"); + checkMatDef("Common/MatDefs/Post/Overlay.j3md"); + checkMatDef("Common/MatDefs/Post/Posterization.j3md"); + + checkMatDef("Common/MatDefs/SSAO/ssao.j3md"); + checkMatDef("Common/MatDefs/SSAO/ssaoBlur.j3md"); + checkMatDef("Common/MatDefs/Shadow/PostShadow.j3md"); + checkMatDef("Common/MatDefs/Shadow/PostShadowPSSM.j3md"); + checkMatDef("Common/MatDefs/Shadow/PreShadow.j3md"); + + checkMatDef("Common/MatDefs/Water/SimpleWater.j3md"); + checkMatDef("Common/MatDefs/Water/Water.j3md"); + } +} diff --git a/jme3-core/src/tools/java/jme3tools/shadercheck/Validator.java b/jme3-core/src/tools/java/jme3tools/shadercheck/Validator.java new file mode 100644 index 000000000..cbfc339da --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/shadercheck/Validator.java @@ -0,0 +1,37 @@ +package jme3tools.shadercheck; + +import com.jme3.shader.Shader; + +/** + * Interface for shader validator tools. + */ +public interface Validator { + + /** + * Returns the name of the validation tool + */ + public String getName(); + + /** + * Returns true if the tool is installed on the system, false otherwise. + */ + public boolean isInstalled(); + + /** + * Returns the tool version as a string, must return null if the tool + * is not installed. + */ + public String getInstalledVersion(); + + /** + * Validates the given shader to make sure it follows all requirements + * of the shader language specified as {@link Shader#getLanguage() }. + * The results of the validation will be written into the + * results argument. + * + * @param shader The shader to validate + * @param results The storage for the validation results + */ + public void validate(Shader shader, StringBuilder results); + +} diff --git a/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java b/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java new file mode 100644 index 000000000..18fc82a61 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.system.AppSettings; +import com.jme3.system.JmeCanvasContext; +import com.jme3.system.JmeSystem; +import java.applet.Applet; +import java.awt.Canvas; +import java.awt.Graphics; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; + +/** + * @author Kirill Vainer + */ +public class AppletHarness extends Applet { + + public static final HashMap appToApplet + = new HashMap(); + + protected JmeCanvasContext context; + protected Canvas canvas; + protected Application app; + + protected String appClass; + protected URL appCfg = null; + protected URL assetCfg = null; + + public static Applet getApplet(Application app){ + return appToApplet.get(app); + } + + private void createCanvas(){ + AppSettings settings = new AppSettings(true); + + // load app cfg + if (appCfg != null){ + InputStream in = null; + try { + in = appCfg.openStream(); + settings.load(in); + in.close(); + } catch (IOException ex){ + // Called before application has been created .... + // Display error message through AWT + JOptionPane.showMessageDialog(this, "An error has occured while " + + "loading applet configuration" + + ex.getMessage(), + "jME3 Applet", + JOptionPane.ERROR_MESSAGE); + ex.printStackTrace(); + } finally { + if (in != null) + try { + in.close(); + } catch (IOException ex) { + } + } + } + + if (assetCfg != null){ + settings.putString("AssetConfigURL", assetCfg.toString()); + } + + settings.setWidth(getWidth()); + settings.setHeight(getHeight()); + + JmeSystem.setLowPermissions(true); + + try{ + Class 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/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java b/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java new file mode 100644 index 000000000..1b364bc39 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java @@ -0,0 +1,849 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app; + +import com.jme3.system.AppSettings; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.prefs.BackingStoreException; +import javax.swing.*; + +/** + * 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 AppSettings + * @author Mark Powell + * @author Eric Woroshow + * @author Joshua Slack - reworked for proper use of GL commands. + */ +public final class SettingsDialog extends JFrame { + + 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; + + // Resource bundle for i18n. + ResourceBundle resourceBundle = ResourceBundle.getBundle("com.jme3.app/SettingsDialog"); + + // connection to properties file. + private final AppSettings source; + + // Title Image + private URL imageFile = null; + // Array of supported display modes + private DisplayMode[] modes = null; + private static final DisplayMode[] windowDefaults = new DisplayMode[] { + new DisplayMode(1024, 768, 24, 60), + new DisplayMode(1280, 720, 24, 60), + new DisplayMode(1280, 1024, 24, 60), + }; + private DisplayMode[] windowModes = null; + + // UI components + private JCheckBox vsyncBox = null; + private JCheckBox fullscreenBox = null; + private JComboBox displayResCombo = null; + private JComboBox colorDepthCombo = null; + private JComboBox displayFreqCombo = null; + private JComboBox antialiasCombo = null; + private JLabel icon = null; + private int selection = 0; + private SelectionListener selectionListener = null; + + private int minWidth = 0; + private int minHeight = 0; + + /** + * 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 loadSettings + * @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; + + //setModal(true); + setAlwaysOnTop(true); + setResizable(false); + + AppSettings registrySettings = new AppSettings(true); + + String appTitle; + if(source.getTitle()!=null){ + appTitle = source.getTitle(); + }else{ + appTitle = registrySettings.getTitle(); + } + + minWidth = source.getMinWidth(); + minHeight = source.getMinHeight(); + + 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()); + + DisplayMode[] merged = new DisplayMode[modes.length + windowDefaults.length]; + + int wdIndex = 0; + int dmIndex = 0; + int mergedIndex; + + for (mergedIndex = 0; + mergedIndex= modes.length) { + merged[mergedIndex] = windowDefaults[wdIndex++]; + } else if (wdIndex >= windowDefaults.length) { + merged[mergedIndex] = modes[dmIndex++]; + } else if (modes[dmIndex].getWidth() < windowDefaults[wdIndex].getWidth()) { + merged[mergedIndex] = modes[dmIndex++]; + } else if (modes[dmIndex].getWidth() == windowDefaults[wdIndex].getWidth()) { + if (modes[dmIndex].getHeight() < windowDefaults[wdIndex].getHeight()) { + merged[mergedIndex] = modes[dmIndex++]; + } else if (modes[dmIndex].getHeight() == windowDefaults[wdIndex].getHeight()) { + merged[mergedIndex] = modes[dmIndex++]; + wdIndex++; + } else { + merged[mergedIndex] = windowDefaults[wdIndex++]; + } + } else { + merged[mergedIndex] = windowDefaults[wdIndex++]; + } + } + + if (merged.length == mergedIndex) { + windowModes = merged; + } else { + windowModes = Arrays.copyOfRange(merged, 0, mergedIndex); + } + + 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); + } + + public int getMinWidth() { + return minWidth; + } + + public void setMinWidth(int minWidth) { + this.minWidth = minWidth; + } + + public int getMinHeight() { + return minHeight; + } + + public void setMinHeight(int minHeight) { + this.minHeight = minHeight; + } + + + + + /** + * 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); + } catch (MalformedURLException e) { + logger.log(Level.WARNING, "Couldn’t read from file '" + image + "'", 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() { + GridBagConstraints gbc; + + JPanel mainPanel = new JPanel(new GridBagLayout()); + + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + logger.warning("Could not set native look and feel."); + } + + addWindowListener(new WindowAdapter() { + + @Override + public void windowClosing(WindowEvent e) { + setUserSelection(CANCEL_SELECTION); + dispose(); + } + }); + + if (source.getIcons() != null) { + safeSetIconImages( (List) Arrays.asList((BufferedImage[]) source.getIcons()) ); + } + + setTitle(MessageFormat.format(resourceBundle.getString("frame.title"), source.getTitle())); + + // The buttons... + JButton ok = new JButton(resourceBundle.getString("button.ok")); + JButton cancel = new JButton(resourceBundle.getString("button.cancel")); + + icon = new JLabel(imageFile != null ? new ImageIcon(imageFile) : null); + + KeyListener aListener = new KeyAdapter() { + + @Override + 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(resourceBundle.getString("checkbox.fullscreen")); + fullscreenBox.setSelected(source.isFullscreen()); + fullscreenBox.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + updateResolutionChoices(); + } + }); + vsyncBox = new JCheckBox(resourceBundle.getString("checkbox.vsync")); + vsyncBox.setSelected(source.isVSync()); + + gbc = new GridBagConstraints(); + gbc.weightx = 0.5; + gbc.gridx = 0; + gbc.gridwidth = 2; + gbc.gridy = 1; + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(fullscreenBox, gbc); + gbc = new GridBagConstraints(); + gbc.weightx = 0.5; + gbc.insets = new Insets(4, 16, 0, 4); + gbc.gridx = 2; + gbc.gridwidth = 2; + gbc.gridy = 1; + gbc.anchor = GridBagConstraints.WEST; + mainPanel.add(vsyncBox, gbc); + + gbc = new GridBagConstraints(); + gbc.insets = new Insets(4, 4, 4, 4); + gbc.gridx = 0; + gbc.gridy = 2; + gbc.anchor = GridBagConstraints.EAST; + gbc.weightx = 0.5; + mainPanel.add(new JLabel(resourceBundle.getString("label.resolutions")), gbc); + gbc = new GridBagConstraints(); + gbc.gridx = 1; + gbc.gridy = 2; + gbc.anchor = GridBagConstraints.WEST; + mainPanel.add(displayResCombo, gbc); + gbc = new GridBagConstraints(); + gbc.insets = new Insets(4, 16, 4, 4); + gbc.gridx = 2; + gbc.gridy = 2; + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(new JLabel(resourceBundle.getString("label.colordepth")), gbc); + gbc = new GridBagConstraints(); + gbc.weightx = 0.5; + gbc.gridx = 3; + gbc.gridy = 2; + gbc.anchor = GridBagConstraints.WEST; + mainPanel.add(colorDepthCombo, gbc); + gbc = new GridBagConstraints(); + gbc.insets = new Insets(4, 4, 4, 4); + gbc.weightx = 0.5; + gbc.gridx = 0; + gbc.gridy = 3; + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(new JLabel(resourceBundle.getString("label.refresh")), gbc); + gbc = new GridBagConstraints(); + gbc.gridx = 1; + gbc.gridy = 3; + gbc.anchor = GridBagConstraints.WEST; + mainPanel.add(displayFreqCombo, gbc); + gbc = new GridBagConstraints(); + gbc.insets = new Insets(4, 16, 4, 4); + gbc.gridx = 2; + gbc.gridy = 3; + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(new JLabel(resourceBundle.getString("label.antialias")), gbc); + gbc = new GridBagConstraints(); + gbc.weightx = 0.5; + gbc.gridx = 3; + gbc.gridy = 3; + gbc.anchor = GridBagConstraints.WEST; + mainPanel.add(antialiasCombo, gbc); + + // 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(); + } + }); + + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridwidth = 2; + gbc.gridy = 4; + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(ok, gbc); + gbc = new GridBagConstraints(); + gbc.insets = new Insets(4, 16, 4, 4); + gbc.gridx = 2; + gbc.gridwidth = 2; + gbc.gridy = 4; + gbc.anchor = GridBagConstraints.WEST; + mainPanel.add(cancel, gbc); + + if (icon != null) { + gbc = new GridBagConstraints(); + gbc.gridwidth = 4; + mainPanel.add(icon, gbc); + } + + this.getContentPane().add(mainPanel); + + pack(); + + mainPanel.getRootPane().setDefaultButton(ok); + SwingUtilities.invokeLater(new Runnable() { + + public void run() { + // Fill in the combos once the window has opened so that the insets can be read. + // The assumption is made that the settings window and the display window will have the + // same insets as that is used to resize the "full screen windowed" mode appropriately. + updateResolutionChoices(); + if (source.getWidth() != 0 && source.getHeight() != 0) { + displayResCombo.setSelectedItem(source.getWidth() + " x " + + source.getHeight()); + } else { + displayResCombo.setSelectedIndex(displayResCombo.getItemCount()-1); + } + + updateAntialiasChoices(); + colorDepthCombo.setSelectedItem(source.getBitsPerPixel() + " bpp"); + } + }); + + } + + /* 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) { + logger.log(Level.WARNING, "Error setting icon images", e); + } + } + + /** + * 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(resourceBundle.getString("antialias.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; } } + */ + + 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, + resourceBundle.getString("error.unsupportedmode")); + } + + 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() { + JComboBox resolutionBox = new JComboBox(); + + resolutionBox.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + updateDisplayChoices(); + } + }); + + return resolutionBox; + } + + /** + * 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( + getWindowedResolutions(windowModes))); + if (displayResCombo.getItemCount() > 0) { + displayResCombo.setSelectedIndex(displayResCombo.getItemCount()-1); + } + colorDepthCombo.setModel(new DefaultComboBoxModel(new String[]{ + "24 bpp", "16 bpp"})); + displayFreqCombo.setModel(new DefaultComboBoxModel( + new String[]{resourceBundle.getString("refresh.na")})); + displayFreqCombo.setEnabled(false); + } else { + displayResCombo.setModel(new DefaultComboBoxModel( + getResolutions(modes, Integer.MAX_VALUE, Integer.MAX_VALUE))); + if (displayResCombo.getItemCount() > 0) { + displayResCombo.setSelectedIndex(displayResCombo.getItemCount()-1); + } + displayFreqCombo.setEnabled(true); + updateDisplayChoices(); + } + } + + private void updateAntialiasChoices() { + // maybe in the future will add support for determining this info + // through pbuffer + String[] choices = new String[]{resourceBundle.getString("antialias.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) { + logger.log(Level.WARNING, "Invalid file name '" + file + "'", 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 + * where the resolution is greater than the configured minimums. + */ + private String[] getResolutions(DisplayMode[] modes, int heightLimit, int widthLimit) { + Insets insets = getInsets(); + heightLimit -= insets.top + insets.bottom; + widthLimit -= insets.left + insets.right; + + ArrayList resolutions = new ArrayList(modes.length); + for (int i = 0; i < modes.length; i++) { + int height = modes[i].getHeight(); + int width = modes[i].getWidth(); + if (width >= minWidth && height >= minHeight) { + if (height >= heightLimit) { + height = heightLimit; + } + if (width >= widthLimit) { + width = widthLimit; + } + + String res = width + " x " + height; + if (!resolutions.contains(res)) { + resolutions.add(res); + } + } + } + + String[] res = new String[resolutions.size()]; + resolutions.toArray(res); + return res; + } + + /** + * Returns every unique resolution from an array of DisplayModes + * where the resolution is greater than the configured minimums and the height + * is less than the current screen resolution. + */ + private String[] getWindowedResolutions(DisplayMode[] modes) { + int maxHeight = 0; + int maxWidth = 0; + + for (int i = 0; i < modes.length; i++) { + if (maxHeight < modes[i].getHeight()) { + maxHeight = modes[i].getHeight(); + } + if (maxWidth < modes[i].getWidth()) { + maxWidth = modes[i].getWidth(); + } + } + + return getResolutions(modes, maxHeight, maxWidth); + } + + /** + * 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/jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java b/jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java new file mode 100644 index 000000000..4c4cd6562 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java @@ -0,0 +1,558 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app.state; + +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; + +/** + * Released under BSD License + * @author monceaux, normenhansen, entrusC + */ +public class MjpegFileWriter { + + int width = 0; + int height = 0; + double framerate = 0; + int numFrames = 0; + File aviFile = null; + FileOutputStream aviOutput = null; + FileChannel aviChannel = null; + long riffOffset = 0; + long aviMovieOffset = 0; + AVIIndexList indexlist = null; + + public MjpegFileWriter(File aviFile, int width, int height, double framerate) throws Exception { + this(aviFile, width, height, framerate, 0); + } + + public MjpegFileWriter(File aviFile, int width, int height, double framerate, int numFrames) throws Exception { + this.aviFile = aviFile; + this.width = width; + this.height = height; + this.framerate = framerate; + this.numFrames = numFrames; + aviOutput = new FileOutputStream(aviFile); + aviChannel = aviOutput.getChannel(); + + RIFFHeader rh = new RIFFHeader(); + aviOutput.write(rh.toBytes()); + aviOutput.write(new AVIMainHeader().toBytes()); + aviOutput.write(new AVIStreamList().toBytes()); + aviOutput.write(new AVIStreamHeader().toBytes()); + aviOutput.write(new AVIStreamFormat().toBytes()); + aviOutput.write(new AVIJunk().toBytes()); + aviMovieOffset = aviChannel.position(); + aviOutput.write(new AVIMovieList().toBytes()); + indexlist = new AVIIndexList(); + } + + public void addImage(Image image) throws Exception { + addImage(image, 0.8f); + } + + public void addImage(Image image, float quality) throws Exception { + addImage(writeImageToBytes(image, quality)); + } + + public void addImage(byte[] imagedata) throws Exception { + byte[] fcc = new byte[]{'0', '0', 'd', 'b'}; + int useLength = imagedata.length; + long position = aviChannel.position(); + int extra = (useLength + (int) position) % 4; + if (extra > 0) { + useLength = useLength + extra; + } + + indexlist.addAVIIndex((int) position, useLength); + + aviOutput.write(fcc); + aviOutput.write(intBytes(swapInt(useLength))); + aviOutput.write(imagedata); + if (extra > 0) { + for (int i = 0; i < extra; i++) { + aviOutput.write(0); + } + } + imagedata = null; + + numFrames++; //add a frame + } + + public void finishAVI() throws Exception { + byte[] indexlistBytes = indexlist.toBytes(); + aviOutput.write(indexlistBytes); + aviOutput.close(); + int fileSize = (int)aviFile.length(); + int listSize = (int) (fileSize - 8 - aviMovieOffset - indexlistBytes.length); + + RandomAccessFile raf = new RandomAccessFile(aviFile, "rw"); + + //add header and length by writing the headers again + //with the now available information + raf.write(new RIFFHeader(fileSize).toBytes()); + raf.write(new AVIMainHeader().toBytes()); + raf.write(new AVIStreamList().toBytes()); + raf.write(new AVIStreamHeader().toBytes()); + raf.write(new AVIStreamFormat().toBytes()); + raf.write(new AVIJunk().toBytes()); + raf.write(new AVIMovieList(listSize).toBytes()); + + raf.close(); + } + + // public void writeAVI(File file) throws Exception + // { + // OutputStream os = new FileOutputStream(file); + // + // // RIFFHeader + // // AVIMainHeader + // // AVIStreamList + // // AVIStreamHeader + // // AVIStreamFormat + // // write 00db and image bytes... + // } + public static int swapInt(int v) { + return (v >>> 24) | (v << 24) | ((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00); + } + + public static short swapShort(short v) { + return (short) ((v >>> 8) | (v << 8)); + } + + public static byte[] intBytes(int i) { + byte[] b = new byte[4]; + b[0] = (byte) (i >>> 24); + b[1] = (byte) ((i >>> 16) & 0x000000FF); + b[2] = (byte) ((i >>> 8) & 0x000000FF); + b[3] = (byte) (i & 0x000000FF); + + return b; + } + + public static byte[] shortBytes(short i) { + byte[] b = new byte[2]; + b[0] = (byte) (i >>> 8); + b[1] = (byte) (i & 0x000000FF); + + return b; + } + + private class RIFFHeader { + + public byte[] fcc = new byte[]{'R', 'I', 'F', 'F'}; + public int fileSize = 0; + public byte[] fcc2 = new byte[]{'A', 'V', 'I', ' '}; + public byte[] fcc3 = new byte[]{'L', 'I', 'S', 'T'}; + public int listSize = 200; + public byte[] fcc4 = new byte[]{'h', 'd', 'r', 'l'}; + + public RIFFHeader() { + } + + public RIFFHeader(int fileSize) { + this.fileSize = fileSize; + } + + public byte[] toBytes() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(fcc); + baos.write(intBytes(swapInt(fileSize))); + baos.write(fcc2); + baos.write(fcc3); + baos.write(intBytes(swapInt(listSize))); + baos.write(fcc4); + baos.close(); + + return baos.toByteArray(); + } + } + + private class AVIMainHeader { + /* + * + * FOURCC fcc; DWORD cb; DWORD dwMicroSecPerFrame; DWORD + * dwMaxBytesPerSec; DWORD dwPaddingGranularity; DWORD dwFlags; DWORD + * dwTotalFrames; DWORD dwInitialFrames; DWORD dwStreams; DWORD + * dwSuggestedBufferSize; DWORD dwWidth; DWORD dwHeight; DWORD + * dwReserved[4]; + */ + + public byte[] fcc = new byte[]{'a', 'v', 'i', 'h'}; + public int cb = 56; + public int dwMicroSecPerFrame = 0; // (1 + // / + // frames + // per + // sec) + // * + // 1,000,000 + public int dwMaxBytesPerSec = 10000000; + public int dwPaddingGranularity = 0; + public int dwFlags = 65552; + public int dwTotalFrames = 0; // replace + // with + // correct + // value + public int dwInitialFrames = 0; + public int dwStreams = 1; + public int dwSuggestedBufferSize = 0; + public int dwWidth = 0; // replace + // with + // correct + // value + public int dwHeight = 0; // replace + // with + // correct + // value + public int[] dwReserved = new int[4]; + + public AVIMainHeader() { + dwMicroSecPerFrame = (int) ((1.0 / framerate) * 1000000.0); + dwWidth = width; + dwHeight = height; + dwTotalFrames = numFrames; + } + + public byte[] toBytes() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(fcc); + baos.write(intBytes(swapInt(cb))); + baos.write(intBytes(swapInt(dwMicroSecPerFrame))); + baos.write(intBytes(swapInt(dwMaxBytesPerSec))); + baos.write(intBytes(swapInt(dwPaddingGranularity))); + baos.write(intBytes(swapInt(dwFlags))); + baos.write(intBytes(swapInt(dwTotalFrames))); + baos.write(intBytes(swapInt(dwInitialFrames))); + baos.write(intBytes(swapInt(dwStreams))); + baos.write(intBytes(swapInt(dwSuggestedBufferSize))); + baos.write(intBytes(swapInt(dwWidth))); + baos.write(intBytes(swapInt(dwHeight))); + baos.write(intBytes(swapInt(dwReserved[0]))); + baos.write(intBytes(swapInt(dwReserved[1]))); + baos.write(intBytes(swapInt(dwReserved[2]))); + baos.write(intBytes(swapInt(dwReserved[3]))); + baos.close(); + + return baos.toByteArray(); + } + } + + private class AVIStreamList { + + public byte[] fcc = new byte[]{'L', 'I', 'S', 'T'}; + public int size = 124; + public byte[] fcc2 = new byte[]{'s', 't', 'r', 'l'}; + + public AVIStreamList() { + } + + public byte[] toBytes() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(fcc); + baos.write(intBytes(swapInt(size))); + baos.write(fcc2); + baos.close(); + + return baos.toByteArray(); + } + } + + private class AVIStreamHeader { + /* + * FOURCC fcc; DWORD cb; FOURCC fccType; FOURCC fccHandler; DWORD + * dwFlags; WORD wPriority; WORD wLanguage; DWORD dwInitialFrames; DWORD + * dwScale; DWORD dwRate; DWORD dwStart; DWORD dwLength; DWORD + * dwSuggestedBufferSize; DWORD dwQuality; DWORD dwSampleSize; struct { + * short int left; short int top; short int right; short int bottom; } + * rcFrame; + */ + + public byte[] fcc = new byte[]{'s', 't', 'r', 'h'}; + public int cb = 64; + public byte[] fccType = new byte[]{'v', 'i', 'd', 's'}; + public byte[] fccHandler = new byte[]{'M', 'J', 'P', 'G'}; + public int dwFlags = 0; + public short wPriority = 0; + public short wLanguage = 0; + public int dwInitialFrames = 0; + public int dwScale = 0; // microseconds + // per + // frame + public int dwRate = 1000000; // dwRate + // / + // dwScale + // = + // frame + // rate + public int dwStart = 0; + public int dwLength = 0; // num + // frames + public int dwSuggestedBufferSize = 0; + public int dwQuality = -1; + public int dwSampleSize = 0; + public int left = 0; + public int top = 0; + public int right = 0; + public int bottom = 0; + + public AVIStreamHeader() { + dwScale = (int) ((1.0 / framerate) * 1000000.0); + dwLength = numFrames; + } + + public byte[] toBytes() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(fcc); + baos.write(intBytes(swapInt(cb))); + baos.write(fccType); + baos.write(fccHandler); + baos.write(intBytes(swapInt(dwFlags))); + baos.write(shortBytes(swapShort(wPriority))); + baos.write(shortBytes(swapShort(wLanguage))); + baos.write(intBytes(swapInt(dwInitialFrames))); + baos.write(intBytes(swapInt(dwScale))); + baos.write(intBytes(swapInt(dwRate))); + baos.write(intBytes(swapInt(dwStart))); + baos.write(intBytes(swapInt(dwLength))); + baos.write(intBytes(swapInt(dwSuggestedBufferSize))); + baos.write(intBytes(swapInt(dwQuality))); + baos.write(intBytes(swapInt(dwSampleSize))); + baos.write(intBytes(swapInt(left))); + baos.write(intBytes(swapInt(top))); + baos.write(intBytes(swapInt(right))); + baos.write(intBytes(swapInt(bottom))); + baos.close(); + + return baos.toByteArray(); + } + } + + private class AVIStreamFormat { + /* + * FOURCC fcc; DWORD cb; DWORD biSize; LONG biWidth; LONG biHeight; WORD + * biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; + * LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD + * biClrImportant; + */ + + public byte[] fcc = new byte[]{'s', 't', 'r', 'f'}; + public int cb = 40; + public int biSize = 40; // same + // as + // cb + public int biWidth = 0; + public int biHeight = 0; + public short biPlanes = 1; + public short biBitCount = 24; + public byte[] biCompression = new byte[]{'M', 'J', 'P', 'G'}; + public int biSizeImage = 0; // width + // x + // height + // in + // pixels + public int biXPelsPerMeter = 0; + public int biYPelsPerMeter = 0; + public int biClrUsed = 0; + public int biClrImportant = 0; + + public AVIStreamFormat() { + biWidth = width; + biHeight = height; + biSizeImage = width * height; + } + + public byte[] toBytes() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(fcc); + baos.write(intBytes(swapInt(cb))); + baos.write(intBytes(swapInt(biSize))); + baos.write(intBytes(swapInt(biWidth))); + baos.write(intBytes(swapInt(biHeight))); + baos.write(shortBytes(swapShort(biPlanes))); + baos.write(shortBytes(swapShort(biBitCount))); + baos.write(biCompression); + baos.write(intBytes(swapInt(biSizeImage))); + baos.write(intBytes(swapInt(biXPelsPerMeter))); + baos.write(intBytes(swapInt(biYPelsPerMeter))); + baos.write(intBytes(swapInt(biClrUsed))); + baos.write(intBytes(swapInt(biClrImportant))); + baos.close(); + + return baos.toByteArray(); + } + } + + private class AVIMovieList { + + public byte[] fcc = new byte[]{'L', 'I', 'S', 'T'}; + public int listSize = 0; + public byte[] fcc2 = new byte[]{'m', 'o', 'v', 'i'}; + + // 00db size jpg image data ... + public AVIMovieList() { + } + + public AVIMovieList(int listSize) { + this.listSize = listSize; + } + + public byte[] toBytes() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(fcc); + baos.write(intBytes(swapInt(listSize))); + baos.write(fcc2); + baos.close(); + + return baos.toByteArray(); + } + } + + private class AVIIndexList { + + public byte[] fcc = new byte[]{'i', 'd', 'x', '1'}; + public int cb = 0; + public List ind = new ArrayList(); + + public AVIIndexList() { + } + + @SuppressWarnings("unused") + public void addAVIIndex(AVIIndex ai) { + ind.add(ai); + } + + public void addAVIIndex(int dwOffset, int dwSize) { + ind.add(new AVIIndex(dwOffset, dwSize)); + } + + public byte[] toBytes() throws Exception { + cb = 16 * ind.size(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(fcc); + baos.write(intBytes(swapInt(cb))); + for (int i = 0; i < ind.size(); i++) { + AVIIndex in = (AVIIndex) ind.get(i); + baos.write(in.toBytes()); + } + + baos.close(); + + return baos.toByteArray(); + } + } + + private class AVIIndex { + + public byte[] fcc = new byte[]{'0', '0', 'd', 'b'}; + public int dwFlags = 16; + public int dwOffset = 0; + public int dwSize = 0; + + public AVIIndex(int dwOffset, int dwSize) { + this.dwOffset = dwOffset; + this.dwSize = dwSize; + } + + public byte[] toBytes() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(fcc); + baos.write(intBytes(swapInt(dwFlags))); + baos.write(intBytes(swapInt(dwOffset))); + baos.write(intBytes(swapInt(dwSize))); + baos.close(); + + return baos.toByteArray(); + } + } + + private class AVIJunk { + + public byte[] fcc = new byte[]{'J', 'U', 'N', 'K'}; + public int size = 1808; + public byte[] data = new byte[size]; + + public AVIJunk() { + Arrays.fill(data, (byte) 0); + } + + public byte[] toBytes() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(fcc); + baos.write(intBytes(swapInt(size))); + baos.write(data); + baos.close(); + + return baos.toByteArray(); + } + } + + public byte[] writeImageToBytes(Image image, float quality) throws Exception { + BufferedImage bi; + if (image instanceof BufferedImage && ((BufferedImage) image).getType() == BufferedImage.TYPE_INT_RGB) { + bi = (BufferedImage) image; + } else { + bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g = bi.createGraphics(); + g.drawImage(image, 0, 0, width, height, null); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + ImageWriter imgWrtr = ImageIO.getImageWritersByFormatName("jpg").next(); + ImageOutputStream imgOutStrm = ImageIO.createImageOutputStream(baos); + imgWrtr.setOutput(imgOutStrm); + + ImageWriteParam jpgWrtPrm = imgWrtr.getDefaultWriteParam(); + jpgWrtPrm.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + jpgWrtPrm.setCompressionQuality(quality); + imgWrtr.write(null, new IIOImage(bi, null, null), jpgWrtPrm); + imgOutStrm.close(); + + baos.close(); + return baos.toByteArray(); + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java b/jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java new file mode 100644 index 000000000..85c8cb7f6 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.app.state; + +import com.jme3.app.Application; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.system.Timer; +import com.jme3.texture.FrameBuffer; +import com.jme3.util.BufferUtils; +import com.jme3.util.Screenshots; +import java.awt.image.BufferedImage; +import java.io.File; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A Video recording AppState that records the screen output into an AVI file with + * M-JPEG content. The file should be playable on any OS in any video player.
      + * The video recording starts when the state is attached and stops when it is detached + * or the application is quit. You can set the fileName of the file to be written when the + * state is detached, else the old file will be overwritten. If you specify no file + * the AppState will attempt to write a file into the user home directory, made unique + * by a timestamp. + * @author normenhansen, Robert McIntyre, entrusC + */ +public class VideoRecorderAppState extends AbstractAppState { + + private int framerate = 30; + private VideoProcessor processor; + private File file; + private Application app; + private ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() { + + public Thread newThread(Runnable r) { + Thread th = new Thread(r); + th.setName("jME Video Processing Thread"); + th.setDaemon(true); + return th; + } + }); + private int numCpus = Runtime.getRuntime().availableProcessors(); + private ViewPort lastViewPort; + private float quality; + private Timer oldTimer; + + /** + * Using this constructor the video files will be written sequentially to the user's home directory with + * a quality of 0.8 and a framerate of 30fps. + */ + public VideoRecorderAppState() { + this(null, 0.8f); + } + + /** + * Using this constructor the video files will be written sequentially to the user's home directory. + * @param quality the quality of the jpegs in the video stream (0.0 smallest file - 1.0 largest file) + */ + public VideoRecorderAppState(float quality) { + this(null, quality); + } + + /** + * Using this constructor the video files will be written sequentially to the user's home directory. + * @param quality the quality of the jpegs in the video stream (0.0 smallest file - 1.0 largest file) + * @param framerate the frame rate of the resulting video, the application will be locked to this framerate + */ + public VideoRecorderAppState(float quality, int framerate) { + this(null, quality, framerate); + } + + /** + * This constructor allows you to specify the output file of the video. The quality is set + * to 0.8 and framerate to 30 fps. + * @param file the video file + */ + public VideoRecorderAppState(File file) { + this(file, 0.8f); + } + + /** + * This constructor allows you to specify the output file of the video as well as the quality + * @param file the video file + * @param quality the quality of the jpegs in the video stream (0.0 smallest file - 1.0 largest file) + * @param framerate the frame rate of the resulting video, the application will be locked to this framerate + */ + public VideoRecorderAppState(File file, float quality) { + this.file = file; + this.quality = quality; + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "JME3 VideoRecorder running on {0} CPU's", numCpus); + } + + /** + * This constructor allows you to specify the output file of the video as well as the quality + * @param file the video file + * @param quality the quality of the jpegs in the video stream (0.0 smallest file - 1.0 largest file) + */ + public VideoRecorderAppState(File file, float quality, int framerate) { + this.file = file; + this.quality = quality; + this.framerate = framerate; + Logger.getLogger(this.getClass().getName()).log(Level.FINE, "JME3 VideoRecorder running on {0} CPU's", numCpus); + } + + public File getFile() { + return file; + } + + public void setFile(File file) { + if (isInitialized()) { + throw new IllegalStateException("Cannot set file while attached!"); + } + this.file = file; + } + + /** + * Get the quality used to compress the video images. + * @return the quality of the jpegs in the video stream (0.0 smallest file - 1.0 largest file) + */ + public float getQuality() { + return quality; + } + + /** + * Set the video image quality from 0(worst/smallest) to 1(best/largest). + * @param quality the quality of the jpegs in the video stream (0.0 smallest file - 1.0 largest file) + */ + public void setQuality(float quality) { + this.quality = quality; + } + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + this.app = app; + this.oldTimer = app.getTimer(); + app.setTimer(new IsoTimer(framerate)); + if (file == null) { + String filename = System.getProperty("user.home") + File.separator + "jMonkey-" + System.currentTimeMillis() / 1000 + ".avi"; + file = new File(filename); + } + processor = new VideoProcessor(); + List vps = app.getRenderManager().getPostViews(); + + for (int i = vps.size() - 1; i >= 0; i-- ) { + lastViewPort = vps.get(i); + if (lastViewPort.isEnabled()) { + break; + } + } + lastViewPort.addProcessor(processor); + } + + @Override + public void cleanup() { + lastViewPort.removeProcessor(processor); + app.setTimer(oldTimer); + initialized = false; + file = null; + super.cleanup(); + } + + private class WorkItem { + + ByteBuffer buffer; + BufferedImage image; + byte[] data; + + public WorkItem(int width, int height) { + image = new BufferedImage(width, height, + BufferedImage.TYPE_4BYTE_ABGR); + buffer = BufferUtils.createByteBuffer(width * height * 4); + } + } + + private class VideoProcessor implements SceneProcessor { + + private Camera camera; + private int width; + private int height; + private RenderManager renderManager; + private boolean isInitilized = false; + private LinkedBlockingQueue freeItems; + private LinkedBlockingQueue usedItems = new LinkedBlockingQueue(); + private MjpegFileWriter writer; + + public void addImage(Renderer renderer, FrameBuffer out) { + if (freeItems == null) { + return; + } + try { + final WorkItem item = freeItems.take(); + usedItems.add(item); + item.buffer.clear(); + renderer.readFrameBuffer(out, item.buffer); + executor.submit(new Callable() { + + public Void call() throws Exception { + Screenshots.convertScreenShot(item.buffer, item.image); + item.data = writer.writeImageToBytes(item.image, quality); + while (usedItems.peek() != item) { + Thread.sleep(1); + } + writer.addImage(item.data); + usedItems.poll(); + freeItems.add(item); + return null; + } + }); + } catch (InterruptedException ex) { + Logger.getLogger(VideoRecorderAppState.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public void initialize(RenderManager rm, ViewPort viewPort) { + this.camera = viewPort.getCamera(); + this.width = camera.getWidth(); + this.height = camera.getHeight(); + this.renderManager = rm; + this.isInitilized = true; + if (freeItems == null) { + freeItems = new LinkedBlockingQueue(); + for (int i = 0; i < numCpus; i++) { + freeItems.add(new WorkItem(width, height)); + } + } + } + + public void reshape(ViewPort vp, int w, int h) { + } + + public boolean isInitialized() { + return this.isInitilized; + } + + public void preFrame(float tpf) { + if (null == writer) { + try { + writer = new MjpegFileWriter(file, width, height, framerate); + } catch (Exception ex) { + Logger.getLogger(VideoRecorderAppState.class.getName()).log(Level.SEVERE, "Error creating file writer: {0}", ex); + } + } + } + + public void postQueue(RenderQueue rq) { + } + + public void postFrame(FrameBuffer out) { + addImage(renderManager.getRenderer(), out); + } + + public void cleanup() { + try { + while (freeItems.size() < numCpus) { + Thread.sleep(10); + } + writer.finishAVI(); + } catch (Exception ex) { + Logger.getLogger(VideoRecorderAppState.class.getName()).log(Level.SEVERE, "Error closing video: {0}", ex); + } + writer = null; + } + } + + public static final class IsoTimer extends com.jme3.system.Timer { + + private float framerate; + private int ticks; + private long lastTime = 0; + + public IsoTimer(float framerate) { + this.framerate = framerate; + this.ticks = 0; + } + + public long getTime() { + return (long) (this.ticks * (1.0f / this.framerate) * 1000f); + } + + public long getResolution() { + return 1000L; + } + + public float getFrameRate() { + return this.framerate; + } + + public float getTimePerFrame() { + return (float) (1.0f / this.framerate); + } + + public void update() { + long time = System.currentTimeMillis(); + long difference = time - lastTime; + lastTime = time; + if (difference < (1.0f / this.framerate) * 1000.0f) { + try { + Thread.sleep(difference); + } catch (InterruptedException ex) { + } + } + this.ticks++; + } + + public void reset() { + this.ticks = 0; + } + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/cursors/plugins/CursorLoader.java b/jme3-desktop/src/main/java/com/jme3/cursors/plugins/CursorLoader.java new file mode 100644 index 000000000..c49c31834 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/cursors/plugins/CursorLoader.java @@ -0,0 +1,739 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cursors.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLoader; +import com.jme3.util.BufferUtils; +import com.jme3.util.LittleEndien; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayInputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.IntBuffer; +import java.util.ArrayList; +import javax.imageio.ImageIO; + +/** + * + * @author MadJack + * @creation Jun 5, 2012 9:45:58 AM + */ +public class CursorLoader implements AssetLoader { + + private boolean isIco; + private boolean isAni; + + /** + * Loads and return a cursor file of one of the following format: .ani, .cur and .ico. + * @param info The {@link AssetInfo} describing the cursor file. + * @return A JmeCursor representation of the LWJGL's Cursor. + * @throws IOException if the file is not found. + */ + public JmeCursor load(AssetInfo info) throws IOException { + + isIco = false; + isAni = false; + + isIco = ((AssetKey) info.getKey()).getExtension().equals("ico"); + if (!isIco) { + isIco = ((AssetKey) info.getKey()).getExtension().equals("cur"); + if (!isIco) { + isAni = ((AssetKey) info.getKey()).getExtension().equals("ani"); + } + } + if (!isAni && !isIco) { + throw new IllegalArgumentException("Cursors supported are .ico, .cur or .ani"); + } + + InputStream in = null; + try { + in = info.openStream(); + return loadCursor(in); + } finally { + if (in != null) { + in.close(); + } + } + } + + private JmeCursor loadCursor(InputStream inStream) throws IOException { + + byte[] icoimages = new byte[0]; // new byte [0] facilitates read() + + if (isAni) { + CursorImageData ciDat = new CursorImageData(); + int numIcons = 0; + int jiffy = 0; + // not using those but keeping references for now. + int steps = 0; + int width = 0; + int height = 0; + int flag = 0; // we don't use that. + int[] rate = null; + int[] animSeq = null; + ArrayList icons; + + DataInput leIn = new LittleEndien(inStream); + int riff = leIn.readInt(); + if (riff == 0x46464952) { // RIFF + // read next int (file length), discarding it, we don't need that. + leIn.readInt(); + + int nextInt = 0; + + nextInt = getNext(leIn); + if (nextInt == 0x4e4f4341) { + // We have ACON, we do nothing +// System.out.println("We have ACON. Next!"); + nextInt = getNext(leIn); + while (nextInt >= 0) { + if (nextInt == 0x68696e61) { +// System.out.println("we have 'anih' header"); + leIn.skipBytes(8); // internal struct length (always 36) + numIcons = leIn.readInt(); + steps = leIn.readInt(); // number of blits for ani cycles + width = leIn.readInt(); + height = leIn.readInt(); + leIn.skipBytes(8); + jiffy = leIn.readInt(); + flag = leIn.readInt(); + nextInt = leIn.readInt(); + } else if (nextInt == 0x65746172) { // found a 'rate' of animation +// System.out.println("we have 'rate'."); + // Fill rate here. + // Rate is synchronous with frames. + int length = leIn.readInt(); + rate = new int[length / 4]; + for (int i = 0; i < length / 4; i++) { + rate[i] = leIn.readInt(); + } + nextInt = leIn.readInt(); + } else if (nextInt == 0x20716573) { // found a 'seq ' of animation +// System.out.println("we have 'seq '."); + // Fill animation sequence here + int length = leIn.readInt(); + animSeq = new int[length / 4]; + for (int i = 0; i < length / 4; i++) { + animSeq[i] = leIn.readInt(); + } + nextInt = leIn.readInt(); + } else if (nextInt == 0x5453494c) { // Found a LIST +// System.out.println("we have 'LIST'."); + int length = leIn.readInt(); + nextInt = leIn.readInt(); + if (nextInt == 0x4f464e49) { // Got an INFO, skip its length + // this part consist of Author, title, etc + leIn.skipBytes(length - 4); +// System.out.println(" Discarding INFO (skipped = " + skipped + ")"); + nextInt = leIn.readInt(); + } else if (nextInt == 0x6d617266) { // found a 'fram' for animation +// System.out.println("we have 'fram'."); + if (leIn.readInt() == 0x6e6f6369) { // we have 'icon' + // We have an icon and from this point on + // the rest is only icons. + int icoLength = leIn.readInt(); + ciDat.numImages = numIcons; + icons = new ArrayList(numIcons); + for (int i = 0; i < numIcons; i++) { + if (i > 0) { + // skip 'icon' header and length as they are + // known already and won't change. + leIn.skipBytes(8); + } + byte[] data = new byte[icoLength]; + ((InputStream) leIn).read(data, 0, icoLength); + // in case the header didn't have width or height + // get it from first image. + if (width == 0 || height == 0 && i == 1) { + width = data[6]; + height = data[7]; + } + icons.add(data); + } + // at this point we have the icons, rates (either + // through jiffy or rate array, the sequence (if + // applicable) and the ani header info. + // Put things together. + ciDat.assembleCursor(icons, rate, animSeq, jiffy, steps, width, height); + ciDat.completeCursor(); + nextInt = leIn.readInt(); + // if for some reason there's JUNK (nextInt > -1) + // bail out. + nextInt = nextInt > -1 ? -1 : nextInt; + } + } + } + } + } + return setJmeCursor(ciDat); + + } else if (riff == 0x58464952) { + throw new IllegalArgumentException("Big-Endian RIFX is not supported. Sorry."); + } else { + throw new IllegalArgumentException("Unknown format."); + } + } else if (isIco) { + DataInputStream in = new DataInputStream(inStream); + int bytesToRead; + while ((bytesToRead = in.available()) != 0) { + byte[] icoimage2 = new byte[icoimages.length + bytesToRead]; + System.arraycopy(icoimages, 0, icoimage2, 0, icoimages.length); + in.read(icoimage2, icoimages.length, bytesToRead); + icoimages = icoimage2; + } + } + + BufferedImage bi[] = parseICOImage(icoimages); + CursorImageData cid = new CursorImageData(bi, 0, 0, 0, 0); + cid.completeCursor(); + + return setJmeCursor(cid); + } + + private JmeCursor setJmeCursor(CursorImageData cid) { + JmeCursor jmeCursor = new JmeCursor(); + + // set cursor's params. + jmeCursor.setWidth(cid.width); + jmeCursor.setHeight(cid.height); + jmeCursor.setxHotSpot(cid.xHotSpot); + jmeCursor.setyHotSpot(cid.yHotSpot); + jmeCursor.setNumImages(cid.numImages); + jmeCursor.setImagesDelay(cid.imgDelay); + jmeCursor.setImagesData(cid.data); +// System.out.println("Width = " + cid.width); +// System.out.println("Height = " + cid.height); +// System.out.println("HSx = " + cid.xHotSpot); +// System.out.println("HSy = " + cid.yHotSpot); +// System.out.println("# img = " + cid.numImages); + + return jmeCursor; + } + + private BufferedImage[] parseICOImage(byte[] icoimage) throws IOException { + /* + * Most of this is original code by Jeff Friesen at + * http://www.informit.com/articles/article.aspx?p=1186882&seqNum=3 + */ + + BufferedImage[] bi; + // Check resource type field. + int FDE_OFFSET = 6; // first directory entry offset + int DE_LENGTH = 16; // directory entry length + int BMIH_LENGTH = 40; // BITMAPINFOHEADER length + + if (icoimage[2] != 1 && icoimage[2] != 2 || icoimage[3] != 0) { + throw new IllegalArgumentException("Bad data in ICO/CUR file. ImageType has to be either 1 or 2."); + } + + int numImages = ubyte(icoimage[5]); + numImages <<= 8; + numImages |= icoimage[4]; + bi = new BufferedImage[numImages]; + int[] colorCount = new int[numImages]; + + for (int i = 0; i < numImages; i++) { + int width = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH]); + + int height = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 1]); + + colorCount[i] = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 2]); + + int bytesInRes = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 11]); + bytesInRes <<= 8; + bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 10]); + bytesInRes <<= 8; + bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 9]); + bytesInRes <<= 8; + bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 8]); + + int imageOffset = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 15]); + imageOffset <<= 8; + imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 14]); + imageOffset <<= 8; + imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 13]); + imageOffset <<= 8; + imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 12]); + + if (icoimage[imageOffset] == 40 + && icoimage[imageOffset + 1] == 0 + && icoimage[imageOffset + 2] == 0 + && icoimage[imageOffset + 3] == 0) { + // BITMAPINFOHEADER detected + + int _width = ubyte(icoimage[imageOffset + 7]); + _width <<= 8; + _width |= ubyte(icoimage[imageOffset + 6]); + _width <<= 8; + _width |= ubyte(icoimage[imageOffset + 5]); + _width <<= 8; + _width |= ubyte(icoimage[imageOffset + 4]); + + // If width is 0 (for 256 pixels or higher), _width contains + // actual width. + + if (width == 0) { + width = _width; + } + + int _height = ubyte(icoimage[imageOffset + 11]); + _height <<= 8; + _height |= ubyte(icoimage[imageOffset + 10]); + _height <<= 8; + _height |= ubyte(icoimage[imageOffset + 9]); + _height <<= 8; + _height |= ubyte(icoimage[imageOffset + 8]); + + // If height is 0 (for 256 pixels or higher), _height contains + // actual height times 2. + + if (height == 0) { + height = _height >> 1; // Divide by 2. + } + int planes = ubyte(icoimage[imageOffset + 13]); + planes <<= 8; + planes |= ubyte(icoimage[imageOffset + 12]); + + int bitCount = ubyte(icoimage[imageOffset + 15]); + bitCount <<= 8; + bitCount |= ubyte(icoimage[imageOffset + 14]); + + // If colorCount [i] is 0, the number of colors is determined + // from the planes and bitCount values. For example, the number + // of colors is 256 when planes is 1 and bitCount is 8. Leave + // colorCount [i] set to 0 when planes is 1 and bitCount is 32. + + if (colorCount[i] == 0) { + if (planes == 1) { + if (bitCount == 1) { + colorCount[i] = 2; + } else if (bitCount == 4) { + colorCount[i] = 16; + } else if (bitCount == 8) { + colorCount[i] = 256; + } else if (bitCount != 32) { + colorCount[i] = (int) Math.pow(2, bitCount); + } + } else { + colorCount[i] = (int) Math.pow(2, bitCount * planes); + } + } + + bi[i] = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // Parse image to image buffer. + + int colorTableOffset = imageOffset + BMIH_LENGTH; + + if (colorCount[i] == 2) { + int xorImageOffset = colorTableOffset + 2 * 4; + + int scanlineBytes = calcScanlineBytes(width, 1); + int andImageOffset = xorImageOffset + scanlineBytes * height; + + int[] masks = {128, 64, 32, 16, 8, 4, 2, 1}; + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + int index; + + if ((ubyte(icoimage[xorImageOffset + row + * scanlineBytes + col / 8]) + & masks[col % 8]) != 0) { + index = 1; + } else { + index = 0; + } + + int rgb = 0; + rgb |= (ubyte(icoimage[colorTableOffset + index * 4 + + 2])); + rgb <<= 8; + rgb |= (ubyte(icoimage[colorTableOffset + index * 4 + + 1])); + rgb <<= 8; + rgb |= (ubyte(icoimage[colorTableOffset + index + * 4])); + + if ((ubyte(icoimage[andImageOffset + row + * scanlineBytes + col / 8]) + & masks[col % 8]) != 0) { + bi[i].setRGB(col, height - 1 - row, rgb); + } else { + bi[i].setRGB(col, height - 1 - row, + 0xff000000 | rgb); + } + } + } + } else if (colorCount[i] == 16) { + int xorImageOffset = colorTableOffset + 16 * 4; + + int scanlineBytes = calcScanlineBytes(width, 4); + int andImageOffset = xorImageOffset + scanlineBytes * height; + + int[] masks = {128, 64, 32, 16, 8, 4, 2, 1}; + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + int index; + if ((col & 1) == 0) // even + { + index = ubyte(icoimage[xorImageOffset + row + * scanlineBytes + col / 2]); + index >>= 4; + } else { + index = ubyte(icoimage[xorImageOffset + row + * scanlineBytes + col / 2]) + & 15; + } + + int rgb = 0; + rgb |= (ubyte(icoimage[colorTableOffset + index * 4 + + 2])); + rgb <<= 8; + rgb |= (ubyte(icoimage[colorTableOffset + index * 4 + + 1])); + rgb <<= 8; + rgb |= (ubyte(icoimage[colorTableOffset + index + * 4])); + + if ((ubyte(icoimage[andImageOffset + row + * calcScanlineBytes(width, 1) + + col / 8]) & masks[col % 8]) + != 0) { + bi[i].setRGB(col, height - 1 - row, rgb); + } else { + bi[i].setRGB(col, height - 1 - row, + 0xff000000 | rgb); + } + } + } + } else if (colorCount[i] == 256) { + int xorImageOffset = colorTableOffset + 256 * 4; + + int scanlineBytes = calcScanlineBytes(width, 8); + int andImageOffset = xorImageOffset + scanlineBytes * height; + + int[] masks = {128, 64, 32, 16, 8, 4, 2, 1}; + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + int index; + index = ubyte(icoimage[xorImageOffset + row + * scanlineBytes + col]); + + int rgb = 0; + rgb |= (ubyte(icoimage[colorTableOffset + index * 4 + + 2])); + rgb <<= 8; + rgb |= (ubyte(icoimage[colorTableOffset + index * 4 + + 1])); + rgb <<= 8; + rgb |= (ubyte(icoimage[colorTableOffset + index * 4])); + + if ((ubyte(icoimage[andImageOffset + row + * calcScanlineBytes(width, 1) + + col / 8]) & masks[col % 8]) + != 0) { + bi[i].setRGB(col, height - 1 - row, rgb); + } else { + bi[i].setRGB(col, height - 1 - row, + 0xff000000 | rgb); + } + } + } + } else if (colorCount[i] == 0) { + int scanlineBytes = calcScanlineBytes(width, 32); + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + int rgb = ubyte(icoimage[colorTableOffset + row + * scanlineBytes + col * 4 + 3]); + rgb <<= 8; + rgb |= ubyte(icoimage[colorTableOffset + row + * scanlineBytes + col * 4 + 2]); + rgb <<= 8; + rgb |= ubyte(icoimage[colorTableOffset + row + * scanlineBytes + col * 4 + 1]); + rgb <<= 8; + rgb |= ubyte(icoimage[colorTableOffset + row + * scanlineBytes + col * 4]); + + bi[i].setRGB(col, height - 1 - row, rgb); + } + } + } + } else if (ubyte(icoimage[imageOffset]) == 0x89 + && icoimage[imageOffset + 1] == 0x50 + && icoimage[imageOffset + 2] == 0x4e + && icoimage[imageOffset + 3] == 0x47 + && icoimage[imageOffset + 4] == 0x0d + && icoimage[imageOffset + 5] == 0x0a + && icoimage[imageOffset + 6] == 0x1a + && icoimage[imageOffset + 7] == 0x0a) { + // PNG detected + + ByteArrayInputStream bais; + bais = new ByteArrayInputStream(icoimage, imageOffset, + bytesInRes); + bi[i] = ImageIO.read(bais); + } else { + throw new IllegalArgumentException("Bad data in ICO/CUR file. BITMAPINFOHEADER or PNG " + + "expected"); + } + } + icoimage = null; // This array can now be garbage collected. + + return bi; + } + + private int ubyte(byte b) { + return (b < 0) ? 256 + b : b; // Convert byte to unsigned byte. + } + + private int calcScanlineBytes(int width, int bitCount) { + // Calculate minimum number of double-words required to store width + // pixels where each pixel occupies bitCount bits. XOR and AND bitmaps + // are stored such that each scanline is aligned on a double-word + // boundary. + + return (((width * bitCount) + 31) / 32) * 4; + } + + private int getNext(DataInput in) throws IOException { + return in.readInt(); + } + + private class CursorImageData { + + int width; + int height; + int xHotSpot; + int yHotSpot; + int numImages; + IntBuffer imgDelay; + IntBuffer data; + + public CursorImageData() { + } + + CursorImageData(BufferedImage[] bi, int delay, int hsX, int hsY, int curType) { + // cursor type + // 0 - Undefined (an array of images inside an ICO) + // 1 - ICO + // 2 - CUR + IntBuffer singleCursor = null; + ArrayList cursors = new ArrayList(); + int bwidth = 0; + int bheight = 0; + boolean multIcons = false; + + // make the cursor image + for (int i = 0; i < bi.length; i++) { + BufferedImage img = bi[i]; + bwidth = img.getWidth(); + bheight = img.getHeight(); + if (curType == 1) { + hsX = 0; + hsY = bheight - 1; + } else if (curType == 2) { + if (hsY == 0) { + // make sure we flip if 0 + hsY = bheight - 1; + } + } else { + // We force to choose 32x32 icon from + // the array of icons in that ICO file. + if (bwidth != 32 && bheight != 32) { + multIcons = true; + continue; + } else { + if (img.getType() != 2) { + continue; + } else { + // force hotspot + hsY = bheight - 1; + } + } + } + + // We flip our image because .ICO and .CUR will always be reversed. + AffineTransform trans = AffineTransform.getScaleInstance(1, -1); + trans.translate(0, -img.getHeight(null)); + AffineTransformOp op = new AffineTransformOp(trans, AffineTransformOp.TYPE_BILINEAR); + img = op.filter(img, null); + + singleCursor = BufferUtils.createIntBuffer(img.getWidth() * img.getHeight()); + DataBufferInt dataIntBuf = (DataBufferInt) img.getData().getDataBuffer(); + singleCursor = IntBuffer.wrap(dataIntBuf.getData()); + cursors.add(singleCursor); + } + + int count; + if (multIcons) { + bwidth = 32; + bheight = 32; + count = 1; + } else { + count = cursors.size(); + } + // put the image in the IntBuffer + data = BufferUtils.createIntBuffer(bwidth * bheight); + imgDelay = BufferUtils.createIntBuffer(bi.length); + for (int i = 0; i < count; i++) { + data.put(cursors.get(i)); + if (delay > 0) { + imgDelay.put(delay); + } + } + width = bwidth; + height = bheight; + xHotSpot = hsX; + yHotSpot = hsY; + numImages = count; + data.rewind(); + if (imgDelay != null) { + imgDelay.rewind(); + } + } + + private void addFrame(byte[] imgData, int rate, int jiffy, int width, int height, int numSeq) throws IOException { + BufferedImage bi[] = parseICOImage(imgData); + int hotspotx = 0; + int hotspoty = 0; + int type = imgData[2] | imgData[3]; + if (type == 2) { + // CUR type, hotspot might be stored. + hotspotx = imgData[10] | imgData[11]; + hotspoty = imgData[12] | imgData[13]; + } else if (type == 1) { + // ICO type, hotspot not stored. Put at 0, height - 1 + // because it's flipped. + hotspotx = 0; + hotspoty = height - 1; + } +// System.out.println("Image type = " + (type == 1 ? "CUR" : "ICO")); + if (rate == 0) { + rate = jiffy; + } + CursorImageData cid = new CursorImageData(bi, rate, hotspotx, hotspoty, type); + if (width == 0) { + this.width = cid.width; + } else { + this.width = width; + } + if (height == 0) { + this.height = cid.height; + } else { + this.height = height; + } + if (data == null) { + if (numSeq > numImages) { + data = BufferUtils.createIntBuffer(this.width * this.height * numSeq); + } else { + data = BufferUtils.createIntBuffer(this.width * this.height * numImages); + } + data.put(cid.data); + } else { + data.put(cid.data); + } + if (imgDelay == null && (numImages > 1 || numSeq > 1)) { + if (numSeq > numImages) { + imgDelay = BufferUtils.createIntBuffer(numSeq); + } else { + imgDelay = BufferUtils.createIntBuffer(numImages); + } + imgDelay.put(cid.imgDelay); + } else if (imgData != null) { + imgDelay.put(cid.imgDelay); + } + xHotSpot = cid.xHotSpot; + yHotSpot = cid.yHotSpot; + cid = null; + } + + void assembleCursor(ArrayList icons, int[] rate, int[] animSeq, int jiffy, int steps, int width, int height) throws IOException { + // Jiffy multiplicator for LWJGL's delay, which is in milisecond. + final int MULT = 17; + numImages = icons.size(); + int frRate = 0; + byte[] frame = new byte[0]; + // if we have an animation sequence we use that + // since the sequence can be larger than the number + // of images in the ani if it reuses one or more of those + // images. + if (animSeq != null && animSeq.length > 0) { + for (int i = 0; i < animSeq.length; i++) { + if (rate != null) { + frRate = rate[i] * MULT; + } else { + frRate = jiffy * MULT; + } + // the frame # is the one in the animation sequence + frame = icons.get(animSeq[i]); + addFrame(frame, frRate, jiffy, width, height, animSeq.length); +// System.out.println("delay of " + frRate); + } + } else { + for (int i = 0; i < icons.size(); i++) { + frame = icons.get(i); + if (rate == null) { + frRate = jiffy * MULT; + } else { + frRate = rate[i] * MULT; + } + addFrame(frame, frRate, jiffy, width, height, 0); +// System.out.println("delay of " + frRate); + } + } + } + + /** + * Called to rewind the buffers after filling them. + */ + void completeCursor() { + if (numImages == 1) { + imgDelay = null; + } else { + imgDelay.rewind(); + } + data.rewind(); + } + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/input/awt/AwtKeyInput.java b/jme3-desktop/src/main/java/com/jme3/input/awt/AwtKeyInput.java new file mode 100644 index 000000000..ac4ff75eb --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/input/awt/AwtKeyInput.java @@ -0,0 +1,613 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.awt; + +import com.jme3.input.KeyInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.KeyInputEvent; +import java.awt.Component; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.logging.Level; +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 final ArrayList eventQueue = new ArrayList(); + private RawInputListener listener; + private Component component; + private BitSet keyStateSet = new BitSet(0xFF); + + public AwtKeyInput(){ + } + + public void initialize() { + } + + public void destroy() { + } + + public void setInputSource(Component comp){ + synchronized (eventQueue){ + if (component != null){ + component.removeKeyListener(this); + eventQueue.clear(); + keyStateSet.clear(); + } + component = comp; + component.addKeyListener(this); + } + } + + public long getInputTimeNanos() { + return System.nanoTime(); + } + + public int getKeyCount() { + return KeyEvent.KEY_LAST+1; + } + + public void update() { + synchronized (eventQueue){ + // flush events to listener + for (int i = 0; i < eventQueue.size(); i++){ + listener.onKeyEvent(eventQueue.get(i)); + } + eventQueue.clear(); + } + } + + public boolean isInitialized() { + return true; + } + + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + public void keyTyped(KeyEvent evt) { + // key code is zero for typed events + } + + public void keyPressed(KeyEvent evt) { + int code = convertAwtKey(evt.getKeyCode()); + + // Check if key was already pressed + if (!keyStateSet.get(code)){ + keyStateSet.set(code); + KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), true, false); + keyEvent.setTime(evt.getWhen()); + synchronized (eventQueue){ + eventQueue.add(keyEvent); + } + } + } + + public void keyReleased(KeyEvent evt) { + int code = convertAwtKey(evt.getKeyCode()); + + // Check if key was already released + if (keyStateSet.get(code)) { + keyStateSet.clear(code); + KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), false, false); + keyEvent.setTime(evt.getWhen()); + synchronized (eventQueue){ + 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.log(Level.WARNING, "unsupported key:{0}", 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.log( Level.WARNING, "unsupported key:{0}", key); + if ( key >= 0x10000 ) { + return key - 0x10000; + } + + return 0; + } + +} diff --git a/jme3-desktop/src/main/java/com/jme3/input/awt/AwtMouseInput.java b/jme3-desktop/src/main/java/com/jme3/input/awt/AwtMouseInput.java new file mode 100644 index 000000000..501530699 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/input/awt/AwtMouseInput.java @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.awt; + +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.MouseInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.system.JmeSystem; +import com.jme3.system.Platform; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.SwingUtilities; + +/** + * 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 visible = true; + + private RawInputListener listener; + + private Component component; + + private final ArrayList eventQueue = new ArrayList(); + private final ArrayList eventQueueCopy = 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 boolean cursorMoved; + private int eventsSinceRecenter; + + public AwtMouseInput() { + location = new Point(); + centerLocation = new Point(); + centerLocationOnScreen = new Point(); + lastKnownLocation = new Point(); + + try { + robot = new Robot(); + } catch (java.awt.AWTException e) { + logger.log(Level.SEVERE, "Could not create a robot, so the mouse cannot be grabbed! ", e); + } + } + + public void setInputSource(Component comp) { + if (component != null) { + component.removeMouseListener(this); + component.removeMouseMotionListener(this); + component.removeMouseWheelListener(this); + + eventQueue.clear(); + + wheelPos = 0; + isRecentering = false; + eventsSinceRecenter = 0; + lastEventX = 0; + lastEventY = 0; + lastEventWheel = 0; + location = new Point(); + centerLocation = new Point(); + centerLocationOnScreen = new Point(); + lastKnownLocation = new Point(); + } + + component = comp; + component.addMouseListener(this); + component.addMouseMotionListener(this); + component.addMouseWheelListener(this); + } + + public void initialize() { + } + + public void destroy() { + } + + public boolean isInitialized() { + return true; + } + + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + public long getInputTimeNanos() { + return System.nanoTime(); + } + + public void setCursorVisible(boolean visible) { +// if(JmeSystem.getPlatform() != Platform.MacOSX32 && +// JmeSystem.getPlatform() != Platform.MacOSX64 && +// JmeSystem.getPlatform() != Platform.MacOSX_PPC32 && +// JmeSystem.getPlatform() != Platform.MacOSX_PPC64){ + if (this.visible != visible) { + lastKnownLocation.x = lastKnownLocation.y = 0; + + this.visible = visible; + final boolean newVisible = visible; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + component.setCursor(newVisible ? null : getTransparentCursor()); + if (!newVisible) { + recenterMouse(component); + } + } + }); +// } + } + } + + public void update() { + if (cursorMoved) { + int newX = location.x; + int newY = location.y; + int newWheel = wheelPos; + + // invert DY + int actualX = lastKnownLocation.x; + int actualY = component.getHeight() - lastKnownLocation.y; + MouseMotionEvent evt = new MouseMotionEvent(actualX, actualY, + newX - lastEventX, + lastEventY - newY, + wheelPos, lastEventWheel - wheelPos); + listener.onMouseMotionEvent(evt); + + lastEventX = newX; + lastEventY = newY; + lastEventWheel = newWheel; + + cursorMoved = false; + } + + synchronized (eventQueue) { + eventQueueCopy.clear(); + eventQueueCopy.addAll(eventQueue); + eventQueue.clear(); + } + + int size = eventQueueCopy.size(); + for (int i = 0; i < size; i++) { + listener.onMouseButtonEvent(eventQueueCopy.get(i)); + } + } + + private Cursor getTransparentCursor() { + if (transparentCursor == null) { + BufferedImage cursorImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + cursorImage.setRGB(0, 0, 0); + transparentCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(0, 0), "empty cursor"); + } + return transparentCursor; + } + +// public void setHardwareCursor(URL file, int xHotspot, int yHotspot) { +// //Create the image from the provided url +// java.awt.Image cursorImage = new ImageIcon( file ).getImage( ); +// //Create a custom cursor with this image +// opaqueCursor = Toolkit.getDefaultToolkit().createCustomCursor( cursorImage , new Point( xHotspot , yHotspot ) , "custom cursor" ); +// //Use this cursor +// setCursorVisible( isCursorVisible ); +// } + + + public int getButtonCount() { + return 3; + } + + public void mouseClicked(MouseEvent awtEvt) { +// MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false); +// listener.onMouseButtonEvent(evt); + } + + public void mousePressed(MouseEvent awtEvt) { + MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(awtEvt), true, awtEvt.getX(), awtEvt.getY()); + evt.setTime(awtEvt.getWhen()); + synchronized (eventQueue) { + eventQueue.add(evt); + } + } + + public void mouseReleased(MouseEvent awtEvt) { + MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(awtEvt), false, awtEvt.getX(), awtEvt.getY()); + evt.setTime(awtEvt.getWhen()); + synchronized (eventQueue) { + eventQueue.add(evt); + } + } + + public void mouseEntered(MouseEvent awtEvt) { + if (!visible) { + recenterMouse(awtEvt.getComponent()); + } + } + + public void mouseExited(MouseEvent awtEvt) { + if (!visible) { + recenterMouse(awtEvt.getComponent()); + } + } + + public void mouseWheelMoved(MouseWheelEvent awtEvt) { + int dwheel = awtEvt.getUnitsToScroll(); + wheelPos += dwheel * WHEEL_AMP; + cursorMoved = true; + } + + public void mouseDragged(MouseEvent awtEvt) { + mouseMoved(awtEvt); + } + + public void mouseMoved(MouseEvent awtEvt) { + 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 == awtEvt.getX() && centerLocation.y == awtEvt.getY()) || eventsSinceRecenter++ == 5) { + lastKnownLocation.x = awtEvt.getX(); + lastKnownLocation.y = awtEvt.getY(); + isRecentering = false; + } + } else { + // MHenze (cylab) Fix Issue 35: + // Compute the delta and absolute coordinates and recenter the mouse if necessary + int dx = awtEvt.getX() - lastKnownLocation.x; + int dy = awtEvt.getY() - lastKnownLocation.y; + location.x += dx; + location.y += dy; + if (!visible) { + recenterMouse(awtEvt.getComponent()); + } + lastKnownLocation.x = awtEvt.getX(); + lastKnownLocation.y = awtEvt.getY(); + + cursorMoved = true; + } + } + + // MHenze (cylab) Fix Issue 35: A method to generate recenter the mouse to allow the InputSystem to "grab" the mouse + private void recenterMouse(final Component component) { + if (robot != null) { + eventsSinceRecenter = 0; + isRecentering = true; + centerLocation.setLocation(component.getWidth() / 2, component.getHeight() / 2); + centerLocationOnScreen.setLocation(centerLocation); + SwingUtilities.convertPointToScreen(centerLocationOnScreen, component); + robot.mouseMove(centerLocationOnScreen.x, centerLocationOnScreen.y); + } + } + + private int getJMEButtonIndex(MouseEvent awtEvt) { + int index; + switch (awtEvt.getButton()) { + default: + case MouseEvent.BUTTON1: //left + index = MouseInput.BUTTON_LEFT; + break; + case MouseEvent.BUTTON2: //middle + index = MouseInput.BUTTON_MIDDLE; + break; + case MouseEvent.BUTTON3: //right + index = MouseInput.BUTTON_RIGHT; + break; + } + return index; + } + + public void setNativeCursor(JmeCursor cursor) { + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/JmeCanvasContext.java b/jme3-desktop/src/main/java/com/jme3/system/JmeCanvasContext.java new file mode 100644 index 000000000..d044b2c79 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/JmeCanvasContext.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import java.awt.Canvas; + +public interface JmeCanvasContext extends JmeContext { + public Canvas getCanvas(); +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java new file mode 100644 index 000000000..a377a21c6 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import com.jme3.app.SettingsDialog; +import com.jme3.app.SettingsDialog.SelectionListener; +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.audio.AudioRenderer; +import com.jme3.system.JmeContext.Type; +import com.jme3.texture.Image; +import com.jme3.texture.image.DefaultImageRaster; +import com.jme3.texture.image.ImageRaster; +import com.jme3.util.Screenshots; +import java.awt.EventQueue; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import javax.imageio.ImageIO; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; + +/** + * + * @author Kirill Vainer, normenhansen + */ +public class JmeDesktopSystem extends JmeSystemDelegate { + + @Override + public AssetManager newAssetManager(URL configFile) { + return new DesktopAssetManager(configFile); + } + + @Override + public void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException { + BufferedImage awtImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); + Screenshots.convertScreenShot(imageData, awtImage); + ImageIO.write(awtImage, format, outStream); + } + + @Override + public ImageRaster createImageRaster(Image image, int slice) { + assert image.getEfficentData() == null; + return new DefaultImageRaster(image, slice); + } + + @Override + public AssetManager newAssetManager() { + return new DesktopAssetManager(null); + } + + @Override + public void showErrorDialog(String message) { + final String msg = message; + final String title = "Error in application"; + EventQueue.invokeLater(new Runnable() { + public void run() { + JOptionPane.showMessageDialog(null, msg, title, JOptionPane.ERROR_MESSAGE); + } + }); + } + + @Override + public boolean showSettingsDialog(AppSettings sourceSettings, final boolean loadFromRegistry) { + if (SwingUtilities.isEventDispatchThread()) { + throw new IllegalStateException("Cannot run from EDT"); + } + + final AppSettings settings = new AppSettings(false); + settings.copyFrom(sourceSettings); + String iconPath = sourceSettings.getSettingsDialogImage(); + if(iconPath == null){ + iconPath = ""; + } + final URL iconUrl = JmeSystem.class.getResource(iconPath.startsWith("/") ? iconPath : "/" + iconPath); + if (iconUrl == null) { + throw new AssetNotFoundException(sourceSettings.getSettingsDialogImage()); + } + + final AtomicBoolean done = new AtomicBoolean(); + final AtomicInteger result = new AtomicInteger(); + final Object lock = new Object(); + + final SelectionListener selectionListener = new SelectionListener() { + + public void onSelection(int selection) { + synchronized (lock) { + done.set(true); + result.set(selection); + lock.notifyAll(); + } + } + }; + SwingUtilities.invokeLater(new Runnable() { + + public void run() { + synchronized (lock) { + SettingsDialog dialog = new SettingsDialog(settings, iconUrl, loadFromRegistry); + dialog.setSelectionListener(selectionListener); + dialog.showDialog(); + } + } + }); + + synchronized (lock) { + while (!done.get()) { + try { + lock.wait(); + } catch (InterruptedException ex) { + } + } + } + + sourceSettings.copyFrom(settings); + + return result.get() == SettingsDialog.APPROVE_SELECTION; + } + + private JmeContext newContextLwjgl(AppSettings settings, JmeContext.Type type) { + try { + Class 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 JmeContext newContextJogl(AppSettings settings, JmeContext.Type type) { + try { + Class ctxClazz = null; + switch (type) { + case Display: + ctxClazz = (Class) Class.forName("com.jme3.system.jogl.JoglNewtDisplay"); + break; + case Canvas: + ctxClazz = (Class) Class.forName("com.jme3.system.jogl.JoglNewtCanvas"); + break; + case OffscreenSurface: + ctxClazz = (Class) Class.forName("com.jme3.system.jogl.JoglOffscreenBuffer"); + break; + default: + throw new IllegalArgumentException("Unsupported context type " + type); + } + + return ctxClazz.newInstance(); + } catch (InstantiationException ex) { + logger.log(Level.SEVERE, "Failed to create context", ex); + } catch (IllegalAccessException ex) { + logger.log(Level.SEVERE, "Failed to create context", ex); + } catch (ClassNotFoundException ex) { + logger.log(Level.SEVERE, "CRITICAL ERROR: Context class is missing!\n" + + "Make sure jme3_jogl is on the classpath.", ex); + } + + return null; + } + + private JmeContext newContextCustom(AppSettings settings, JmeContext.Type type) { + try { + String className = settings.getRenderer().substring("CUSTOM".length()); + + Class 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; + } + + @Override + public JmeContext newContext(AppSettings settings, Type contextType) { + initialize(settings); + JmeContext ctx; + if (settings.getRenderer() == null + || settings.getRenderer().equals("NULL") + || contextType == JmeContext.Type.Headless) { + ctx = new NullContext(); + ctx.setSettings(settings); + } else if (settings.getRenderer().startsWith("LWJGL")) { + ctx = newContextLwjgl(settings, contextType); + ctx.setSettings(settings); + } else if (settings.getRenderer().startsWith("JOGL")) { + ctx = newContextJogl(settings, contextType); + ctx.setSettings(settings); + } else if (settings.getRenderer().startsWith("CUSTOM")) { + ctx = newContextCustom(settings, contextType); + ctx.setSettings(settings); + } else { + throw new UnsupportedOperationException( + "Unrecognizable renderer specified: " + + settings.getRenderer()); + } + return ctx; + } + + @Override + public AudioRenderer newAudioRenderer(AppSettings settings) { + initialize(settings); + Class 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(); + return ar; + } catch (InstantiationException ex) { + logger.log(Level.SEVERE, "Failed to create context", ex); + } catch (IllegalAccessException ex) { + logger.log(Level.SEVERE, "Failed to create context", ex); + } catch (ClassNotFoundException ex) { + logger.log(Level.SEVERE, "CRITICAL ERROR: Audio implementation class is missing!\n" + + "Make sure jme3_lwjgl-oal or jm3_joal is on the classpath.", ex); + } + return null; + } + + @Override + public void initialize(AppSettings settings) { + if (initialized) { + return; + } + + initialized = true; + try { + if (!lowPermissions) { + // can only modify logging settings + // if permissions are available +// JmeFormatter formatter = new JmeFormatter(); +// Handler fileHandler = new FileHandler("jme.log"); +// fileHandler.setFormatter(formatter); +// Logger.getLogger("").addHandler(fileHandler); +// Handler consoleHandler = new ConsoleHandler(); +// consoleHandler.setFormatter(formatter); +// Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]); +// Logger.getLogger("").addHandler(consoleHandler); + } +// } catch (IOException ex){ +// logger.log(Level.SEVERE, "I/O Error while creating log file", ex); + } catch (SecurityException ex) { + logger.log(Level.SEVERE, "Security error in creating log file", ex); + } + logger.log(Level.INFO, "Running on {0}", getFullName()); + + if (!lowPermissions) { + try { + Natives.extractNativeLibs(getPlatform(), settings); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error while copying native libraries", ex); + } + } + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/Natives.java b/jme3-desktop/src/main/java/com/jme3/system/Natives.java new file mode 100644 index 000000000..778436aab --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/Natives.java @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Helper class for extracting the natives (dll, so) from the jars. + * This class should only be used internally. + */ +public final class Natives { + + private static final Logger logger = Logger.getLogger(Natives.class.getName()); + private static final byte[] buf = new byte[1024 * 100]; + private static File extractionDirOverride = null; + private static File extractionDir = null; + + public static void setExtractionDir(String name) { + extractionDirOverride = new File(name).getAbsoluteFile(); + } + + public static File getExtractionDir() { + if (extractionDirOverride != null) { + return extractionDirOverride; + } + if (extractionDir == null) { + File workingFolder = new File("").getAbsoluteFile(); + if (!workingFolder.canWrite()) { + setStorageExtractionDir(); + } else { + try { + File file = new File(workingFolder.getAbsolutePath() + File.separator + ".jmetestwrite"); + file.createNewFile(); + file.delete(); + extractionDir = workingFolder; + } catch (Exception e) { + setStorageExtractionDir(); + } + } + } + return extractionDir; + } + + private static void setStorageExtractionDir() { + logger.log(Level.WARNING, "Working directory is not writable. Using home directory instead."); + extractionDir = new File(JmeSystem.getStorageFolder(), + "natives_" + Integer.toHexString(computeNativesHash())); + if (!extractionDir.exists()) { + extractionDir.mkdir(); + } + } + + private static int computeNativesHash() { + URLConnection conn = null; + try { + String classpath = System.getProperty("java.class.path"); + URL url = Thread.currentThread().getContextClassLoader().getResource("com/jme3/system/Natives.class"); + + StringBuilder sb = new StringBuilder(url.toString()); + if (sb.indexOf("jar:") == 0) { + sb.delete(0, 4); + sb.delete(sb.indexOf("!"), sb.length()); + sb.delete(sb.lastIndexOf("/") + 1, sb.length()); + } + try { + url = new URL(sb.toString()); + } catch (MalformedURLException ex) { + throw new UnsupportedOperationException(ex); + } + + conn = url.openConnection(); + int hash = classpath.hashCode() ^ (int) conn.getLastModified(); + return hash; + } catch (IOException ex) { + throw new UnsupportedOperationException(ex); + } finally { + if (conn != null) { + try { + conn.getInputStream().close(); + conn.getOutputStream().close(); + } catch (IOException ex) { } + } + } + } + + public static void extractNativeLib(String sysName, String name) throws IOException { + extractNativeLib(sysName, name, false, true); + } + + public static void extractNativeLib(String sysName, String name, boolean load) throws IOException { + extractNativeLib(sysName, name, load, true); + } + + public static void extractNativeLib(String sysName, String name, boolean load, boolean warning) throws IOException { + String fullname; + String path; + //XXX: hack to allow specifying the extension via supplying an extension in the name (e.g. "blah.dylib") + // this is needed on osx where the openal.dylib always needs to be extracted as dylib + // and never as jnilib, even if that is the platform JNI lib suffix (OpenAL is no JNI library) + if(!name.contains(".")){ + // automatic name mapping + fullname = System.mapLibraryName(name); + path = "native/" + sysName + "/" + fullname; + //XXX: Hack to extract jnilib to dylib on OSX Java 1.7+ + // This assumes all jni libs for osx are stored as "jnilib" in the jar file. + // It will be extracted with the name required for the platform. + // At a later stage this should probably inverted so that dylib is the default name. + if(sysName.equals("macosx")){ + path = path.replaceAll("dylib","jnilib"); + } + } else{ + fullname = name; + path = "native/" + sysName + "/" + fullname; + } + + URL url = Thread.currentThread().getContextClassLoader().getResource(path); + + // Also check for binaries that are not packed in folders by jme team, e.g. maven artifacts + if(url == null){ + path = fullname; + if(sysName.equals("macosx") && !name.contains(".")){ + path = path.replaceAll("dylib","jnilib"); + } + url = Thread.currentThread().getContextClassLoader().getResource(path); + } + + if(url == null){ + if (!warning) { + logger.log(Level.WARNING, "Cannot locate native library in classpath: {0}/{1}", + new String[]{sysName, fullname}); + } + // Still try loading the library without a filename, maybe it is + // accessible otherwise + try{ + System.loadLibrary(name); + } catch(UnsatisfiedLinkError e){ + if (!warning) { + logger.log(Level.WARNING, "Cannot load native library: {0}/{1}", + new String[]{sysName, fullname}); + } + } + return; + } + + URLConnection conn = url.openConnection(); + InputStream in = conn.getInputStream(); + File targetFile = new File(getExtractionDir(), fullname); + OutputStream out = null; + try { + if (targetFile.exists()) { + // OK, compare last modified date of this file to + // file in jar + long targetLastModified = targetFile.lastModified(); + long sourceLastModified = conn.getLastModified(); + + // Allow ~1 second range for OSes that only support low precision + if (targetLastModified + 1000 > sourceLastModified) { + logger.log(Level.FINE, "Not copying library {0}. Latest already extracted.", fullname); + return; + } + } + + out = new FileOutputStream(targetFile); + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + in.close(); + in = null; + out.close(); + out = null; + + // NOTE: On OSes that support "Date Created" property, + // this will cause the last modified date to be lower than + // date created which makes no sense + targetFile.setLastModified(conn.getLastModified()); + } catch (FileNotFoundException ex) { + if (ex.getMessage().contains("used by another process")) { + return; + } + + throw ex; + } finally { + if (load) { + System.load(targetFile.getAbsolutePath()); + } + if(in != null){ + in.close(); + } + if(out != null){ + out.close(); + } + } + logger.log(Level.FINE, "Copied {0} to {1}", new Object[]{fullname, targetFile}); + } + + protected static boolean isUsingNativeBullet() { + try { + Class clazz = Class.forName("com.jme3.bullet.util.NativeMeshUtil"); + return clazz != null; + } catch (ClassNotFoundException ex) { + return false; + } + } + + public static void extractNativeLibs(Platform platform, AppSettings settings) throws IOException { + String renderer = settings.getRenderer(); + String audioRenderer = settings.getAudioRenderer(); + boolean needLWJGL = false; + boolean needOAL = false; + boolean needJInput = false; + boolean needNativeBullet = isUsingNativeBullet(); + + if (renderer != null) { + if (renderer.startsWith("LWJGL")) { + needLWJGL = true; + } + } + if (audioRenderer != null) { + if (audioRenderer.equals("LWJGL")) { + needLWJGL = true; + needOAL = true; + } + } + needJInput = settings.useJoysticks(); + + String libraryPath = getExtractionDir().toString(); + if (needLWJGL) { + logger.log(Level.INFO, "Extraction Directory: {0}", getExtractionDir().toString()); + + // LWJGL supports this feature where + // it can load libraries from this path. + System.setProperty("org.lwjgl.librarypath", libraryPath); + } + if (needJInput) { + // AND Luckily enough JInput supports the same feature. + System.setProperty("net.java.games.input.librarypath", libraryPath); + } + + switch (platform) { + case Windows64: + if (needLWJGL) { + extractNativeLib("windows", "lwjgl64"); + } + if (needOAL) { + extractNativeLib("windows", "OpenAL64", true, false); + } + if (needJInput) { + extractNativeLib("windows", "jinput-dx8_64"); + extractNativeLib("windows", "jinput-raw_64"); + } + if (needNativeBullet) { + extractNativeLib("windows", "bulletjme64", true, false); + } + break; + case Windows32: + if (needLWJGL) { + extractNativeLib("windows", "lwjgl"); + } + if (needOAL) { + extractNativeLib("windows", "OpenAL32", true, false); + } + if (needJInput) { + extractNativeLib("windows", "jinput-dx8"); + extractNativeLib("windows", "jinput-raw"); + } + if (needNativeBullet) { + extractNativeLib("windows", "bulletjme", true, false); + } + break; + case Linux64: + if (needLWJGL) { + extractNativeLib("linux", "lwjgl64"); + } + if (needJInput) { + extractNativeLib("linux", "jinput-linux64"); + } + if (needOAL) { + extractNativeLib("linux", "openal64"); + } + if (needNativeBullet) { + extractNativeLib("linux", "bulletjme64", true, false); + } + break; + case Linux32: + if (needLWJGL) { + extractNativeLib("linux", "lwjgl"); + } + if (needJInput) { + extractNativeLib("linux", "jinput-linux"); + } + if (needOAL) { + extractNativeLib("linux", "openal"); + } + if (needNativeBullet) { + extractNativeLib("linux", "bulletjme", true, false); + } + break; + case MacOSX_PPC32: + case MacOSX32: + case MacOSX_PPC64: + case MacOSX64: + if (needLWJGL) { + extractNativeLib("macosx", "lwjgl"); + } + if (needOAL){ + extractNativeLib("macosx", "openal.dylib"); + } + if (needJInput) { + extractNativeLib("macosx", "jinput-osx"); + } + if (needNativeBullet) { + extractNativeLib("macosx", "bulletjme", true, false); + } + break; + } + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java new file mode 100644 index 000000000..1227cbdf2 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system.awt; + +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import com.jme3.util.Screenshots; +import java.awt.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferStrategy; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +public class AwtPanel extends Canvas implements SceneProcessor { + + private boolean attachAsMain = false; + + private BufferedImage img; + private FrameBuffer fb; + private ByteBuffer byteBuf; + private IntBuffer intBuf; + private RenderManager rm; + private PaintMode paintMode; + private ArrayList viewPorts = new ArrayList(); + + // Visibility/drawing vars + private BufferStrategy strategy; + private AffineTransformOp transformOp; + private AtomicBoolean hasNativePeer = new AtomicBoolean(false); + private AtomicBoolean showing = new AtomicBoolean(false); + private AtomicBoolean repaintRequest = new AtomicBoolean(false); + + // Reshape vars + private int newWidth = 1; + private int newHeight = 1; + private AtomicBoolean reshapeNeeded = new AtomicBoolean(false); + private final Object lock = new Object(); + + public AwtPanel(PaintMode paintMode){ + this.paintMode = paintMode; + + if (paintMode == PaintMode.Accelerated){ + setIgnoreRepaint(true); + } + + addComponentListener(new ComponentAdapter(){ + @Override + public void componentResized(ComponentEvent e) { + synchronized (lock){ + int newWidth2 = Math.max(getWidth(), 1); + int newHeight2 = Math.max(getHeight(), 1); + if (newWidth != newWidth2 || newHeight != newHeight2){ + newWidth = newWidth2; + newHeight = newHeight2; + reshapeNeeded.set(true); + System.out.println("EDT: componentResized " + newWidth + ", " + newHeight); + } + } + } + }); + } + + @Override + public void addNotify(){ + super.addNotify(); + + synchronized (lock){ + hasNativePeer.set(true); + System.out.println("EDT: addNotify"); + } + + requestFocusInWindow(); + } + + @Override + public void removeNotify(){ + synchronized (lock){ + hasNativePeer.set(false); + System.out.println("EDT: removeNotify"); + } + + super.removeNotify(); + } + + @Override + public void paint(Graphics g){ + Graphics2D g2d = (Graphics2D) g; + synchronized (lock){ + g2d.drawImage(img, transformOp, 0, 0); + } + } + + public boolean checkVisibilityState(){ + if (!hasNativePeer.get()){ + if (strategy != null){ +// strategy.dispose(); + strategy = null; + System.out.println("OGL: Not visible. Destroy strategy."); + } + return false; + } + + boolean currentShowing = isShowing(); + if (showing.getAndSet(currentShowing) != currentShowing){ + if (currentShowing){ + System.out.println("OGL: Enter showing state."); + }else{ + System.out.println("OGL: Exit showing state."); + } + } + return currentShowing; + } + + public void repaintInThread(){ + // Convert screenshot. + byteBuf.clear(); + rm.getRenderer().readFrameBuffer(fb, byteBuf); + + synchronized (lock){ + // All operations on img must be synchronized + // as it is accessed from EDT. + Screenshots.convertScreenShot2(intBuf, img); + repaint(); + } + } + + public void drawFrameInThread(){ + // Convert screenshot. + byteBuf.clear(); + rm.getRenderer().readFrameBuffer(fb, byteBuf); + Screenshots.convertScreenShot2(intBuf, img); + + synchronized (lock){ + // All operations on strategy should be synchronized (?) + if (strategy == null){ + try { + createBufferStrategy(1, + new BufferCapabilities( + new ImageCapabilities(true), + new ImageCapabilities(true), + BufferCapabilities.FlipContents.UNDEFINED) + ); + } catch (AWTException ex) { + ex.printStackTrace(); + } + strategy = getBufferStrategy(); + System.out.println("OGL: Visible. Create strategy."); + } + + // Draw screenshot. + do { + do { + Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics(); + if (g2d == null){ + System.out.println("OGL: DrawGraphics was null."); + return; + } + + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_SPEED); + + g2d.drawImage(img, transformOp, 0, 0); + g2d.dispose(); + strategy.show(); + } while (strategy.contentsRestored()); + } while (strategy.contentsLost()); + } + } + + public boolean isActiveDrawing(){ + return paintMode != PaintMode.OnRequest && showing.get(); + } + + public void attachTo(boolean overrideMainFramebuffer, ViewPort ... vps){ + if (viewPorts.size() > 0){ + for (ViewPort vp : viewPorts){ + vp.setOutputFrameBuffer(null); + } + viewPorts.get(viewPorts.size()-1).removeProcessor(this); + } + + viewPorts.addAll(Arrays.asList(vps)); + viewPorts.get(viewPorts.size()-1).addProcessor(this); + + this.attachAsMain = overrideMainFramebuffer; + } + + public void initialize(RenderManager rm, ViewPort vp) { + if (this.rm == null){ + // First time called in OGL thread + this.rm = rm; + reshapeInThread(1, 1); + } + } + + private void reshapeInThread(int width, int height) { + byteBuf = BufferUtils.ensureLargeEnough(byteBuf, width * height * 4); + intBuf = byteBuf.asIntBuffer(); + + fb = new FrameBuffer(width, height, 1); + fb.setDepthBuffer(Format.Depth); + fb.setColorBuffer(Format.RGB8); + + if (attachAsMain){ + rm.getRenderer().setMainFrameBufferOverride(fb); + } + + synchronized (lock){ + img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + } + +// synchronized (lock){ +// img = (BufferedImage) getGraphicsConfiguration().createCompatibleImage(width, height); +// } + + AffineTransform tx = AffineTransform.getScaleInstance(1, -1); + tx.translate(0, -img.getHeight()); + transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + + for (ViewPort vp : viewPorts){ + if (!attachAsMain){ + vp.setOutputFrameBuffer(fb); + } + vp.getCamera().resize(width, height, true); + + // NOTE: Hack alert. This is done ONLY for custom framebuffers. + // Main framebuffer should use RenderManager.notifyReshape(). + for (SceneProcessor sp : vp.getProcessors()){ + sp.reshape(vp, width, height); + } + } + } + + public boolean isInitialized() { + return fb != null; + } + + public void preFrame(float tpf) { + } + + public void postQueue(RenderQueue rq) { + } + + @Override + public void invalidate(){ + // For "PaintMode.OnDemand" only. + repaintRequest.set(true); + } + + public void postFrame(FrameBuffer out) { + if (!attachAsMain && out != fb){ + throw new IllegalStateException("Why did you change the output framebuffer?"); + } + + if (reshapeNeeded.getAndSet(false)){ + reshapeInThread(newWidth, newHeight); + }else{ + if (!checkVisibilityState()){ + return; + } + + switch (paintMode){ + case Accelerated: + drawFrameInThread(); + break; + case Repaint: + repaintInThread(); + break; + case OnRequest: + if (repaintRequest.getAndSet(false)){ + repaintInThread(); + } + break; + } + } + } + + public void reshape(ViewPort vp, int w, int h) { + } + + public void cleanup() { + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java new file mode 100644 index 000000000..9f94e9231 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system.awt; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; +import com.jme3.input.awt.AwtKeyInput; +import com.jme3.input.awt.AwtMouseInput; +import com.jme3.renderer.Renderer; +import com.jme3.system.*; +import java.util.ArrayList; + +public class AwtPanelsContext implements JmeContext { + + protected JmeContext actualContext; + protected AppSettings settings = new AppSettings(true); + protected SystemListener listener; + protected ArrayList panels = new ArrayList(); + protected AwtPanel inputSource; + + protected AwtMouseInput mouseInput = new AwtMouseInput(); + protected AwtKeyInput keyInput = new AwtKeyInput(); + + protected boolean lastThrottleState = false; + + private class AwtPanelsListener implements SystemListener { + + public void initialize() { + initInThread(); + } + + public void reshape(int width, int height) { + throw new IllegalStateException(); + } + + public void update() { + updateInThread(); + } + + public void requestClose(boolean esc) { + // shouldn't happen + throw new IllegalStateException(); + } + + public void gainFocus() { + // shouldn't happen + throw new IllegalStateException(); + } + + public void loseFocus() { + // shouldn't happen + throw new IllegalStateException(); + } + + public void handleError(String errorMsg, Throwable t) { + listener.handleError(errorMsg, t); + } + + public void destroy() { + destroyInThread(); + } + } + + public void setInputSource(AwtPanel panel){ + if (!panels.contains(panel)) + throw new IllegalArgumentException(); + + inputSource = panel; + mouseInput.setInputSource(panel); + keyInput.setInputSource(panel); + } + + public Type getType() { + return Type.OffscreenSurface; + } + + public void setSystemListener(SystemListener listener) { + this.listener = listener; + } + + public AppSettings getSettings() { + return settings; + } + + public Renderer getRenderer() { + return actualContext.getRenderer(); + } + + public MouseInput getMouseInput() { + return mouseInput; + } + + public KeyInput getKeyInput() { + return keyInput; + } + + public JoyInput getJoyInput() { + return null; + } + + public TouchInput getTouchInput() { + return null; + } + + public Timer getTimer() { + return actualContext.getTimer(); + } + + public boolean isCreated() { + return actualContext != null && actualContext.isCreated(); + } + + public boolean isRenderable() { + return actualContext != null && actualContext.isRenderable(); + } + + public AwtPanelsContext(){ + } + + public AwtPanel createPanel(PaintMode paintMode){ + AwtPanel panel = new AwtPanel(paintMode); + panels.add(panel); + return panel; + } + + private void initInThread(){ + listener.initialize(); + } + + private void updateInThread(){ + // Check if throttle required + boolean needThrottle = true; + + for (AwtPanel panel : panels){ + if (panel.isActiveDrawing()){ + needThrottle = false; + break; + } + } + + if (lastThrottleState != needThrottle){ + lastThrottleState = needThrottle; + if (lastThrottleState){ + System.out.println("OGL: Throttling update loop."); + }else{ + System.out.println("OGL: Ceased throttling update loop."); + } + } + + if (needThrottle){ + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + } + } + + listener.update(); + } + + private void destroyInThread(){ + listener.destroy(); + } + + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + this.settings.setRenderer(AppSettings.LWJGL_OPENGL2); + if (actualContext != null){ + actualContext.setSettings(settings); + } + } + + public void create(boolean waitFor) { + if (actualContext != null){ + throw new IllegalStateException("Already created"); + } + + actualContext = JmeSystem.newContext(settings, Type.OffscreenSurface); + actualContext.setSystemListener(new AwtPanelsListener()); + actualContext.create(waitFor); + } + + public void destroy(boolean waitFor) { + if (actualContext == null) + throw new IllegalStateException("Not created"); + + // destroy parent context + actualContext.destroy(waitFor); + } + + public void setTitle(String title) { + // not relevant, ignore + } + + public void setAutoFlushFrames(boolean enabled) { + // not relevant, ignore + } + + public void restart() { + // only relevant if changing pixel format. + } + +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/awt/PaintMode.java b/jme3-desktop/src/main/java/com/jme3/system/awt/PaintMode.java new file mode 100644 index 000000000..10ddb4638 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/PaintMode.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system.awt; + +public enum PaintMode { + Accelerated, + Repaint, + OnRequest; +} diff --git a/jme3-desktop/src/main/java/com/jme3/texture/plugins/AWTLoader.java b/jme3-desktop/src/main/java/com/jme3/texture/plugins/AWTLoader.java new file mode 100644 index 000000000..9b5c8f1da --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/texture/plugins/AWTLoader.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoadException; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.TextureKey; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.image.*; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import javax.imageio.ImageIO; + +public class AWTLoader implements AssetLoader { + + public static final ColorModel AWT_RGBA4444 = new DirectColorModel(16, + 0xf000, + 0x0f00, + 0x00f0, + 0x000f); + + public static final ColorModel AWT_RGBA5551 + = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), + new int[]{5, 5, 5, 1}, + true, + false, + Transparency.BITMASK, + DataBuffer.TYPE_BYTE); + + private Object extractImageData(BufferedImage img){ + DataBuffer buf = img.getRaster().getDataBuffer(); + switch (buf.getDataType()){ + case DataBuffer.TYPE_BYTE: + DataBufferByte byteBuf = (DataBufferByte) buf; + return byteBuf.getData(); + case DataBuffer.TYPE_USHORT: + DataBufferUShort shortBuf = (DataBufferUShort) buf; + return shortBuf.getData(); + } + return null; + } + + private void flipImage(byte[] img, int width, int height, int bpp){ + int scSz = (width * bpp) / 8; + byte[] sln = new byte[scSz]; + int y2 = 0; + for (int y1 = 0; y1 < height / 2; y1++){ + y2 = height - y1 - 1; + System.arraycopy(img, y1 * scSz, sln, 0, scSz); + System.arraycopy(img, y2 * scSz, img, y1 * scSz, scSz); + System.arraycopy(sln, 0, img, y2 * scSz, scSz); + } + } + + private void flipImage(short[] img, int width, int height, int bpp){ + int scSz = (width * bpp) / 8; + scSz /= 2; // Because shorts are 2 bytes + short[] sln = new short[scSz]; + int y2 = 0; + for (int y1 = 0; y1 < height / 2; y1++){ + y2 = height - y1 - 1; + System.arraycopy(img, y1 * scSz, sln, 0, scSz); + System.arraycopy(img, y2 * scSz, img, y1 * scSz, scSz); + System.arraycopy(sln, 0, img, y2 * scSz, scSz); + } + } + + public Image load(BufferedImage img, boolean flipY){ + int width = img.getWidth(); + int height = img.getHeight(); + + switch (img.getType()){ + case BufferedImage.TYPE_4BYTE_ABGR: // most common in PNG images w/ alpha + byte[] dataBuf1 = (byte[]) extractImageData(img); + if (flipY) + flipImage(dataBuf1, width, height, 32); + + ByteBuffer data1 = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*4); + data1.put(dataBuf1); + return new Image(Format.ABGR8, width, height, data1); + case BufferedImage.TYPE_3BYTE_BGR: // most common in JPEG images + byte[] dataBuf2 = (byte[]) extractImageData(img); + if (flipY) + flipImage(dataBuf2, width, height, 24); + + ByteBuffer data2 = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*3); + data2.put(dataBuf2); + return new Image(Format.BGR8, width, height, data2); + case BufferedImage.TYPE_BYTE_GRAY: // grayscale fonts + byte[] dataBuf3 = (byte[]) extractImageData(img); + if (flipY) + flipImage(dataBuf3, width, height, 8); + ByteBuffer data3 = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()); + data3.put(dataBuf3); + return new Image(Format.Luminance8, width, height, data3); + case BufferedImage.TYPE_USHORT_GRAY: // grayscale heightmap + short[] dataBuf4 = (short[]) extractImageData(img); + if (flipY) + flipImage(dataBuf4, width, height, 16); + + ByteBuffer data4 = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*2); + data4.asShortBuffer().put(dataBuf4); + return new Image(Format.Luminance16, width, height, data4); + default: + break; + } + + if (img.getTransparency() == Transparency.OPAQUE){ + ByteBuffer data = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*3); + // no alpha + for (int y = 0; y < height; y++){ + for (int x = 0; x < width; x++){ + int ny = y; + if (flipY){ + ny = height - y - 1; + } + + int rgb = img.getRGB(x,ny); + byte r = (byte) ((rgb & 0x00FF0000) >> 16); + byte g = (byte) ((rgb & 0x0000FF00) >> 8); + byte b = (byte) ((rgb & 0x000000FF)); + data.put(r).put(g).put(b); + } + } + data.flip(); + return new Image(Format.RGB8, width, height, data); + }else{ + ByteBuffer data = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*4); + // no alpha + for (int y = 0; y < height; y++){ + for (int x = 0; x < width; x++){ + int ny = y; + if (flipY){ + ny = height - y - 1; + } + + int rgb = img.getRGB(x,ny); + byte a = (byte) ((rgb & 0xFF000000) >> 24); + byte r = (byte) ((rgb & 0x00FF0000) >> 16); + byte g = (byte) ((rgb & 0x0000FF00) >> 8); + byte b = (byte) ((rgb & 0x000000FF)); + data.put(r).put(g).put(b).put(a); + } + } + data.flip(); + return new Image(Format.RGBA8, width, height, data); + } + } + + public Image load(InputStream in, boolean flipY) throws IOException{ + ImageIO.setUseCache(false); + BufferedImage img = ImageIO.read(in); + if (img == null){ + return null; + } + return load(img, flipY); + } + + public Object load(AssetInfo info) throws IOException { + if (ImageIO.getImageReadersBySuffix(info.getKey().getExtension()) != null){ + boolean flip = ((TextureKey) info.getKey()).isFlipY(); + InputStream in = null; + try { + in = info.openStream(); + Image img = load(in, flip); + if (img == null){ + throw new AssetLoadException("The given image cannot be loaded " + info.getKey()); + } + return img; + } finally { + if (in != null){ + in.close(); + } + } + }else{ + throw new AssetLoadException("The extension " + info.getKey().getExtension() + " is not supported"); + } + } +} diff --git a/jme3-desktop/src/main/java/com/jme3/util/Screenshots.java b/jme3-desktop/src/main/java/com/jme3/util/Screenshots.java new file mode 100644 index 000000000..e8959ad1b --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/util/Screenshots.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.awt.image.WritableRaster; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +public final class Screenshots { + + public static void convertScreenShot2(IntBuffer bgraBuf, BufferedImage out){ + WritableRaster wr = out.getRaster(); + DataBufferInt db = (DataBufferInt) wr.getDataBuffer(); + + int[] cpuArray = db.getData(); + + bgraBuf.clear(); + bgraBuf.get(cpuArray); + +// int width = wr.getWidth(); +// int height = wr.getHeight(); +// +// // flip the components the way AWT likes them +// for (int y = 0; y < height / 2; y++){ +// for (int x = 0; x < width; x++){ +// int inPtr = (y * width + x); +// int outPtr = ((height-y-1) * width + x); +// int pixel = cpuArray[inPtr]; +// cpuArray[inPtr] = cpuArray[outPtr]; +// cpuArray[outPtr] = pixel; +// } +// } + } + + /** + * Flips the image along the Y axis and converts BGRA to ABGR + * + * @param bgraBuf + * @param out + */ + 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/jme3-desktop/src/main/java/jme3tools/converters/ImageToAwt.java b/jme3-desktop/src/main/java/jme3tools/converters/ImageToAwt.java new file mode 100644 index 000000000..084dac8d4 --- /dev/null +++ b/jme3-desktop/src/main/java/jme3tools/converters/ImageToAwt.java @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.converters; + +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.plugins.AWTLoader; +import com.jme3.util.BufferUtils; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.image.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.EnumMap; + +public class ImageToAwt { + + private static final EnumMap params + = new EnumMap(Format.class); + + private static class DecodeParams { + + final int bpp, am, rm, gm, bm, as, rs, gs, bs, im, is; + + public DecodeParams(int bpp, int am, int rm, int gm, int bm, int as, int rs, int gs, int bs, int im, int is) { + this.bpp = bpp; + this.am = am; + this.rm = rm; + this.gm = gm; + this.bm = bm; + this.as = as; + this.rs = rs; + this.gs = gs; + this.bs = bs; + this.im = im; + this.is = is; + } + + public DecodeParams(int bpp, int rm, int rs, int im, int is, boolean alpha){ + this.bpp = bpp; + if (alpha){ + this.am = rm; + this.as = rs; + this.rm = 0; + this.rs = 0; + }else{ + this.rm = rm; + this.rs = rs; + this.am = 0; + this.as = 0; + } + + this.gm = 0; + this.bm = 0; + this.gs = 0; + this.bs = 0; + this.im = im; + this.is = is; + } + + public DecodeParams(int bpp, int rm, int rs, int im, int is){ + this(bpp, rm, rs, im, is, false); + } + } + + static { + final int mx___ = 0xff000000; + final int m_x__ = 0x00ff0000; + final int m__x_ = 0x0000ff00; + final int m___x = 0x000000ff; + final int sx___ = 24; + final int s_x__ = 16; + final int s__x_ = 8; + final int s___x = 0; + final int mxxxx = 0xffffffff; + final int sxxxx = 0; + + final int m4x___ = 0xf000; + final int m4_x__ = 0x0f00; + final int m4__x_ = 0x00f0; + final int m4___x = 0x000f; + final int s4x___ = 12; + final int s4_x__ = 8; + final int s4__x_ = 4; + final int s4___x = 0; + + final int m5___ = 0xf800; + final int m_5__ = 0x07c0; + final int m__5_ = 0x003e; + final int m___1 = 0x0001; + + final int s5___ = 11; + final int s_5__ = 6; + final int s__5_ = 1; + final int s___1 = 0; + + final int m5__ = 0xf800; + final int m_6_ = 0x07e0; + final int m__5 = 0x001f; + + final int s5__ = 11; + final int s_6_ = 5; + final int s__5 = 0; + + final int mxx__ = 0xffff0000; + final int sxx__ = 32; + final int m__xx = 0x0000ffff; + final int s__xx = 0; + + // note: compressed, depth, or floating point formats not included here.. + + params.put(Format.ABGR8, new DecodeParams(4, mx___, m___x, m__x_, m_x__, + sx___, s___x, s__x_, s_x__, + mxxxx, sxxxx)); + params.put(Format.ARGB4444, new DecodeParams(2, m4x___, m4_x__, m4__x_, m4___x, + s4x___, s4_x__, s4__x_, s4___x, + mxxxx, sxxxx)); + params.put(Format.Alpha16, new DecodeParams(2, mxxxx, sxxxx, mxxxx, sxxxx, true)); + params.put(Format.Alpha8, new DecodeParams(1, mxxxx, sxxxx, mxxxx, sxxxx, true)); + params.put(Format.BGR8, new DecodeParams(3, 0, m___x, m__x_, m_x__, + 0, s___x, s__x_, s_x__, + mxxxx, sxxxx)); + params.put(Format.Luminance16, new DecodeParams(2, mxxxx, sxxxx, mxxxx, sxxxx, false)); + params.put(Format.Luminance8, new DecodeParams(1, mxxxx, sxxxx, mxxxx, sxxxx, false)); + params.put(Format.Luminance16Alpha16, new DecodeParams(4, m__xx, mxx__, 0, 0, + s__xx, sxx__, 0, 0, + mxxxx, sxxxx)); + params.put(Format.Luminance16F, new DecodeParams(2, mxxxx, sxxxx, mxxxx, sxxxx, false)); + params.put(Format.Luminance16FAlpha16F, new DecodeParams(4, m__xx, mxx__, 0, 0, + s__xx, sxx__, 0, 0, + mxxxx, sxxxx)); + params.put(Format.Luminance32F, new DecodeParams(4, mxxxx, sxxxx, mxxxx, sxxxx, false)); + params.put(Format.Luminance8, new DecodeParams(1, mxxxx, sxxxx, mxxxx, sxxxx, false)); + params.put(Format.RGB5A1, new DecodeParams(2, m___1, m5___, m_5__, m__5_, + s___1, s5___, s_5__, s__5_, + mxxxx, sxxxx)); + params.put(Format.RGB565, new DecodeParams(2, 0, m5__ , m_6_ , m__5, + 0, s5__ , s_6_ , s__5, + mxxxx, sxxxx)); + params.put(Format.RGB8, new DecodeParams(3, 0, m_x__, m__x_, m___x, + 0, s_x__, s__x_, s___x, + mxxxx, sxxxx)); + params.put(Format.RGBA8, new DecodeParams(4, m___x, mx___, m_x__, m__x_, + s___x, sx___, s_x__, s__x_, + mxxxx, sxxxx)); + } + + private static int Ix(int x, int y, int w){ + return y * w + x; + } + + private static int readPixel(ByteBuffer buf, int idx, int bpp){ + buf.position(idx); + int original = buf.get() & 0xff; + while ((--bpp) > 0){ + original = (original << 8) | (buf.get() & 0xff); + } + return original; + } + + private static void writePixel(ByteBuffer buf, int idx, int pixel, int bpp){ + buf.position(idx); + while ((--bpp) >= 0){ +// pixel = pixel >> 8; + byte bt = (byte) ((pixel >> (bpp * 8)) & 0xff); +// buf.put( (byte) (pixel & 0xff) ); + buf.put(bt); + } + } + + + /** + * Convert an AWT image to jME image. + */ + public static void convert(BufferedImage image, Format format, ByteBuffer buf){ + DecodeParams p = params.get(format); + if (p == null) + throw new UnsupportedOperationException("Image format " + format + " is not supported"); + + int width = image.getWidth(); + int height = image.getHeight(); + + boolean alpha = true; + boolean luminance = false; + + int reductionA = 8 - Integer.bitCount(p.am); + int reductionR = 8 - Integer.bitCount(p.rm); + int reductionG = 8 - Integer.bitCount(p.gm); + int reductionB = 8 - Integer.bitCount(p.bm); + + int initialPos = buf.position(); + for (int y = 0; y < height; y++){ + for (int x = 0; x < width; x++){ + // Get ARGB + int argb = image.getRGB(x, y); + + // Extract color components + int a = (argb & 0xff000000) >> 24; + int r = (argb & 0x00ff0000) >> 16; + int g = (argb & 0x0000ff00) >> 8; + int b = (argb & 0x000000ff); + + // Remove anything after 8 bits + a = a & 0xff; + r = r & 0xff; + g = g & 0xff; + b = b & 0xff; + + // Set full alpha if target image has no alpha + if (!alpha) + a = 0xff; + + // Convert color to luminance if target + // image is in luminance format + if (luminance){ + // convert RGB to luminance + } + + // Do bit reduction, assumes proper rounding has already been + // done. + a = a >> reductionA; + r = r >> reductionR; + g = g >> reductionG; + b = b >> reductionB; + + // Put components into appropriate positions + a = (a << p.as) & p.am; + r = (r << p.rs) & p.rm; + g = (g << p.gs) & p.gm; + b = (b << p.bs) & p.bm; + + int outputPixel = ((a | r | g | b) << p.is) & p.im; + int i = initialPos + (Ix(x,y,width) * p.bpp); + writePixel(buf, i, outputPixel, p.bpp); + } + } + } + + private static final double LOG2 = Math.log(2); + + public static void createData(Image image, boolean mipmaps){ + int bpp = image.getFormat().getBitsPerPixel(); + int w = image.getWidth(); + int h = image.getHeight(); + if (!mipmaps){ + image.setData(BufferUtils.createByteBuffer(w*h*bpp/8)); + return; + } + int expectedMipmaps = 1 + (int) Math.ceil(Math.log(Math.max(h, w)) / LOG2); + int[] mipMapSizes = new int[expectedMipmaps]; + int total = 0; + for (int i = 0; i < mipMapSizes.length; i++){ + int size = (w * h * bpp) / 8; + total += size; + mipMapSizes[i] = size; + w /= 2; + h /= 2; + } + image.setMipMapSizes(mipMapSizes); + image.setData(BufferUtils.createByteBuffer(total)); + } + + /** + * Convert the image from the given format to the output format. + * It is assumed that both images have buffers with the appropriate + * number of elements and that both have the same dimensions. + * + * @param input + * @param output + */ + public static void convert(Image input, Image output){ + DecodeParams inParams = params.get(input.getFormat()); + DecodeParams outParams = params.get(output.getFormat()); + + if (inParams == null || outParams == null) + throw new UnsupportedOperationException(); + + int width = input.getWidth(); + int height = input.getHeight(); + + if (width != output.getWidth() || height != output.getHeight()) + throw new IllegalArgumentException(); + + ByteBuffer inData = input.getData(0); + + boolean inAlpha = false; + boolean inLum = false; + boolean inRGB = false; + if (inParams.am != 0) { + inAlpha = true; + } + + if (inParams.rm != 0 && inParams.gm == 0 && inParams.bm == 0) { + inLum = true; + } else if (inParams.rm != 0 && inParams.gm != 0 && inParams.bm != 0) { + inRGB = true; + } + + int expansionA = 8 - Integer.bitCount(inParams.am); + int expansionR = 8 - Integer.bitCount(inParams.rm); + int expansionG = 8 - Integer.bitCount(inParams.gm); + int expansionB = 8 - Integer.bitCount(inParams.bm); + + int inputPixel; + for (int y = 0; y < height; y++){ + for (int x = 0; x < width; x++){ + int i = Ix(x, y, width) * inParams.bpp; + inputPixel = (readPixel(inData, i, inParams.bpp) & inParams.im) >> inParams.is; + + int a = (inputPixel & inParams.am) >> inParams.as; + int r = (inputPixel & inParams.rm) >> inParams.rs; + int g = (inputPixel & inParams.gm) >> inParams.gs; + int b = (inputPixel & inParams.bm) >> inParams.bs; + + r = r & 0xff; + g = g & 0xff; + b = b & 0xff; + a = a & 0xff; + + a = a << expansionA; + r = r << expansionR; + g = g << expansionG; + b = b << expansionB; + + if (inLum) + b = g = r; + + if (!inAlpha) + a = 0xff; + +// int argb = (a << 24) | (r << 16) | (g << 8) | b; +// out.setRGB(x, y, argb); + } + } + } + + public static BufferedImage convert(Image image, boolean do16bit, boolean fullalpha, int mipLevel){ + Format format = image.getFormat(); + DecodeParams p = params.get(image.getFormat()); + if (p == null) + throw new UnsupportedOperationException(); + + int width = image.getWidth(); + int height = image.getHeight(); + + int level = mipLevel; + while (--level >= 0){ + width /= 2; + height /= 2; + } + + ByteBuffer buf = image.getData(0); + buf.order(ByteOrder.LITTLE_ENDIAN); + + BufferedImage out; + + boolean alpha = false; + boolean luminance = false; + boolean rgb = false; + if (p.am != 0) + alpha = true; + + if (p.rm != 0 && p.gm == 0 && p.bm == 0) + luminance = true; + else if (p.rm != 0 && p.gm != 0 && p.bm != 0) + rgb = true; + + // alpha OR luminance but not both + if ( (alpha && !rgb && !luminance) || (luminance && !alpha && !rgb) ){ + out = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); + }else if ( (rgb && alpha) || (luminance && alpha) ){ + if (do16bit){ + if (fullalpha){ + ColorModel model = AWTLoader.AWT_RGBA4444; + WritableRaster raster = model.createCompatibleWritableRaster(width, width); + out = new BufferedImage(model, raster, false, null); + }else{ + // RGB5_A1 + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + int[] nBits = {5, 5, 5, 1}; + int[] bOffs = {0, 1, 2, 3}; + ColorModel colorModel = new ComponentColorModel(cs, nBits, true, false, + Transparency.BITMASK, + DataBuffer.TYPE_BYTE); + WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, + width, height, + width*2, 2, + bOffs, null); + out = new BufferedImage(colorModel, raster, false, null); + } + }else{ + out = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + } + }else{ + if (do16bit){ + out = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_565_RGB); + }else{ + out = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + } + } + + int expansionA = 8 - Integer.bitCount(p.am); + int expansionR = 8 - Integer.bitCount(p.rm); + int expansionG = 8 - Integer.bitCount(p.gm); + int expansionB = 8 - Integer.bitCount(p.bm); + + if (expansionR < 0){ + expansionR = 0; + } + + int mipPos = 0; + for (int i = 0; i < mipLevel; i++){ + mipPos += image.getMipMapSizes()[i]; + } + int inputPixel; + for (int y = 0; y < height; y++){ + for (int x = 0; x < width; x++){ + int i = mipPos + (Ix(x,y,width) * p.bpp); + inputPixel = (readPixel(buf,i,p.bpp) & p.im) >> p.is; + int a = (inputPixel & p.am) >> p.as; + int r = (inputPixel & p.rm) >> p.rs; + int g = (inputPixel & p.gm) >> p.gs; + int b = (inputPixel & p.bm) >> p.bs; + + r = r & 0xff; + g = g & 0xff; + b = b & 0xff; + a = a & 0xff; + + a = a << expansionA; + r = r << expansionR; + g = g << expansionG; + b = b << expansionB; + + if (luminance) + b = g = r; + + if (!alpha) + a = 0xff; + + int argb = (a << 24) | (r << 16) | (g << 8) | b; + out.setRGB(x, y, argb); + } + } + + return out; + } + +} diff --git a/jme3-desktop/src/main/java/jme3tools/converters/MipMapGenerator.java b/jme3-desktop/src/main/java/jme3tools/converters/MipMapGenerator.java new file mode 100644 index 000000000..cfcff0a94 --- /dev/null +++ b/jme3-desktop/src/main/java/jme3tools/converters/MipMapGenerator.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.converters; + +import com.jme3.math.FastMath; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.plugins.AWTLoader; +import com.jme3.util.BufferUtils; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +public class MipMapGenerator { + + private static BufferedImage scaleDown(BufferedImage sourceImage, int targetWidth, int targetHeight) { + int sourceWidth = sourceImage.getWidth(); + int sourceHeight = sourceImage.getHeight(); + + BufferedImage targetImage = new BufferedImage(targetWidth, targetHeight, sourceImage.getType()); + + Graphics2D g = targetImage.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.drawImage(sourceImage, 0, 0, targetWidth, targetHeight, 0, 0, sourceWidth, sourceHeight, null); + g.dispose(); + + return targetImage; + } + + public static void resizeToPowerOf2(Image image){ + BufferedImage original = ImageToAwt.convert(image, false, true, 0); + int potWidth = FastMath.nearestPowerOfTwo(image.getWidth()); + int potHeight = FastMath.nearestPowerOfTwo(image.getHeight()); + int potSize = Math.max(potWidth, potHeight); + + BufferedImage scaled = scaleDown(original, potSize, potSize); + + AWTLoader loader = new AWTLoader(); + Image output = loader.load(scaled, false); + + image.setWidth(potSize); + image.setHeight(potSize); + image.setDepth(0); + image.setData(output.getData(0)); + image.setFormat(output.getFormat()); + image.setMipMapSizes(null); + } + + public static void generateMipMaps(Image image){ + BufferedImage original = ImageToAwt.convert(image, false, true, 0); + int width = original.getWidth(); + int height = original.getHeight(); + int level = 0; + + BufferedImage current = original; + AWTLoader loader = new AWTLoader(); + ArrayList 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/jme3-desktop/src/main/java/jme3tools/navigation/Coordinate.java b/jme3-desktop/src/main/java/jme3tools/navigation/Coordinate.java new file mode 100644 index 000000000..0a8e82360 --- /dev/null +++ b/jme3-desktop/src/main/java/jme3tools/navigation/Coordinate.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.navigation; + +import java.text.DecimalFormat; + +/** + * Coordinate class. Used to store a coordinate in [DD]D MM.M format. + * + * @author Benjamin Jakobus (based on JMarine by Benjamin Jakobus and Cormac Gebruers) + * @version 1.0 + * @since 1.0 + */ +public class Coordinate { + + /* the degree part of the position (+ N/E, -W/S) */ + private int deg; + + /* the decimals of a minute */ + private double minsDecMins; + + /* the coordinate as a decimal*/ + private double decCoordinate; + + /* whether this coordinate is a latitude or a longitude: : LAT==0, LONG==1 */ + private int coOrdinate; + + /* The minutes trailing decimal precision to use for positions */ + public static final int MINPRECISION = 4; + /* The degrees trailing decimal precision to use for positions */ + public static final int DEGPRECISION = 7; + + /* typeDefs for coOrdinates */ + public static final int LAT = 0; + public static final int LNG = 1; + + /* typeDefs for quadrant */ + public static final int E = 0; + public static final int S = 1; + public static final int W = 2; + public static final int N = 3; + + /** + * Constructor + * + * @param deg + * @param minsDecMins + * @param coOrdinate + * @param quad + * @throws InvalidPositionException + * @since 1.0 + */ + public Coordinate(int deg, float minsDecMins, int coOrdinate, + int quad) throws InvalidPositionException { + buildCoOrdinate(deg, minsDecMins, coOrdinate, quad); + if (verify()) { + } else { + throw new InvalidPositionException(); + } + } + + /** + * Constructor + * @param decCoordinate + * @param coOrdinate + * @throws InvalidPositionException + * @since 1.0 + */ + public Coordinate(double decCoordinate, int coOrdinate) throws InvalidPositionException { + DecimalFormat form = new DecimalFormat("#.#######"); + + this.decCoordinate = decCoordinate; + this.coOrdinate = coOrdinate; + if (verify()) { + deg = new Float(decCoordinate).intValue(); + if (deg < 0) { + minsDecMins = Double.parseDouble(form.format((Math.abs(decCoordinate) - Math.abs(deg)) * 60)); + } else { + minsDecMins = Double.parseDouble(form.format((decCoordinate - deg) * 60)); + } + } else { + throw new InvalidPositionException(); + } + } + + /** + * This constructor takes a coordinate in the ALRS formats i.e + * 38∞31.64'N for lat, and 28∞19.12'W for long + * Note: ALRS positions are occasionally written with the decimal minutes + * apostrophe in the 'wrong' place and with an non CP1252 compliant decimal character. + * This issue has to be corrected in the source database + * @param coOrdinate + * @throws InvalidPositionException + * @since 1.0 + */ + public Coordinate(String coOrdinate) throws InvalidPositionException { + //firstly split it into its component parts and dispose of the unneeded characters + String[] items = coOrdinate.split("°"); + int deg = Integer.valueOf(items[0]); + + items = items[1].split("'"); + float minsDecMins = Float.valueOf(items[0]); + char quad = items[1].charAt(0); + + switch (quad) { + case 'N': + buildCoOrdinate(deg, minsDecMins, Coordinate.LAT, Coordinate.N); + break; + case 'S': + buildCoOrdinate(deg, minsDecMins, Coordinate.LAT, Coordinate.S); + break; + case 'E': + buildCoOrdinate(deg, minsDecMins, Coordinate.LNG, Coordinate.E); + break; + case 'W': + buildCoOrdinate(deg, minsDecMins, Coordinate.LNG, Coordinate.W); + } + if (verify()) { + } else { + throw new InvalidPositionException(); + } + } + + /** + * Prints out a coordinate as a string + * @return the coordinate in decimal format + * @since 1.0 + */ + public String toStringDegMin() { + String str = ""; + String quad = ""; + StringUtil su = new StringUtil(); + switch (coOrdinate) { + case LAT: + if (decCoordinate >= 0) { + quad = "N"; + } else { + quad = "S"; + } + str = su.padNumZero(Math.abs(deg), 2); + str += "\u00b0" + su.padNumZero(Math.abs(minsDecMins), 2, MINPRECISION) + "'" + quad; + break; + case LNG: + if (decCoordinate >= 0) { + quad = "E"; + } else { + quad = "W"; + } + str = su.padNumZero(Math.abs(deg), 3); + str += "\u00b0" + su.padNumZero(Math.abs(minsDecMins), 2, MINPRECISION) + "'" + quad; + break; + } + return str; + } + + /** + * Prints out a coordinate as a string + * @return the coordinate in decimal format + * @since 1.0 + */ + public String toStringDec() { + StringUtil u = new StringUtil(); + switch (coOrdinate) { + case LAT: + return u.padNumZero(decCoordinate, 2, DEGPRECISION); + case LNG: + return u.padNumZero(decCoordinate, 3, DEGPRECISION); + } + return "error"; + } + + /** + * Returns the coordinate's decimal value + * @return float the decimal value of the coordinate + * @since 1.0 + */ + public double decVal() { + return decCoordinate; + } + + /** + * Determines whether a decimal position is valid + * @return result of validity test + * @since 1.0 + */ + private boolean verify() { + switch (coOrdinate) { + case LAT: + if (Math.abs(decCoordinate) > 90.0) { + return false; + } + break; + + case LNG: + if (Math.abs(decCoordinate) > 180) { + return false; + } + } + return true; + } + + /** + * Populate this object by parsing the arguments to the function + * Placed here to allow multiple constructors to use it + * @since 1.0 + */ + private void buildCoOrdinate(int deg, float minsDecMins, int coOrdinate, + int quad) { + NumUtil nu = new NumUtil(); + + switch (coOrdinate) { + case LAT: + switch (quad) { + case N: + this.deg = deg; + this.minsDecMins = minsDecMins; + this.coOrdinate = coOrdinate; + decCoordinate = nu.Round(this.deg + (float) this.minsDecMins / 60, Coordinate.MINPRECISION); + break; + + case S: + this.deg = -deg; + this.minsDecMins = minsDecMins; + this.coOrdinate = coOrdinate; + decCoordinate = nu.Round(this.deg - ((float) this.minsDecMins / 60), Coordinate.MINPRECISION); + } + + case LNG: + switch (quad) { + case E: + this.deg = deg; + this.minsDecMins = minsDecMins; + this.coOrdinate = coOrdinate; + decCoordinate = nu.Round(this.deg + ((float) this.minsDecMins / 60), Coordinate.MINPRECISION); + break; + + case W: + this.deg = -deg; + this.minsDecMins = minsDecMins; + this.coOrdinate = coOrdinate; + decCoordinate = nu.Round(this.deg - ((float) this.minsDecMins / 60), Coordinate.MINPRECISION); + } + } + } +} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/GCSailing.java b/jme3-desktop/src/main/java/jme3tools/navigation/GCSailing.java new file mode 100644 index 000000000..5a5182ba2 --- /dev/null +++ b/jme3-desktop/src/main/java/jme3tools/navigation/GCSailing.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.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/jme3-desktop/src/main/java/jme3tools/navigation/InvalidPositionException.java b/jme3-desktop/src/main/java/jme3tools/navigation/InvalidPositionException.java new file mode 100644 index 000000000..77240ed40 --- /dev/null +++ b/jme3-desktop/src/main/java/jme3tools/navigation/InvalidPositionException.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.navigation; + +/** + * + * @author normenhansen + */ +public class InvalidPositionException extends Exception{ + +} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/MapModel2D.java b/jme3-desktop/src/main/java/jme3tools/navigation/MapModel2D.java new file mode 100644 index 000000000..f3094071a --- /dev/null +++ b/jme3-desktop/src/main/java/jme3tools/navigation/MapModel2D.java @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.navigation; + +import java.awt.Point; +import java.text.DecimalFormat; + +/** + * A representation of the actual map in terms of lat/long and x,y co-ordinates. + * The Map class contains various helper methods such as methods for determining + * the pixel positions for lat/long co-ordinates and vice versa. + * + * @author Cormac Gebruers + * @author Benjamin Jakobus + * @version 1.0 + * @since 1.0 + */ +public class MapModel2D { + + /* The number of radians per degree */ + private final static double RADIANS_PER_DEGREE = 57.2957; + + /* The number of degrees per radian */ + private final static double DEGREES_PER_RADIAN = 0.0174532925; + + /* The map's width in longitude */ + public final static int DEFAULT_MAP_WIDTH_LONGITUDE = 360; + + /* The top right hand corner of the map */ + private Position centre; + + /* The x and y co-ordinates for the viewport's centre */ + private int xCentre; + private int yCentre; + + /* The width (in pixels) of the viewport holding the map */ + private int viewportWidth; + + /* The viewport height in pixels */ + private int viewportHeight; + + /* The number of minutes that one pixel represents */ + private double minutesPerPixel; + + /** + * Constructor + * @param viewportWidth the pixel width of the viewport (component) in which + * the map is displayed + * @since 1.0 + */ + public MapModel2D(int viewportWidth) { + try { + this.centre = new Position(0, 0); + } catch (InvalidPositionException e) { + e.printStackTrace(); + } + + this.viewportWidth = viewportWidth; + + // Calculate the number of minutes that one pixel represents along the longitude + calculateMinutesPerPixel(DEFAULT_MAP_WIDTH_LONGITUDE); + + // Calculate the viewport height based on its width and the number of degrees (85) + // in our map + viewportHeight = ((int) NavCalculator.computeDMPClarkeSpheroid(0, 85) / (int) minutesPerPixel) * 2; +// viewportHeight = viewportWidth; // REMOVE!!! + // Determine the map's x,y centre + xCentre = viewportWidth / 2; + yCentre = viewportHeight / 2; + } + + /** + * Returns the height of the viewport in pixels + * @return the height of the viewport in pixels + * @since 0.1 + */ + public int getViewportPixelHeight() { + return viewportHeight; + } + + /** + * Calculates the number of minutes per pixels using a given + * map width in longitude + * @param mapWidthInLongitude + * @since 1.0 + */ + public void calculateMinutesPerPixel(double mapWidthInLongitude) { + minutesPerPixel = (mapWidthInLongitude * 60) / (double) viewportWidth; + } + + /** + * Returns the width of the viewport in pixels + * @return the width of the viewport in pixels + * @since 0.1 + */ + public int getViewportPixelWidth() { + return viewportWidth; + } + + public void setViewportWidth(int viewportWidth) { + this.viewportWidth = viewportWidth; + } + + public void setViewportHeight(int viewportHeight) { + this.viewportHeight = viewportHeight; + } + + public void setCentre(Position centre) { + this.centre = centre; + } + + /** + * Returns the number of minutes there are per pixel + * @return the number of minutes per pixel + * @since 1.0 + */ + public double getMinutesPerPixel() { + return minutesPerPixel; + } + + public double getMetersPerPixel() { + return 1853 * minutesPerPixel; + } + + public void setMinutesPerPixel(double minutesPerPixel) { + this.minutesPerPixel = minutesPerPixel; + } + + /** + * Converts a latitude/longitude position into a pixel co-ordinate + * @param position the position to convert + * @return {@code Point} a pixel co-ordinate + * @since 1.0 + */ + public Point toPixel(Position position) { + // Get the distance between position and the centre for calculating + // the position's longitude translation + double distance = NavCalculator.computeLongDiff(centre.getLongitude(), + position.getLongitude()); + + // Use the distance from the centre to calculate the pixel x co-ordinate + double distanceInPixels = (distance / minutesPerPixel); + + // Use the difference in meridional parts to calculate the pixel y co-ordinate + double dmp = NavCalculator.computeDMPClarkeSpheroid(centre.getLatitude(), + position.getLatitude()); + + int x = 0; + int y = 0; + + if (centre.getLatitude() == position.getLatitude()) { + y = yCentre; + } + if (centre.getLongitude() == position.getLongitude()) { + x = xCentre; + } + + // Distinguish between northern and southern hemisphere for latitude calculations + if (centre.getLatitude() > 0 && position.getLatitude() > centre.getLatitude()) { + // Centre is north. Position is north of centre + y = yCentre + (int) ((dmp) / minutesPerPixel); + } else if (centre.getLatitude() > 0 && position.getLatitude() < centre.getLatitude()) { + // Centre is north. Position is south of centre + y = yCentre - (int) ((dmp) / minutesPerPixel); + } else if (centre.getLatitude() < 0 && position.getLatitude() > centre.getLatitude()) { + // Centre is south. Position is north of centre + y = yCentre + (int) ((dmp) / minutesPerPixel); + } else if (centre.getLatitude() < 0 && position.getLatitude() < centre.getLatitude()) { + // Centre is south. Position is south of centre + y = yCentre - (int) ((dmp) / minutesPerPixel); + } else if (centre.getLatitude() == 0 && position.getLatitude() > centre.getLatitude()) { + // Centre is at the equator. Position is north of the equator + y = yCentre + (int) ((dmp) / minutesPerPixel); + } else if (centre.getLatitude() == 0 && position.getLatitude() < centre.getLatitude()) { + // Centre is at the equator. Position is south of the equator + y = yCentre - (int) ((dmp) / minutesPerPixel); + } + + // Distinguish between western and eastern hemisphere for longitude calculations + if (centre.getLongitude() < 0 && position.getLongitude() < centre.getLongitude()) { + // Centre is west. Position is west of centre + x = xCentre - (int) distanceInPixels; + } else if (centre.getLongitude() < 0 && position.getLongitude() > centre.getLongitude()) { + // Centre is west. Position is south of centre + x = xCentre + (int) distanceInPixels; + } else if (centre.getLongitude() > 0 && position.getLongitude() < centre.getLongitude()) { + // Centre is east. Position is west of centre + x = xCentre - (int) distanceInPixels; + } else if (centre.getLongitude() > 0 && position.getLongitude() > centre.getLongitude()) { + // Centre is east. Position is east of centre + x = xCentre + (int) distanceInPixels; + } else if (centre.getLongitude() == 0 && position.getLongitude() > centre.getLongitude()) { + // Centre is at the equator. Position is east of centre + x = xCentre + (int) distanceInPixels; + } else if (centre.getLongitude() == 0 && position.getLongitude() < centre.getLongitude()) { + // Centre is at the equator. Position is west of centre + x = xCentre - (int) distanceInPixels; + } + + // Distinguish between northern and souterhn hemisphere for longitude calculations + return new Point(x, y); + } + + /** + * Converts a pixel position into a mercator position + * @param p {@link Point} object that you wish to convert into + * longitude / latiude + * @return the converted {@code Position} object + * @since 1.0 + */ + public Position toPosition(Point p) { + double lat, lon; + Position pos = null; + try { + Point pixelCentre = toPixel(new Position(0, 0)); + + // Get the distance between position and the centre + double xDistance = distance(xCentre, p.getX()); + double yDistance = distance(pixelCentre.getY(), p.getY()); + double lonDistanceInDegrees = (xDistance * minutesPerPixel) / 60; + double mp = (yDistance * minutesPerPixel); + // If we are zoomed in past a certain point, then use linear search. + // Otherwise use binary search + if (getMinutesPerPixel() < 0.05) { + lat = findLat(mp, getCentre().getLatitude()); + if (lat == -1000) { + System.out.println("lat: " + lat); + } + } else { + lat = findLat(mp, 0.0, 85.0); + } + lon = (p.getX() < xCentre ? centre.getLongitude() - lonDistanceInDegrees + : centre.getLongitude() + lonDistanceInDegrees); + + if (p.getY() > pixelCentre.getY()) { + lat = -1 * lat; + } + if (lat == -1000 || lon == -1000) { + return pos; + } + pos = new Position(lat, lon); + } catch (InvalidPositionException ipe) { + ipe.printStackTrace(); + } + return pos; + } + + /** + * Calculates distance between two points on the map in pixels + * @param a + * @param b + * @return distance the distance between a and b in pixels + * @since 1.0 + */ + private double distance(double a, double b) { + return Math.abs(a - b); + } + + /** + * Defines the centre of the map in pixels + * @param p 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 {@link Point} object marking the map's (x,y) centre + * @since 1.0 + */ + public Point getPixelCentre() { + return new Point(xCentre, yCentre); + } + + /** + * Returns the {@code Position} centre of the map + * @return {@code Position} object marking the map's (lat, long) centre + * @since 1.0 + */ + public Position getCentre() { + return centre; + } + + /** + * Uses binary search to find the latitude of a given MP. + * + * @param mp maridian part + * @param low + * @param high + * @return the latitude of the MP value + * @since 1.0 + */ + private double findLat(double mp, double low, double high) { + DecimalFormat form = new DecimalFormat("#.####"); + mp = Math.round(mp); + double midLat = (low + high) / 2.0; + // ctr is used to make sure that with some + // numbers which can't be represented exactly don't inifitely repeat + double guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat); + + while (low <= high) { + if (guessMP == mp) { + return midLat; + } else { + if (guessMP > mp) { + high = midLat - 0.0001; + } else { + low = midLat + 0.0001; + } + } + + midLat = Double.valueOf(form.format(((low + high) / 2.0))); + guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat); + guessMP = Math.round(guessMP); + } + return -1000; + } + + /** + * Uses linear search to find the latitude of a given MP + * @param mp the meridian part for which to find the latitude + * @param previousLat the previous latitude. Used as a upper / lower bound + * @return the latitude of the MP value + */ + private double findLat(double mp, double previousLat) { + DecimalFormat form = new DecimalFormat("#.#####"); + mp = Double.parseDouble(form.format(mp)); + double guessMP; + for (double lat = previousLat - 0.25; lat < previousLat + 1; lat += 0.00001) { + guessMP = NavCalculator.computeDMPClarkeSpheroid(0, lat); + guessMP = Double.parseDouble(form.format(guessMP)); + if (guessMP == mp || Math.abs(guessMP - mp) < 0.001) { + return lat; + } + } + return -1000; + } +} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/MapModel3D.java b/jme3-desktop/src/main/java/jme3tools/navigation/MapModel3D.java new file mode 100644 index 000000000..633d88bfc --- /dev/null +++ b/jme3-desktop/src/main/java/jme3tools/navigation/MapModel3D.java @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.navigation; + +import com.jme3.math.Vector3f; +import java.text.DecimalFormat; + + +/** + * A representation of the actual map in terms of lat/long and x,y,z co-ordinates. + * The Map class contains various helper methods such as methods for determining + * the world unit positions for lat/long coordinates and vice versa. This map projection + * does not handle screen/pixel coordinates. + * + * @author Benjamin Jakobus (thanks to Cormac Gebruers) + * @version 1.0 + * @since 1.0 + */ +public class MapModel3D { + + /* The number of radians per degree */ + private final static double RADIANS_PER_DEGREE = 57.2957; + + /* The number of degrees per radian */ + private final static double DEGREES_PER_RADIAN = 0.0174532925; + + /* The map's width in longitude */ + public final static int DEFAULT_MAP_WIDTH_LONGITUDE = 360; + + /* The top right hand corner of the map */ + private Position centre; + + /* The x and y co-ordinates for the viewport's centre */ + private int xCentre; + private int zCentre; + + /* The width (in world units (wu)) of the viewport holding the map */ + private int worldWidth; + + /* The viewport height in pixels */ + private int worldHeight; + + /* The number of minutes that one pixel represents */ + private double minutesPerWorldUnit; + + /** + * Constructor. + * + * @param worldWidth The world unit width the map's area + * @since 1.0 + */ + public MapModel3D(int worldWidth) { + try { + this.centre = new Position(0, 0); + } catch (InvalidPositionException e) { + e.printStackTrace(); + } + + this.worldWidth = worldWidth; + + // Calculate the number of minutes that one pixel represents along the longitude + calculateMinutesPerWorldUnit(DEFAULT_MAP_WIDTH_LONGITUDE); + + // Calculate the viewport height based on its width and the number of degrees (85) + // in our map + worldHeight = ((int) NavCalculator.computeDMPClarkeSpheroid(0, 85) / (int) minutesPerWorldUnit) * 2; + + // Determine the map's x,y centre + xCentre = 0; + zCentre = 0; +// xCentre = worldWidth / 2; +// zCentre = worldHeight / 2; + } + + /** + * Returns the height of the viewport in pixels. + * + * @return The height of the viewport in pixels. + * @since 1.0 + */ + public int getWorldHeight() { + return worldHeight; + } + + /** + * Calculates the number of minutes per pixels using a given + * map width in longitude. + * + * @param mapWidthInLongitude The map's with in degrees of longitude. + * @since 1.0 + */ + public void calculateMinutesPerWorldUnit(double mapWidthInLongitude) { + // Multiply mapWidthInLongitude by 60 to convert it to minutes. + minutesPerWorldUnit = (mapWidthInLongitude * 60) / (double) worldWidth; + } + + /** + * Returns the width of the viewport in pixels. + * + * @return The width of the viewport in pixels. + * @since 1.0 + */ + public int getWorldWidth() { + return worldWidth; + } + + /** + * Sets the world's desired width. + * + * @param viewportWidth The world's desired width in WU. + * @since 1.0 + */ + public void setWorldWidth(int viewportWidth) { + this.worldWidth = viewportWidth; + } + + /** + * Sets the world's desired height. + * + * @param viewportHeight The world's desired height in WU. + * @since 1.0 + */ + public void setWorldHeight(int viewportHeight) { + this.worldHeight = viewportHeight; + } + + /** + * Sets the map's centre. + * + * @param centre The 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 posVec 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 posVec 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/jme3-desktop/src/main/java/jme3tools/navigation/NavCalculator.java b/jme3-desktop/src/main/java/jme3tools/navigation/NavCalculator.java new file mode 100644 index 000000000..bc4deaf50 --- /dev/null +++ b/jme3-desktop/src/main/java/jme3tools/navigation/NavCalculator.java @@ -0,0 +1,618 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.navigation; + + + +/** + * A utlity class for performing position calculations + * + * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin + * Jakobus) + * @version 1.0 + * @since 1.0 + */ +public class NavCalculator { + + private double distance; + private double trueCourse; + + /* The earth's radius in meters */ + public static final int WGS84_EARTH_RADIUS = 6378137; + private String strCourse; + + /* The sailing calculation type */ + public static final int MERCATOR = 0; + public static final int GC = 1; + + /* The degree precision to use for courses */ + public static final int RL_CRS_PRECISION = 1; + + /* The distance precision to use for distances */ + public static final int RL_DIST_PRECISION = 1; + public static final int METERS_PER_MINUTE = 1852; + + /** + * Constructor + * @param P1 + * @param P2 + * @param calcType + * @since 1.0 + */ + public NavCalculator(Position P1, Position P2, int calcType) { + switch (calcType) { + case MERCATOR: + mercatorSailing(P1, P2); + break; + case GC: + greatCircleSailing(P1, P2); + break; + } + } + + /** + * Constructor + * @since 1.0 + */ + public NavCalculator() { + } + + /** + * Determines a great circle track between two positions + * @param p1 origin position + * @param p2 destination position + */ + public GCSailing greatCircleSailing(Position p1, Position p2) { + return new GCSailing(new int[0], new float[0]); + } + + /** + * Determines a Rhumb Line course and distance between two points + * @param p1 origin position + * @param p2 destination position + */ + public RLSailing rhumbLineSailing(Position p1, Position p2) { + RLSailing rl = mercatorSailing(p1, p2); + return rl; + } + + /** + * Determines the rhumb line course and distance between two positions + * @param p1 origin position + * @param p2 destination position + */ + public RLSailing mercatorSailing(Position p1, Position p2) { + + double dLat = computeDLat(p1.getLatitude(), p2.getLatitude()); + //plane sailing... + if (dLat == 0) { + RLSailing rl = planeSailing(p1, p2); + return rl; + } + + double dLong = computeDLong(p1.getLongitude(), p2.getLongitude()); + double dmp = (float) computeDMPClarkeSpheroid(p1.getLatitude(), p2.getLatitude()); + + trueCourse = (float) Math.toDegrees(Math.atan(dLong / dmp)); + double degCrs = convertCourse((float) trueCourse, p1, p2); + distance = (float) Math.abs(dLat / Math.cos(Math.toRadians(trueCourse))); + + RLSailing rl = new RLSailing(degCrs, (float) distance); + trueCourse = rl.getCourse(); + strCourse = (dLat < 0 ? "S" : "N"); + strCourse += " " + trueCourse; + strCourse += " " + (dLong < 0 ? "W" : "E"); + return rl; + + } + + /** + * Calculate a plane sailing situation - i.e. where Lats are the same + * @param p1 + * @param p2 + * @return + * @since 1.0 + */ + public RLSailing planeSailing(Position p1, Position p2) { + double dLong = computeDLong(p1.getLongitude(), p2.getLongitude()); + + double sgnDLong = 0 - (dLong / Math.abs(dLong)); + if (Math.abs(dLong) > 180 * 60) { + dLong = (360 * 60 - Math.abs(dLong)) * sgnDLong; + } + + double redist = 0; + double recourse = 0; + if (p1.getLatitude() == 0) { + redist = Math.abs(dLong); + } else { + redist = Math.abs(dLong * (float) Math.cos(p1.getLatitude() * 2 * Math.PI / 360)); + } + recourse = (float) Math.asin(0 - sgnDLong); + recourse = recourse * 360 / 2 / (float) Math.PI; + + if (recourse < 0) { + recourse = recourse + 360; + } + return new RLSailing(recourse, redist); + } + + /** + * Converts a course from cardinal XddY to ddd notation + * @param tc + * @param p1 position one + * @param p2 position two + * @return + * @since 1.0 + */ + public static double convertCourse(float tc, Position p1, Position p2) { + + double dLat = p1.getLatitude() - p2.getLatitude(); + double dLong = p1.getLongitude() - p2.getLongitude(); + //NE + if (dLong >= 0 & dLat >= 0) { + return Math.abs(tc); + } + + //SE + if (dLong >= 0 & dLat < 0) { + return 180 - Math.abs(tc); + } + + //SW + if (dLong < 0 & dLat < 0) { + return 180 + Math.abs(tc); + } + + //NW + if (dLong < 0 & dLat >= 0) { + return 360 - Math.abs(tc); + } + return -1; + } + + /** + * Getter method for the distance between two points + * @return distance + * @since 1.0 + */ + public double getDistance() { + return distance; + } + + /** + * Getter method for the true course + * @return true course + * @since 1.0 + */ + public double getTrueCourse() { + return trueCourse; + } + + /** + * Getter method for the true course + * @return true course + * @since 1.0 + */ + public String getStrCourse() { + return strCourse; + } + + /** + * Computes the difference in meridional parts for two latitudes in minutes + * (based on Clark 1880 spheroid) + * @param lat1 + * @param lat2 + * @return difference in minutes + * @since 1.0 + */ + public static double computeDMPClarkeSpheroid(double lat1, double lat2) { + double absLat1 = Math.abs(lat1); + double absLat2 = Math.abs(lat2); + + double m1 = (7915.704468 * (Math.log(Math.tan(Math.toRadians(45 + + (absLat1 / 2)))) / Math.log(10)) + - 23.268932 * Math.sin(Math.toRadians(absLat1)) + - 0.052500 * Math.pow(Math.sin(Math.toRadians(absLat1)), 3) + - 0.000213 * Math.pow(Math.sin(Math.toRadians(absLat1)), 5)); + + double m2 = (7915.704468 * (Math.log(Math.tan(Math.toRadians(45 + + (absLat2 / 2)))) / Math.log(10)) + - 23.268932 * Math.sin(Math.toRadians(absLat2)) + - 0.052500 * Math.pow(Math.sin(Math.toRadians(absLat2)), 3) + - 0.000213 * Math.pow(Math.sin(Math.toRadians(absLat2)), 5)); + if ((lat1 <= 0 && lat2 <= 0) || (lat1 > 0 && lat2 > 0)) { + return Math.abs(m1 - m2); + } else { + return m1 + m2; + } + } + + /** + * Computes the difference in meridional parts for a perfect sphere between + * two degrees of latitude + * @param lat1 + * @param lat2 + * @return difference in meridional parts between lat1 and lat2 in minutes + * @since 1.0 + */ + public static float computeDMPWGS84Spheroid(float lat1, float lat2) { + float absLat1 = Math.abs(lat1); + float absLat2 = Math.abs(lat2); + + float m1 = (float) (7915.7045 * Math.log10(Math.tan(Math.toRadians(45 + (absLat1 / 2)))) + - 23.01358 * Math.sin(absLat1 - 0.05135) * Math.pow(Math.sin(absLat1), 3)); + + float m2 = (float) (7915.7045 * Math.log10(Math.tan(Math.toRadians(45 + (absLat2 / 2)))) + - 23.01358 * Math.sin(absLat2 - 0.05135) * Math.pow(Math.sin(absLat2), 3)); + + if (lat1 <= 0 & lat2 <= 0 || lat1 > 0 & lat2 > 0) { + return Math.abs(m1 - m2); + } else { + return m1 + m2; + } + } + + /** + * Predicts the position of a target for a given time in the future + * @param time the number of seconds from now for which to predict the future + * position + * @param speed the miles per minute that the target is traveling + * @param currentLat the target's current latitude + * @param currentLong the target's current longitude + * @param course the target's current course in degrees + * @return the predicted future position + * @since 1.0 + */ + public static Position predictPosition(int time, double speed, + double currentLat, double currentLong, double course) { + Position futurePosition = null; + course = Math.toRadians(course); + double futureLong = currentLong + speed * time * Math.sin(course); + double futureLat = currentLat + speed * time * Math.cos(course); + try { + futurePosition = new Position(futureLat, futureLong); + } catch (InvalidPositionException ipe) { + ipe.printStackTrace(); + } + return futurePosition; + + } + + /** + * Computes the coordinate of position B relative to an offset given + * a distance and an angle. + * + * @param offset The offset position. + * @param bearing The bearing between the offset and the coordinate + * that you want to calculate. + * @param distance The distance, in meters, between the offset + * and point B. + * @return The position of point B that is located from + * given offset at given distance and angle. + * @since 1.0 + */ + public static Position computePosition(Position initialPos, double heading, + double distance) { + if (initialPos == null) { + return null; + } + double angle; + if (heading < 90) { + angle = heading; + } else if (heading > 90 && heading < 180) { + angle = 180 - heading; + } else if (heading > 180 && heading < 270) { + angle = heading - 180; + } else { + angle = 360 - heading; + } + + Position newPosition = null; + + // Convert meters into nautical miles + distance = distance * 0.000539956803; + angle = Math.toRadians(angle); + double initialLat = initialPos.getLatitude(); + double initialLong = initialPos.getLongitude(); + double dlat = distance * Math.cos(angle); + dlat = dlat / 60; + dlat = Math.abs(dlat); + double newLat = 0; + if ((heading > 270 && heading < 360) || (heading > 0 && heading < 90)) { + newLat = initialLat + dlat; + } else if (heading < 270 && heading > 90) { + newLat = initialLat - dlat; + } + double meanLat = (Math.abs(dlat) / 2.0) + newLat; + double dep = (Math.abs(dlat * 60)) * Math.tan(angle); + double dlong = dep * (1.0 / Math.cos(Math.toRadians(meanLat))); + dlong = dlong / 60; + dlong = Math.abs(dlong); + double newLong; + if (heading > 180 && heading < 360) { + newLong = initialLong - dlong; + } else { + newLong = initialLong + dlong; + } + + if (newLong < -180) { + double diff = Math.abs(newLong + 180); + newLong = 180 - diff; + } + + if (newLong > 180) { + double diff = Math.abs(newLong + 180); + newLong = (180 - diff) * -1; + } + + if (heading == 0 || heading == 360 || heading == 180) { + newLong = initialLong; + newLat = initialLat + dlat; + } else if (heading == 90 || heading == 270) { + newLat = initialLat; +// newLong = initialLong + dlong; THIS WAS THE ORIGINAL (IT WORKED) + newLong = initialLong - dlong; + } + try { + newPosition = new Position(newLat, + newLong); + } catch (InvalidPositionException ipe) { + ipe.printStackTrace(); + System.out.println(newLat + "," + newLong); + } + return newPosition; + } + + /** + * Computes the difference in Longitude between two positions and assigns the + * correct sign -westwards travel, + eastwards travel + * @param lng1 + * @param lng2 + * @return difference in longitude + * @since 1.0 + */ + public static double computeDLong(double lng1, double lng2) { + if (lng1 - lng2 == 0) { + return 0; + } + + // both easterly + if (lng1 >= 0 & lng2 >= 0) { + return -(lng1 - lng2) * 60; + } + //both westerly + if (lng1 < 0 & lng2 < 0) { + return -(lng1 - lng2) * 60; + } + + //opposite sides of Date line meridian + + //sum less than 180 + if (Math.abs(lng1) + Math.abs(lng2) < 180) { + if (lng1 < 0 & lng2 > 0) { + return -(Math.abs(lng1) + Math.abs(lng2)) * 60; + } else { + return Math.abs(lng1) + Math.abs(lng2) * 60; + } + } else { + //sum greater than 180 + if (lng1 < 0 & lng2 > 0) { + return -(360 - (Math.abs(lng1) + Math.abs(lng2))) * 60; + } else { + return (360 - (Math.abs(lng1) + Math.abs(lng2))) * 60; + } + } + } + + /** + * Computes the difference in Longitude between two positions and assigns the + * correct sign -westwards travel, + eastwards travel + * @param lng1 + * @param lng2 + * @return difference in longitude + * @since 1.0 + */ + public static double computeLongDiff(double lng1, double lng2) { + if (lng1 - lng2 == 0) { + return 0; + } + + // both easterly + if (lng1 >= 0 & lng2 >= 0) { + return Math.abs(-(lng1 - lng2) * 60); + } + //both westerly + if (lng1 < 0 & lng2 < 0) { + return Math.abs(-(lng1 - lng2) * 60); + } + + if (lng1 == 0) { + return Math.abs(lng2 * 60); + } + + if (lng2 == 0) { + return Math.abs(lng1 * 60); + } + + return (Math.abs(lng1) + Math.abs(lng2)) * 60; + } + + /** + * Compute the difference in latitude between two positions + * @param lat1 + * @param lat2 + * @return difference in latitude + * @since 1.0 + */ + public static double computeDLat(double lat1, double lat2) { + //same side of equator + + //plane sailing + if (lat1 - lat2 == 0) { + return 0; + } + + //both northerly + if (lat1 >= 0 & lat2 >= 0) { + return -(lat1 - lat2) * 60; + } + //both southerly + if (lat1 < 0 & lat2 < 0) { + return -(lat1 - lat2) * 60; + } + + //opposite sides of equator + if (lat1 >= 0) { + //heading south + return -(Math.abs(lat1) + Math.abs(lat2)); + } else { + //heading north + return (Math.abs(lat1) + Math.abs(lat2)); + } + } + + public static class Quadrant { + + private static final Quadrant FIRST = new Quadrant(1, 1); + private static final Quadrant SECOND = new Quadrant(-1, 1); + private static final Quadrant THIRD = new Quadrant(-1, -1); + private static final Quadrant FOURTH = new Quadrant(1, -1); + private final int lonMultiplier; + private final int latMultiplier; + + public Quadrant(final int xMultiplier, final int yMultiplier) { + this.lonMultiplier = xMultiplier; + this.latMultiplier = yMultiplier; + } + + static Quadrant getQuadrant(double degrees, boolean invert) { + if (invert) { + if (degrees >= 0 && degrees <= 90) { + return FOURTH; + } else if (degrees > 90 && degrees <= 180) { + return THIRD; + } else if (degrees > 180 && degrees <= 270) { + return SECOND; + } + return FIRST; + } else { + if (degrees >= 0 && degrees <= 90) { + return FIRST; + } else if (degrees > 90 && degrees <= 180) { + return SECOND; + } else if (degrees > 180 && degrees <= 270) { + return THIRD; + } + return FOURTH; + } + } + } + + /** + * Converts meters to degrees. + * + * @param meters The meters that you want to convert into degrees. + * @return The degree equivalent of the given meters. + * @since 1.0 + */ + public static double toDegrees(double meters) { + return (meters / METERS_PER_MINUTE) / 60; + } + + /** + * Computes the bearing between two points. + * + * @param p1 + * @param p2 + * @return + * @since 1.0 + */ + public static int computeBearing(Position p1, Position p2) { + int bearing; + double dLon = computeDLong(p1.getLongitude(), p2.getLongitude()); + double y = Math.sin(dLon) * Math.cos(p2.getLatitude()); + double x = Math.cos(p1.getLatitude()) * Math.sin(p2.getLatitude()) + - Math.sin(p1.getLatitude()) * Math.cos(p2.getLatitude()) * Math.cos(dLon); + bearing = (int) Math.toDegrees(Math.atan2(y, x)); + return bearing; + } + + /** + * Computes the angle between two points. + * + * @param p1 + * @param p2 + * @return + */ + public static int computeAngle(Position p1, Position p2) { + // cos (adj / hyp) + double adj = Math.abs(p1.getLongitude() - p2.getLongitude()); + double opp = Math.abs(p1.getLatitude() - p2.getLatitude()); + return (int) Math.toDegrees(Math.atan(opp / adj)); + +// int angle = (int)Math.atan2(p2.getLatitude() - p1.getLatitude(), +// p2.getLongitude() - p1.getLongitude()); + //Actually it's ATan2(dy , dx) where dy = y2 - y1 and dx = x2 - x1, or ATan(dy / dx) + } + + public static int computeHeading(Position p1, Position p2) { + int angle = computeAngle(p1, p2); + // NE + if (p2.getLongitude() >= p1.getLongitude() && p2.getLatitude() >= p1.getLatitude()) { + return angle; + } else if (p2.getLongitude() >= p1.getLongitude() && p2.getLatitude() <= p1.getLatitude()) { + // SE + return 90 + angle; + } else if (p2.getLongitude() <= p1.getLongitude() && p2.getLatitude() <= p1.getLatitude()) { + // SW + return 270 - angle; + } else { + // NW + return 270 + angle; + } + } + + public static void main(String[] args) { + try { + int pos = NavCalculator.computeHeading(new Position(0, 0), new Position(10, -10)); +// System.out.println(pos.getLatitude() + "," + pos.getLongitude()); + System.out.println(pos); + } catch (Exception e) { + } + + + + + + } +} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/NumUtil.java b/jme3-desktop/src/main/java/jme3tools/navigation/NumUtil.java new file mode 100644 index 000000000..4110f5096 --- /dev/null +++ b/jme3-desktop/src/main/java/jme3tools/navigation/NumUtil.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.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/jme3-desktop/src/main/java/jme3tools/navigation/Position.java b/jme3-desktop/src/main/java/jme3tools/navigation/Position.java new file mode 100644 index 000000000..2de517fac --- /dev/null +++ b/jme3-desktop/src/main/java/jme3tools/navigation/Position.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.navigation; + +/** + * This class represents the position of an entity in the world. + * + * @author Benjamin Jakobus (based on JMarine by Cormac Gebruers and Benjamin Jakobus) + * @version 1.0 + * @since 1.0 + */ +public class Position { + + /* the latitude (+ N/E) */ + private Coordinate lat; + + /* the longitude (-W/S) */ + private Coordinate lng; + + /* An optional time to associate with this position - for historical tracking */ + private String utcTimeStamp; + + /* Degree position */ + private double degree; + + /** + * A new position expressed in decimal format + * @param dblLat + * @param dblLng + * @since 1.0 + */ + public Position(double dblLat, double dblLng) throws InvalidPositionException { + lat = new Coordinate(dblLat, Coordinate.LAT); + lng = new Coordinate(dblLng, Coordinate.LNG); + } + + /** + * A new position expressed in decimal format and degrees + * @param dblLat + * @param dblLng + * @param degree + * @since 1.0 + */ +// public Position(double dblLat, double dblLng, double degree) throws InvalidPositionException { +// lat = new Coordinate(dblLat, Coordinate.LAT); +// lng = new Coordinate(dblLng, Coordinate.LNG); +// this.degree = degree; +// } + /** + * A new position expressed in DegMin format + * @param latDeg + * @param latMin + * @param lngDeg + * @param lngMin + * @since 1.0 + */ + public Position(int latDeg, float latMin, int latQuad, int lngDeg, + float lngMin, int lngQuad) throws InvalidPositionException { + lat = new Coordinate(latDeg, latMin, Coordinate.LAT, latQuad); + lng = new Coordinate(lngDeg, lngMin, Coordinate.LNG, lngQuad); + } + + /** + * A new position expressed in ALRS format + * @param lat + * @param lng + * @since 1.0 + */ + public Position(String lat, String lng) throws InvalidPositionException { + this.lat = new Coordinate(lat); + this.lng = new Coordinate(lng); + } + + /** + * A new position expressed in NMEA GPS message format: + * 4807.038,N,01131.000,E + * @param latNMEAGPS + * @param latQuad + * @param lngNMEAGPS + * @param lngQuad + * @param utcTimeStamp + * @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/jme3-desktop/src/main/java/jme3tools/navigation/RLSailing.java b/jme3-desktop/src/main/java/jme3tools/navigation/RLSailing.java new file mode 100644 index 000000000..c9d70b8e5 --- /dev/null +++ b/jme3-desktop/src/main/java/jme3tools/navigation/RLSailing.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.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/jme3-desktop/src/main/java/jme3tools/navigation/StringUtil.java b/jme3-desktop/src/main/java/jme3tools/navigation/StringUtil.java new file mode 100644 index 000000000..873e6087b --- /dev/null +++ b/jme3-desktop/src/main/java/jme3tools/navigation/StringUtil.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3tools.navigation; + +import java.util.regex.Pattern; + +/** + * A collection of String utilities. + * + * @author Benjamin Jakobus + * @version 1.0 + */ +public class StringUtil { + + /** + * Splits a newline (\n) delimited string into an array of strings + * + * @param str the string to split up + * @param delimiter the delimiter to use in splitting + * @return 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 {@link String} with zeros for printing + * + * @param input the String 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/jme3-desktop/src/main/resources/com/jme3/app/Monkey.png b/jme3-desktop/src/main/resources/com/jme3/app/Monkey.png new file mode 100644 index 000000000..e1c8c3d8b Binary files /dev/null and b/jme3-desktop/src/main/resources/com/jme3/app/Monkey.png differ diff --git a/jme3-desktop/src/main/resources/com/jme3/app/SettingsDialog.properties b/jme3-desktop/src/main/resources/com/jme3/app/SettingsDialog.properties new file mode 100644 index 000000000..c27594839 --- /dev/null +++ b/jme3-desktop/src/main/resources/com/jme3/app/SettingsDialog.properties @@ -0,0 +1,17 @@ +frame.title=Display Settings for {0} + +button.ok=Continue +button.cancel=Cancel + +checkbox.fullscreen=Fullscreen? +checkbox.vsync=Vsync? + +label.resolutions=Screen Resolution +label.colordepth=Color Depth +label.refresh=Refresh Rate +label.antialias=Anti-Aliasing + +antialias.disabled=Disabled +refresh.na=n/a + +error.unsupportedmode=Your monitor reports that it does not support the display mode you\'ve selected.\nThe combination of bit depth and refresh rate is not supported. diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/BloomFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/BloomFilter.java new file mode 100644 index 000000000..778111a3c --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/BloomFilter.java @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.Image.Format; +import java.io.IOException; +import java.util.ArrayList; + +/** + * BloomFilter is used to make objects in the scene have a glow effect.
      + * There are 2 mode : Scene and Objects.
      + * Scene mode extracts the bright parts of the scene to make them glow
      + * Object mode make objects glow according to their material's glowMap or their GlowColor
      + * @see advanced:bloom_and_glow for more details + * + * @author Rémy Bouquet aka Nehon + */ +public class BloomFilter extends Filter { + + /** + * GlowMode specifies if the glow will be applied to the whole scene,or to objects that have aglow color or a glow map + */ + public enum GlowMode { + + /** + * Apply bloom filter to bright areas in the scene. + */ + Scene, + /** + * Apply bloom only to objects that have a glow map or a glow color. + */ + Objects, + /** + * Apply bloom to both bright parts of the scene and objects with glow map. + */ + SceneAndObjects; + } + + private GlowMode glowMode = GlowMode.Scene; + //Bloom parameters + private float blurScale = 1.5f; + private float exposurePower = 5.0f; + private float exposureCutOff = 0.0f; + private float bloomIntensity = 2.0f; + private float downSamplingFactor = 1; + private Pass preGlowPass; + private Pass extractPass; + private Pass horizontalBlur = new Pass(); + private Pass verticalalBlur = new Pass(); + private Material extractMat; + private Material vBlurMat; + private Material hBlurMat; + private int screenWidth; + private int screenHeight; + private RenderManager renderManager; + private ViewPort viewPort; + + private AssetManager assetManager; + private int initalWidth; + private int initalHeight; + + /** + * Creates a Bloom filter + */ + public BloomFilter() { + super("BloomFilter"); + } + + /** + * Creates the bloom filter with the specific glow mode + * @param glowMode + */ + public BloomFilter(GlowMode glowMode) { + this(); + this.glowMode = glowMode; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + this.renderManager = renderManager; + this.viewPort = vp; + + this.assetManager = manager; + this.initalWidth = w; + this.initalHeight = h; + + screenWidth = (int) Math.max(1, (w / downSamplingFactor)); + screenHeight = (int) Math.max(1, (h / downSamplingFactor)); + // System.out.println(screenWidth + " " + screenHeight); + if (glowMode != GlowMode.Scene) { + preGlowPass = new Pass(); + preGlowPass.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth); + } + + postRenderPasses = new ArrayList(); + //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()); + } + + + protected void reInitFilter() { + initFilter(assetManager, renderManager, viewPort, initalWidth, initalHeight); + } + + @Override + protected Material getMaterial() { + material.setFloat("BloomIntensity", bloomIntensity); + return material; + } + + @Override + protected void postQueue(RenderQueue queue) { + if (glowMode != GlowMode.Scene) { + renderManager.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha); + renderManager.getRenderer().setFrameBuffer(preGlowPass.getRenderFrameBuffer()); + renderManager.getRenderer().clearBuffers(true, true, true); + renderManager.setForcedTechnique("Glow"); + renderManager.renderViewPortQueues(viewPort, false); + renderManager.setForcedTechnique(null); + renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer()); + } + } + + @Override + protected void cleanUpFilter(Renderer r) { + if (glowMode != GlowMode.Scene) { + preGlowPass.cleanup(r); + } + } + + /** + * returns the bloom intensity + * @return + */ + public float getBloomIntensity() { + return bloomIntensity; + } + + /** + * intensity of the bloom effect default is 2.0 + * @param bloomIntensity + */ + public void setBloomIntensity(float bloomIntensity) { + this.bloomIntensity = bloomIntensity; + } + + /** + * returns the blur scale + * @return + */ + public float getBlurScale() { + return blurScale; + } + + /** + * sets The spread of the bloom default is 1.5f + * @param blurScale + */ + public void setBlurScale(float blurScale) { + this.blurScale = blurScale; + } + + /** + * returns the exposure cutoff
      + * for more details see {@link #setExposureCutOff(float exposureCutOff)} + * @return + */ + public float getExposureCutOff() { + return exposureCutOff; + } + + /** + * Define the color threshold on which the bloom will be applied (0.0 to 1.0) + * @param exposureCutOff + */ + public void setExposureCutOff(float exposureCutOff) { + this.exposureCutOff = exposureCutOff; + } + + /** + * returns the exposure power
      + * form more details see {@link #setExposurePower(float exposurePower)} + * @return + */ + public float getExposurePower() { + return exposurePower; + } + + /** + * defines how many time the bloom extracted color will be multiplied by itself. default id 5.0
      + * a high value will reduce rough edges in the bloom and somhow the range of the bloom area * + * @param exposurePower + */ + public void setExposurePower(float exposurePower) { + this.exposurePower = exposurePower; + } + + /** + * returns the downSampling factor
      + * form more details see {@link #setDownSamplingFactor(float downSamplingFactor)} + * @return + */ + public float getDownSamplingFactor() { + return downSamplingFactor; + } + + /** + * Sets the downSampling factor : the size of the computed texture will be divided by this factor. default is 1 for no downsampling + * A 2 value is a good way of widening the blur + * @param downSamplingFactor + */ + public void setDownSamplingFactor(float downSamplingFactor) { + this.downSamplingFactor = downSamplingFactor; + if (assetManager != null) // dirty isInitialised check + reInitFilter(); + } + + @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/jme3-effects/src/main/java/com/jme3/post/filters/CartoonEdgeFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/CartoonEdgeFilter.java new file mode 100644 index 000000000..8308c8a46 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/CartoonEdgeFilter.java @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.post.Filter; +import com.jme3.post.Filter.Pass; +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.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); + private RenderManager renderManager; + private ViewPort viewPort; + + /** + * Creates a CartoonEdgeFilter + */ + public CartoonEdgeFilter() { + super("CartoonEdgeFilter"); + } + + @Override + protected boolean isRequiresDepthTexture() { + return true; + } + + @Override + protected void postQueue(RenderQueue queue) { + Renderer r = renderManager.getRenderer(); + r.setFrameBuffer(normalPass.getRenderFrameBuffer()); + renderManager.getRenderer().clearBuffers(true, true, true); + renderManager.setForcedTechnique("PreNormalPass"); + renderManager.renderViewPortQueues(viewPort, false); + renderManager.setForcedTechnique(null); + renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer()); + } + + @Override + protected Material getMaterial() { + material.setTexture("NormalsTexture", normalPass.getRenderedTexture()); + return material; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + this.renderManager = renderManager; + this.viewPort = vp; + 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 + protected void cleanUpFilter(Renderer r) { + normalPass.cleanup(r); + } + + + + /** + * Return the depth sensitivity
      + * for more details see {@link #setDepthSensitivity(float depthSensitivity)} + * @return + */ + public float getDepthSensitivity() { + return depthSensitivity; + } + + /** + * sets the depth sensitivity
      + * defines how much depth will influence edges, default is 10 + * @param depthSensitivity + */ + public void setDepthSensitivity(float depthSensitivity) { + this.depthSensitivity = depthSensitivity; + if (material != null) { + material.setFloat("DepthSensitivity", depthSensitivity); + } + } + + /** + * returns the depth threshold
      + * for more details see {@link #setDepthThreshold(float depthThreshold)} + * @return + */ + public float getDepthThreshold() { + return depthThreshold; + } + + /** + * sets the depth threshold
      + * Defines at what threshold of difference of depth an edge is outlined default is 0.1f + * @param depthThreshold + */ + public void setDepthThreshold(float depthThreshold) { + this.depthThreshold = depthThreshold; + if (material != null) { + material.setFloat("DepthThreshold", depthThreshold); + } + } + + /** + * returns the edge intensity
      + * for more details see {@link #setEdgeIntensity(float edgeIntensity) } + * @return + */ + public float getEdgeIntensity() { + return edgeIntensity; + } + + /** + * sets the edge intensity
      + * Defineshow visilble will be the outlined edges + * @param edgeIntensity + */ + public void setEdgeIntensity(float edgeIntensity) { + this.edgeIntensity = edgeIntensity; + if (material != null) { + material.setFloat("EdgeIntensity", edgeIntensity); + } + } + + /** + * returns the width of the edges + * @return + */ + public float getEdgeWidth() { + return edgeWidth; + } + + /** + * sets the witdh of the edge in pixels default is 1 + * @param edgeWidth + */ + public void setEdgeWidth(float edgeWidth) { + this.edgeWidth = edgeWidth; + if (material != null) { + material.setFloat("EdgeWidth", edgeWidth); + } + + } + + /** + * returns the normals sensitivity
      + * form more details see {@link #setNormalSensitivity(float normalSensitivity)} + * @return + */ + public float getNormalSensitivity() { + return normalSensitivity; + } + + /** + * sets the normals sensitivity default is 1 + * @param normalSensitivity + */ + public void setNormalSensitivity(float normalSensitivity) { + this.normalSensitivity = normalSensitivity; + if (material != null) { + material.setFloat("NormalSensitivity", normalSensitivity); + } + } + + /** + * returns the normal threshold
      + * for more details see {@link #setNormalThreshold(float normalThreshold)} + * + * @return + */ + public float getNormalThreshold() { + return normalThreshold; + } + + /** + * sets the normal threshold default is 0.5 + * @param normalThreshold + */ + public void setNormalThreshold(float normalThreshold) { + this.normalThreshold = normalThreshold; + if (material != null) { + material.setFloat("NormalThreshold", normalThreshold); + } + } + + /** + * returns the edge color + * @return + */ + public ColorRGBA getEdgeColor() { + return edgeColor; + } + + /** + * Sets the edge color, default is black + * @param edgeColor + */ + public void setEdgeColor(ColorRGBA edgeColor) { + this.edgeColor = edgeColor; + if (material != null) { + material.setColor("EdgeColor", edgeColor); + } + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/ColorOverlayFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/ColorOverlayFilter.java new file mode 100644 index 000000000..97a930c85 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/ColorOverlayFilter.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import java.io.IOException; + +/** + * This filter simply multiply the whole scene by a color + * @author Rémy Bouquet aka Nehon + */ +public class ColorOverlayFilter extends Filter { + + private ColorRGBA color = ColorRGBA.White; + + /** + * creates a colorOverlayFilter with a white coor (transparent) + */ + public ColorOverlayFilter() { + super("Color Overlay"); + } + + /** + * creates a colorOverlayFilter with the given color + * @param color + */ + public ColorOverlayFilter(ColorRGBA color) { + this(); + this.color = color; + } + + @Override + protected Material getMaterial() { + + material.setColor("Color", color); + return material; + } + + /** + * returns the color + * @return color + */ + public ColorRGBA getColor() { + return color; + } + + /** + * sets the color + * @param color + */ + public void setColor(ColorRGBA color) { + this.color = color; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + material = new Material(manager, "Common/MatDefs/Post/Overlay.j3md"); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(color, "color", ColorRGBA.White); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + color = (ColorRGBA) ic.readSavable("color", ColorRGBA.White); + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/ComposeFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/ComposeFilter.java new file mode 100644 index 000000000..2aff1b174 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/ComposeFilter.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.texture.Texture2D; +import java.io.IOException; + +/** + * This filter compose a texture with the viewport texture. This is used to + * compose post processed texture from another viewport. + * + * the compositing is done using the alpha value of the viewportTexture : + * mix(compositeTextureColor, viewPortColor, viewportColor.alpha); + * + * It's important for a good result that the viewport clear color alpha be 0. + * + * @author Rémy Bouquet aka Nehon + */ +public class ComposeFilter extends Filter { + + private Texture2D compositeTexture; + + /** + * creates a ComposeFilter + */ + public ComposeFilter() { + super("Compose Filter"); + } + + /** + * creates a ComposeFilter with the given texture + * + * @param color + */ + public ComposeFilter(Texture2D compositeTexture) { + this(); + this.compositeTexture = compositeTexture; + } + + @Override + protected Material getMaterial() { + + material.setTexture("CompositeTexture", compositeTexture); + return material; + } + + /** + * + * @return the compositeTexture + */ + public Texture2D getCompositeTexture() { + return compositeTexture; + } + + /** + * sets the compositeTexture + * + * @param compositeTexture + */ + public void setCompositeTexture(Texture2D compositeTexture) { + this.compositeTexture = compositeTexture; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + material = new Material(manager, "Common/MatDefs/Post/Compose.j3md"); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/CrossHatchFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/CrossHatchFilter.java new file mode 100644 index 000000000..699a252cd --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/CrossHatchFilter.java @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * A Post Processing filter that makes the screen look like it was drawn as + * diagonal lines with a pen. + * Try combining this with a cartoon edge filter to obtain manga style visuals. + * + * Based on an article from Geeks3D: + * http://www.geeks3d.com/20110219/shader-library-crosshatching-glsl-filter/ + * + * @author Roy Straver a.k.a. Baal Garnaal + */ +public class CrossHatchFilter extends Filter { + + private ColorRGBA lineColor = ColorRGBA.Black.clone(); + private ColorRGBA paperColor = ColorRGBA.White.clone(); + private float colorInfluenceLine = 0.8f; + private float colorInfluencePaper = 0.1f; + private float fillValue = 0.9f; + private float luminance1 = 0.9f; + private float luminance2 = 0.7f; + private float luminance3 = 0.5f; + private float luminance4 = 0.3f; + private float luminance5 = 0.0f; + private float lineThickness = 1.0f; + private float lineDistance = 4.0f; + + /** + * Creates a crossHatch filter + */ + public CrossHatchFilter() { + super("CrossHatchFilter"); + } + + /** + * Creates a crossHatch filter + * @param lineColor the colors of the lines + * @param paperColor the paper color + */ + public CrossHatchFilter(ColorRGBA lineColor, ColorRGBA paperColor) { + this(); + this.lineColor = lineColor; + this.paperColor = paperColor; + } + + @Override + protected boolean isRequiresDepthTexture() { + return false; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + material = new Material(manager, "Common/MatDefs/Post/CrossHatch.j3md"); + material.setColor("LineColor", lineColor); + material.setColor("PaperColor", paperColor); + + material.setFloat("ColorInfluenceLine", colorInfluenceLine); + material.setFloat("ColorInfluencePaper", colorInfluencePaper); + + material.setFloat("FillValue", fillValue); + + material.setFloat("Luminance1", luminance1); + material.setFloat("Luminance2", luminance2); + material.setFloat("Luminance3", luminance3); + material.setFloat("Luminance4", luminance4); + material.setFloat("Luminance5", luminance5); + + material.setFloat("LineThickness", lineThickness); + material.setFloat("LineDistance", lineDistance); + } + + @Override + protected Material getMaterial() { + return material; + } + + /** + * Sets color used to draw lines + * @param lineColor + */ + public void setLineColor(ColorRGBA lineColor) { + this.lineColor = lineColor; + if (material != null) { + material.setColor("LineColor", lineColor); + } + } + + /** + * Sets color used as background + * @param paperColor + */ + public void setPaperColor(ColorRGBA paperColor) { + this.paperColor = paperColor; + if (material != null) { + material.setColor("PaperColor", paperColor); + } + } + + /** + * Sets color influence of original image on lines drawn + * @param colorInfluenceLine + */ + public void setColorInfluenceLine(float colorInfluenceLine) { + this.colorInfluenceLine = colorInfluenceLine; + if (material != null) { + material.setFloat("ColorInfluenceLine", colorInfluenceLine); + } + } + + /** + * Sets color influence of original image on non-line areas + * @param colorInfluencePaper + */ + public void setColorInfluencePaper(float colorInfluencePaper) { + this.colorInfluencePaper = colorInfluencePaper; + if (material != null) { + material.setFloat("ColorInfluencePaper", colorInfluencePaper); + } + } + + /** + * Sets line/paper color ratio for areas with values < luminance5, + * really dark areas get no lines but a filled blob instead + * @param fillValue + */ + public void setFillValue(float fillValue) { + this.fillValue = fillValue; + if (material != null) { + material.setFloat("FillValue", fillValue); + } + } + + /** + * + * Sets minimum luminance levels for lines drawn + * @param luminance1 Top-left to down right 1 + * @param luminance2 Top-right to bottom left 1 + * @param luminance3 Top-left to down right 2 + * @param luminance4 Top-right to bottom left 2 + * @param luminance5 Blobs + */ + public void setLuminanceLevels(float luminance1, float luminance2, float luminance3, float luminance4, float luminance5) { + this.luminance1 = luminance1; + this.luminance2 = luminance2; + this.luminance3 = luminance3; + this.luminance4 = luminance4; + this.luminance5 = luminance5; + + if (material != null) { + material.setFloat("Luminance1", luminance1); + material.setFloat("Luminance2", luminance2); + material.setFloat("Luminance3", luminance3); + material.setFloat("Luminance4", luminance4); + material.setFloat("Luminance5", luminance5); + } + } + + /** + * Sets the thickness of lines drawn + * @param lineThickness + */ + public void setLineThickness(float lineThickness) { + this.lineThickness = lineThickness; + if (material != null) { + material.setFloat("LineThickness", lineThickness); + } + } + + /** + * Sets minimum distance between lines drawn + * Primary lines are drawn at 2*lineDistance + * Secondary lines are drawn at lineDistance + * @param lineDistance + */ + public void setLineDistance(float lineDistance) { + this.lineDistance = lineDistance; + if (material != null) { + material.setFloat("LineDistance", lineDistance); + } + } + + /** + * Returns line color + * @return + */ + public ColorRGBA getLineColor() { + return lineColor; + } + + /** + * Returns paper background color + * @return + */ + public ColorRGBA getPaperColor() { + return paperColor; + } + + /** + * Returns current influence of image colors on lines + */ + public float getColorInfluenceLine() { + return colorInfluenceLine; + } + + /** + * Returns current influence of image colors on paper background + */ + public float getColorInfluencePaper() { + return colorInfluencePaper; + } + + /** + * Returns line/paper color ratio for blobs + */ + public float getFillValue() { + return fillValue; + } + + /** + * Returns the thickness of the lines drawn + */ + public float getLineThickness() { + return lineThickness; + } + + /** + * Returns minimum distance between lines + */ + public float getLineDistance() { + return lineDistance; + } + + /** + * Returns treshold for lines 1 + */ + public float getLuminance1() { + return luminance1; + } + + /** + * Returns treshold for lines 2 + */ + public float getLuminance2() { + return luminance2; + } + + /** + * Returns treshold for lines 3 + */ + public float getLuminance3() { + return luminance3; + } + + /** + * Returns treshold for lines 4 + */ + public float getLuminance4() { + return luminance4; + } + + /** + * Returns treshold for blobs + */ + public float getLuminance5() { + return luminance5; + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java new file mode 100644 index 000000000..381bff2ff --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import java.io.IOException; + +/** + * A post-processing filter that performs a depth range + * blur using a scaled convolution filter. + * + * @version $Revision: 779 $ + * @author Paul Speed + */ +public class DepthOfFieldFilter extends Filter { + + private float focusDistance = 50f; + private float focusRange = 10f; + private float blurScale = 1f; + // These values are set internally based on the + // viewport size. + private float xScale; + private float yScale; + + /** + * Creates a DepthOfField filter + */ + public DepthOfFieldFilter() { + super("Depth Of Field"); + } + + @Override + protected boolean isRequiresDepthTexture() { + return true; + } + + @Override + protected Material getMaterial() { + + return material; + } + + @Override + protected void initFilter(AssetManager assets, RenderManager renderManager, + ViewPort vp, int w, int h) { + material = new Material(assets, "Common/MatDefs/Post/DepthOfField.j3md"); + material.setFloat("FocusDistance", focusDistance); + material.setFloat("FocusRange", focusRange); + + + xScale = 1.0f / w; + yScale = 1.0f / h; + + material.setFloat("XScale", blurScale * xScale); + material.setFloat("YScale", blurScale * yScale); + } + + /** + * Sets the distance at which objects are purely in focus. + */ + public void setFocusDistance(float f) { + + this.focusDistance = f; + if (material != null) { + material.setFloat("FocusDistance", focusDistance); + } + + } + + /** + * returns the focus distance + * @return + */ + public float getFocusDistance() { + return focusDistance; + } + + /** + * Sets the range to either side of focusDistance where the + * objects go gradually out of focus. Less than focusDistance - focusRange + * and greater than focusDistance + focusRange, objects are maximally "blurred". + */ + public void setFocusRange(float f) { + this.focusRange = f; + if (material != null) { + material.setFloat("FocusRange", focusRange); + } + + } + + /** + * returns the focus range + * @return + */ + public float getFocusRange() { + return focusRange; + } + + /** + * Sets the blur amount by scaling the convolution filter up or + * down. A value of 1 (the default) performs a sparse 5x5 evenly + * distribubted convolution at pixel level accuracy. Higher values skip + * more pixels, and so on until you are no longer blurring the image + * but simply hashing it. + * + * The sparse convolution is as follows: + *%MINIFYHTMLc3d0cd9fab65de6875a381fd3f83e1b338%* + * Where 'x' is the texel being modified. Setting blur scale higher + * than 1 spaces the samples out. + */ + public void setBlurScale(float f) { + this.blurScale = f; + if (material != null) { + material.setFloat("XScale", blurScale * xScale); + material.setFloat("YScale", blurScale * yScale); + } + } + + /** + * returns the blur scale + * @return + */ + public float getBlurScale() { + return blurScale; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(blurScale, "blurScale", 1f); + oc.write(focusDistance, "focusDistance", 50f); + oc.write(focusRange, "focusRange", 10f); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + blurScale = ic.readFloat("blurScale", 1f); + focusDistance = ic.readFloat("focusDistance", 50f); + focusRange = ic.readFloat("focusRange", 10f); + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/FXAAFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/FXAAFilter.java new file mode 100644 index 000000000..7d95c67bb --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/FXAAFilter.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * http://www.geeks3d.com/20110405/fxaa-fast-approximate-anti-aliasing-demo-glsl-opengl-test-radeon-geforce/3/ + * http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf + * + * @author Phate666 (adapted to jme3) + * + */ +public class FXAAFilter extends Filter { + + private float subPixelShift = 1.0f / 4.0f; + private float vxOffset = 0.0f; + private float spanMax = 8.0f; + private float reduceMul = 1.0f / 8.0f; + + public FXAAFilter() { + super("FXAAFilter"); + } + + @Override + protected void initFilter(AssetManager manager, + RenderManager renderManager, ViewPort vp, int w, int h) { + material = new Material(manager, "Common/MatDefs/Post/FXAA.j3md"); + material.setFloat("SubPixelShift", subPixelShift); + material.setFloat("VxOffset", vxOffset); + material.setFloat("SpanMax", spanMax); + material.setFloat("ReduceMul", reduceMul); + } + + @Override + protected Material getMaterial() { + return material; + } + + public void setSpanMax(float spanMax) { + this.spanMax = spanMax; + if (material != null) { + material.setFloat("SpanMax", this.spanMax); + } + } + + /** + * set to 0.0f for higher quality + * + * @param subPixelShift + */ + public void setSubPixelShift(float subPixelShift) { + this.subPixelShift = subPixelShift; + if (material != null) { + material.setFloat("SubPixelShif", this.subPixelShift); + } + } + + /** + * set to 0.0f for higher quality + * + * @param reduceMul + */ + public void setReduceMul(float reduceMul) { + this.reduceMul = reduceMul; + if (material != null) { + material.setFloat("ReduceMul", this.reduceMul); + } + } + + public void setVxOffset(float vxOffset) { + this.vxOffset = vxOffset; + if (material != null) { + material.setFloat("VxOffset", this.vxOffset); + } + } + + public float getReduceMul() { + return reduceMul; + } + + public float getSpanMax() { + return spanMax; + } + + public float getSubPixelShift() { + return subPixelShift; + } + + public float getVxOffset() { + return vxOffset; + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/FadeFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/FadeFilter.java new file mode 100644 index 000000000..be7fe46ab --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/FadeFilter.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import java.io.IOException; + +/** + * + * Fade Filter allows you to make an animated fade effect on a scene. + * @author Rémy Bouquet aka Nehon + * implemented from boxjar implementation + * @see 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; + + /** + * Creates a FadeFilter + */ + public FadeFilter() { + super("Fade In/Out"); + } + + /** + * Creates a FadeFilter with the given duration + * @param duration + */ + public FadeFilter(float duration) { + this(); + this.duration = duration; + } + + @Override + protected Material getMaterial() { + material.setFloat("Value", value); + return material; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + material = new Material(manager, "Common/MatDefs/Post/Fade.j3md"); + } + + @Override + protected void preFrame(float tpf) { + if (playing) { + value += tpf * direction / duration; + + if (direction > 0 && value > 1) { + value = 1; + playing = false; + } + if (direction < 0 && value < 0) { + value = 0; + playing = false; + } + } + } + + /** + * returns the duration of the effect + * @return + */ + public float getDuration() { + return duration; + } + + /** + * Sets the duration of the filter default is 1 second + * @param duration + */ + public void setDuration(float duration) { + this.duration = duration; + } + + /** + * fades the scene in (black to scene) + */ + public void fadeIn() { + setEnabled(true); + direction = 1; + playing = true; + } + + /** + * fades the scene out (scene to black) + */ + public void fadeOut() { + setEnabled(true); + direction = -1; + playing = true; + + } + + public void pause() { + playing = false; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(duration, "duration", 1); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + duration = ic.readFloat("duration", 1); + } + + /** + * return the current value of the fading + * can be used to chack if fade is complete (eg value=1) + * @return + */ + public float getValue() { + return value; + } + + /** + * sets the fade value + * can be used to force complete black or compete scene + * @param value + */ + public void setValue(float value) { + this.value = value; + if (material != null) { + material.setFloat("Value", value); + } + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/FogFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/FogFilter.java new file mode 100644 index 000000000..c1df3b761 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/FogFilter.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import java.io.IOException; + +/** + * A filter to render a fog effect + * @author Rémy Bouquet aka Nehon + */ +public class FogFilter extends Filter { + + private ColorRGBA fogColor = ColorRGBA.White.clone(); + private float fogDensity = 0.7f; + private float fogDistance = 1000; + + /** + * Creates a FogFilter + */ + public FogFilter() { + super("FogFilter"); + } + + /** + * Create a fog filter + * @param fogColor the color of the fog (default is white) + * @param fogDensity the density of the fog (default is 0.7) + * @param fogDistance the distance of the fog (default is 1000) + */ + public FogFilter(ColorRGBA fogColor, float fogDensity, float fogDistance) { + this(); + this.fogColor = fogColor; + this.fogDensity = fogDensity; + this.fogDistance = fogDistance; + } + + @Override + protected boolean isRequiresDepthTexture() { + return true; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + material = new Material(manager, "Common/MatDefs/Post/Fog.j3md"); + material.setColor("FogColor", fogColor); + material.setFloat("FogDensity", fogDensity); + material.setFloat("FogDistance", fogDistance); + } + + @Override + protected Material getMaterial() { + + return material; + } + + + /** + * returns the fog color + * @return + */ + public ColorRGBA getFogColor() { + return fogColor; + } + + /** + * Sets the color of the fog + * @param fogColor + */ + public void setFogColor(ColorRGBA fogColor) { + if (material != null) { + material.setColor("FogColor", fogColor); + } + this.fogColor = fogColor; + } + + /** + * returns the fog density + * @return + */ + public float getFogDensity() { + return fogDensity; + } + + /** + * Sets the density of the fog, a high value gives a thick fog + * @param fogDensity + */ + public void setFogDensity(float fogDensity) { + if (material != null) { + material.setFloat("FogDensity", fogDensity); + } + this.fogDensity = fogDensity; + } + + /** + * returns the fog distance + * @return + */ + public float getFogDistance() { + return fogDistance; + } + + /** + * the distance of the fog. the higer the value the distant the fog looks + * @param fogDistance + */ + public void setFogDistance(float fogDistance) { + if (material != null) { + material.setFloat("FogDistance", fogDistance); + } + this.fogDistance = fogDistance; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(fogColor, "fogColor", ColorRGBA.White.clone()); + oc.write(fogDensity, "fogDensity", 0.7f); + oc.write(fogDistance, "fogDistance", 1000); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + fogColor = (ColorRGBA) ic.readSavable("fogColor", ColorRGBA.White.clone()); + fogDensity = ic.readFloat("fogDensity", 0.7f); + fogDistance = ic.readFloat("fogDistance", 1000); + } + + +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/GammaCorrectionFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/GammaCorrectionFilter.java new file mode 100644 index 000000000..a761fe6d5 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/GammaCorrectionFilter.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * + * @author Phate666 + * @version 1.0 initial version + * @version 1.1 added luma + */ +public class GammaCorrectionFilter extends Filter +{ + private float gamma = 2.0f; + private boolean computeLuma = false; + + public GammaCorrectionFilter() + { + super("GammaCorrectionFilter"); + } + + public GammaCorrectionFilter(float gamma) + { + this(); + this.setGamma(gamma); + } + + @Override + protected Material getMaterial() + { + return material; + } + + @Override + protected void initFilter(AssetManager manager, + RenderManager renderManager, ViewPort vp, int w, int h) + { + material = new Material(manager, + "Common/MatDefs/Post/GammaCorrection.j3md"); + material.setFloat("gamma", gamma); + material.setBoolean("computeLuma", computeLuma); + } + + public float getGamma() + { + return gamma; + } + + /** + * set to 0.0 to disable gamma correction + * @param gamma + */ + public void setGamma(float gamma) + { + if (material != null) + { + material.setFloat("gamma", gamma); + } + this.gamma = gamma; + } + + public boolean isComputeLuma() + { + return computeLuma; + } + + public void setComputeLuma(boolean computeLuma) + { + if (material != null) + { + material.setBoolean("computeLuma", computeLuma); + } + this.computeLuma = computeLuma; + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/LightScatteringFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/LightScatteringFilter.java new file mode 100644 index 000000000..20e5e3397 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/LightScatteringFilter.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.post.Filter; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import java.io.IOException; + +/** + * LightScattering filters creates rays comming from a light sources + * This is often reffered as god rays. + * + * @author Rémy Bouquet aka Nehon + */ +public class LightScatteringFilter extends Filter { + + private Vector3f lightPosition; + private Vector3f screenLightPos = new Vector3f(); + private int nbSamples = 50; + private float blurStart = 0.02f; + private float blurWidth = 0.9f; + private float lightDensity = 1.4f; + private boolean adaptative = true; + Vector3f viewLightPos = new Vector3f(); + private boolean display = true; + private float innerLightDensity; + private ViewPort viewPort; + + /** + * creates a lightScaterring filter + */ + public LightScatteringFilter() { + super("Light Scattering"); + } + + /** + * Creates a lightScatteringFilter + * @param lightPosition + */ + public LightScatteringFilter(Vector3f lightPosition) { + this(); + this.lightPosition = lightPosition; + } + + @Override + protected boolean isRequiresDepthTexture() { + return true; + } + + @Override + protected Material getMaterial() { + material.setVector3("LightPosition", screenLightPos); + material.setInt("NbSamples", nbSamples); + material.setFloat("BlurStart", blurStart); + material.setFloat("BlurWidth", blurWidth); + material.setFloat("LightDensity", innerLightDensity); + material.setBoolean("Display", display); + return material; + } + + @Override + protected void postQueue(RenderQueue queue) { + getClipCoordinates(lightPosition, screenLightPos, viewPort.getCamera()); + viewPort.getCamera().getViewMatrix().mult(lightPosition, viewLightPos); + if (adaptative) { + innerLightDensity = Math.max(lightDensity - Math.max(screenLightPos.x, screenLightPos.y), 0.0f); + } else { + innerLightDensity = lightDensity; + } + display = innerLightDensity != 0.0 && viewLightPos.z < 0; + } + + private Vector3f getClipCoordinates(Vector3f worldPosition, Vector3f store, Camera cam) { + + float w = cam.getViewProjectionMatrix().multProj(worldPosition, store); + store.divideLocal(w); + + store.x = ((store.x + 1f) * (cam.getViewPortRight() - cam.getViewPortLeft()) / 2f + cam.getViewPortLeft()); + store.y = ((store.y + 1f) * (cam.getViewPortTop() - cam.getViewPortBottom()) / 2f + cam.getViewPortBottom()); + store.z = (store.z + 1f) / 2f; + + return store; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + this.viewPort = vp; + material = new Material(manager, "Common/MatDefs/Post/LightScattering.j3md"); + } + + /** + * returns the blur start of the scattering + * see {@link #setBlurStart(float blurStart)} + * @return + */ + public float getBlurStart() { + return blurStart; + } + + /** + * sets the blur start
      + * at which distance from the light source the effect starts default is 0.02 + * @param blurStart + */ + public void setBlurStart(float blurStart) { + this.blurStart = blurStart; + } + + /** + * returns the blur width
      + * see {@link #setBlurWidth(float blurWidth)} + * @return + */ + public float getBlurWidth() { + return blurWidth; + } + + /** + * sets the blur width default is 0.9 + * @param blurWidth + */ + public void setBlurWidth(float blurWidth) { + this.blurWidth = blurWidth; + } + + /** + * returns the light density + * see {@link #setLightDensity(float lightDensity)} + * + * @return + */ + public float getLightDensity() { + return lightDensity; + } + + /** + * sets how much the effect is visible over the rendered scene default is 1.4 + * @param lightDensity + */ + public void setLightDensity(float lightDensity) { + this.lightDensity = lightDensity; + } + + /** + * returns the light position + * @return + */ + public Vector3f getLightPosition() { + return lightPosition; + } + + /** + * sets the light position + * @param lightPosition + */ + public void setLightPosition(Vector3f lightPosition) { + this.lightPosition = lightPosition; + } + + /** + * returns the nmber of samples for the radial blur + * @return + */ + public int getNbSamples() { + return nbSamples; + } + + /** + * sets the number of samples for the radial blur default is 50 + * the higher the value the higher the quality, but the slower the performances. + * @param nbSamples + */ + public void setNbSamples(int nbSamples) { + this.nbSamples = nbSamples; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(lightPosition, "lightPosition", Vector3f.ZERO); + oc.write(nbSamples, "nbSamples", 50); + oc.write(blurStart, "blurStart", 0.02f); + oc.write(blurWidth, "blurWidth", 0.9f); + oc.write(lightDensity, "lightDensity", 1.4f); + oc.write(adaptative, "adaptative", true); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + lightPosition = (Vector3f) ic.readSavable("lightPosition", Vector3f.ZERO); + nbSamples = ic.readInt("nbSamples", 50); + blurStart = ic.readFloat("blurStart", 0.02f); + blurWidth = ic.readFloat("blurWidth", 0.9f); + lightDensity = ic.readFloat("lightDensity", 1.4f); + adaptative = ic.readBoolean("adaptative", true); + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/PosterizationFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/PosterizationFilter.java new file mode 100644 index 000000000..2e380c610 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/PosterizationFilter.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * A Post Processing filter to change colors appear with sharp edges as if the + * available amount of colors available was not enough to draw the true image. + * Possibly useful in cartoon styled games. Use the strength variable to lessen + * influence of this filter on the total result. Values from 0.2 to 0.7 appear + * to give nice results. + * + * Based on an article from Geeks3D: + * http://www.geeks3d.com/20091027/shader-library-posterization-post-processing-effect-glsl/ + * + * @author Roy Straver a.k.a. Baal Garnaal + */ +public class PosterizationFilter extends Filter { + + private int numColors = 8; + private float gamma = 0.6f; + private float strength = 1.0f; + + /** + * Creates a posterization Filter + */ + public PosterizationFilter() { + super("PosterizationFilter"); + } + + /** + * Creates a posterization Filter with the given number of colors + * @param numColors + */ + public PosterizationFilter(int numColors) { + this(); + this.numColors = numColors; + } + + /** + * Creates a posterization Filter with the given number of colors and gamma + * @param numColors + * @param gamma + */ + public PosterizationFilter(int numColors, float gamma) { + this(numColors); + this.gamma = gamma; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + material = new Material(manager, "Common/MatDefs/Post/Posterization.j3md"); + material.setInt("NumColors", numColors); + material.setFloat("Gamma", gamma); + material.setFloat("Strength", strength); + } + + @Override + protected Material getMaterial() { + return material; + } + + /** + * Sets number of color levels used to draw the screen + */ + public void setNumColors(int numColors) { + this.numColors = numColors; + if (material != null) { + material.setInt("NumColors", numColors); + } + } + + /** + * Sets gamma level used to enhange visual quality + */ + public void setGamma(float gamma) { + this.gamma = gamma; + if (material != null) { + material.setFloat("Gamma", gamma); + } + } + + /** + * Sets urrent strength value, i.e. influence on final image + */ + public void setStrength(float strength) { + this.strength = strength; + if (material != null) { + material.setFloat("Strength", strength); + } + } + + /** + * Returns number of color levels used + */ + public int getNumColors() { + return numColors; + } + + /** + * Returns current gamma value + */ + public float getGamma() { + return gamma; + } + + /** + * Returns current strength value, i.e. influence on final image + */ + public float getStrength() { + return strength; + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/RadialBlurFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/RadialBlurFilter.java new file mode 100644 index 000000000..3fb25b51e --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/RadialBlurFilter.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.shader.VarType; +import java.io.IOException; + +/** + * Radially blurs the scene from the center of it + * @author Rémy Bouquet aka Nehon + */ +public class RadialBlurFilter extends Filter { + + private float sampleDist = 1.0f; + private float sampleStrength = 2.2f; + private float[] samples = {-0.08f, -0.05f, -0.03f, -0.02f, -0.01f, 0.01f, 0.02f, 0.03f, 0.05f, 0.08f}; + + /** + * Creates a RadialBlurFilter + */ + public RadialBlurFilter() { + super("Radial blur"); + } + + /** + * Creates a RadialBlurFilter + * @param sampleDist the distance between samples + * @param sampleStrength the strenght of each sample + */ + public RadialBlurFilter(float sampleDist, float sampleStrength) { + this(); + this.sampleDist = sampleDist; + this.sampleStrength = sampleStrength; + } + + @Override + protected Material getMaterial() { + material.setFloat("SampleDist", sampleDist); + material.setFloat("SampleStrength", sampleStrength); + material.setParam("Samples", VarType.FloatArray, samples); + return material; + } + + /** + * return the sample distance + * @return + */ + public float getSampleDistance() { + return sampleDist; + } + + /** + * sets the samples distances default is 1 + * @param sampleDist + */ + public void setSampleDistance(float sampleDist) { + this.sampleDist = sampleDist; + } + + /** + * + * @return + * @deprecated use {@link #getSampleDistance()} + */ + @Deprecated + public float getSampleDist() { + return sampleDist; + } + + /** + * + * @param sampleDist + * @deprecated use {@link #setSampleDistance(float sampleDist)} + */ + @Deprecated + public void setSampleDist(float sampleDist) { + this.sampleDist = sampleDist; + } + + /** + * Returns the sample Strength + * @return + */ + public float getSampleStrength() { + return sampleStrength; + } + + /** + * sets the sample streanght default is 2.2 + * @param sampleStrength + */ + public void setSampleStrength(float sampleStrength) { + this.sampleStrength = sampleStrength; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + material = new Material(manager, "Common/MatDefs/Blur/RadialBlur.j3md"); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(sampleDist, "sampleDist", 1.0f); + oc.write(sampleStrength, "sampleStrength", 2.2f); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + sampleDist = ic.readFloat("sampleDist", 1.0f); + sampleStrength = ic.readFloat("sampleStrength", 2.2f); + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/TranslucentBucketFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/TranslucentBucketFilter.java new file mode 100644 index 000000000..31238cdca --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/TranslucentBucketFilter.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.effect.ParticleEmitter; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A filter to handle translucent objects when rendering a scene with filters that uses depth like WaterFilter and SSAOFilter + * just create a TranslucentBucketFilter and add it to the Filter list of a FilterPostPorcessor + * @author Nehon + */ +public final class TranslucentBucketFilter extends Filter { + + private final static Logger logger = Logger.getLogger(TranslucentBucketFilter.class.getName()); + private RenderManager renderManager; + private boolean enabledSoftParticles = false; + private Texture depthTexture; + private ViewPort viewPort; + + public TranslucentBucketFilter() { + super("TranslucentBucketFilter"); + } + + public TranslucentBucketFilter(boolean enabledSoftParticles) { + this(); + this.enabledSoftParticles = enabledSoftParticles; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager rm, ViewPort vp, int w, int h) { + this.renderManager = rm; + this.viewPort = vp; + material = new Material(manager, "Common/MatDefs/Post/Overlay.j3md"); + material.setColor("Color", ColorRGBA.White); + Texture2D tex = processor.getFilterTexture(); + material.setTexture("Texture", tex); + if (tex.getImage().getMultiSamples() > 1) { + material.setInt("NumSamples", tex.getImage().getMultiSamples()); + } else { + material.clearParam("NumSamples"); + } + renderManager.setHandleTranslucentBucket(false); + if (enabledSoftParticles && depthTexture != null) { + initSoftParticles(vp, true); + } + } + + private void initSoftParticles(ViewPort vp, boolean enabledSP) { + if (depthTexture != null) { + for (Spatial scene : vp.getScenes()) { + makeSoftParticleEmitter(scene, enabledSP && enabled); + } + } + + } + + @Override + protected void setDepthTexture(Texture depthTexture) { + this.depthTexture = depthTexture; + if (enabledSoftParticles && depthTexture != null) { + initSoftParticles(viewPort, true); + } + } + + /** + * Override this method and return false if your Filter does not need the scene texture + * @return + */ + @Override + protected boolean isRequiresSceneTexture() { + return false; + } + + @Override + protected boolean isRequiresDepthTexture() { + return enabledSoftParticles; + } + + @Override + protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) { + renderManager.setCamera(viewPort.getCamera(), false); + if (prevFilterBuffer != sceneBuffer) { + renderManager.getRenderer().copyFrameBuffer(prevFilterBuffer, sceneBuffer, false); + } + renderManager.getRenderer().setFrameBuffer(sceneBuffer); + viewPort.getQueue().renderQueue(RenderQueue.Bucket.Translucent, renderManager, viewPort.getCamera()); + } + + @Override + protected void cleanUpFilter(Renderer r) { + if (renderManager != null) { + renderManager.setHandleTranslucentBucket(true); + } + + initSoftParticles(viewPort, false); + } + + @Override + protected Material getMaterial() { + return material; + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + if (renderManager != null) { + renderManager.setHandleTranslucentBucket(!enabled); + } + initSoftParticles(viewPort, enabledSoftParticles); + } + + private void makeSoftParticleEmitter(Spatial scene, boolean enabled) { + if (scene instanceof Node) { + Node n = (Node) scene; + for (Spatial child : n.getChildren()) { + makeSoftParticleEmitter(child, enabled); + } + } + if (scene instanceof ParticleEmitter) { + ParticleEmitter emitter = (ParticleEmitter) scene; + if (enabled) { + enabledSoftParticles = enabled; + + if( processor.getNumSamples()>1){ + emitter.getMaterial().selectTechnique("SoftParticles15", renderManager); + emitter.getMaterial().setInt("NumSamplesDepth", processor.getNumSamples()); + }else{ + emitter.getMaterial().selectTechnique("SoftParticles", renderManager); + } + emitter.getMaterial().setTexture("DepthTexture", processor.getDepthTexture()); + emitter.setQueueBucket(RenderQueue.Bucket.Translucent); + + logger.log(Level.FINE, "Made particle Emitter {0} soft.", emitter.getName()); + } else { + emitter.getMaterial().clearParam("DepthTexture"); + emitter.getMaterial().selectTechnique("Default", renderManager); + // emitter.setQueueBucket(RenderQueue.Bucket.Transparent); + logger.log(Level.FINE, "Particle Emitter {0} is not soft anymore.", emitter.getName()); + } + } + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java b/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java new file mode 100644 index 000000000..75d6b1c86 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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.renderer.queue.RenderQueue; +import com.jme3.shader.VarType; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import java.io.IOException; +import java.util.ArrayList; + +/** + * SSAO stands for screen space ambient occlusion + * It's a technique that fake ambient lighting by computing shadows that near by objects would casts on each others + * under the effect of an ambient light + * more info on this in this blog post http://jmonkeyengine.org/2010/08/16/screen-space-ambient-occlusion-for-jmonkeyengine-3-0/ + * + * @author Rémy Bouquet aka Nehon + */ +public class SSAOFilter extends Filter { + + private Pass normalPass; + private Vector3f frustumCorner; + private Vector2f frustumNearFar; + private Vector2f[] samples = {new Vector2f(1.0f, 0.0f), new Vector2f(-1.0f, 0.0f), new Vector2f(0.0f, 1.0f), new Vector2f(0.0f, -1.0f)}; + private float sampleRadius = 5.1f; + private float intensity = 1.5f; + private float scale = 0.2f; + private float bias = 0.1f; + private boolean useOnlyAo = false; + private boolean useAo = true; + private Material ssaoMat; + private Pass ssaoPass; +// private Material downSampleMat; +// private Pass downSamplePass; + private float downSampleFactor = 1f; + private RenderManager renderManager; + private ViewPort viewPort; + + /** + * Create a Screen Space Ambient Occlusion Filter + */ + public SSAOFilter() { + super("SSAOFilter"); + } + + /** + * Create a Screen Space Ambient Occlusion Filter + * @param sampleRadius The radius of the area where random samples will be picked. default 5.1f + * @param intensity intensity of the resulting AO. default 1.2f + * @param scale distance between occluders and occludee. default 0.2f + * @param bias the width of the occlusion cone considered by the occludee. default 0.1f + */ + public SSAOFilter(float sampleRadius, float intensity, float scale, float bias) { + this(); + this.sampleRadius = sampleRadius; + this.intensity = intensity; + this.scale = scale; + this.bias = bias; + } + + @Override + protected boolean isRequiresDepthTexture() { + return true; + } + + @Override + protected void postQueue(RenderQueue queue) { + Renderer r = renderManager.getRenderer(); + r.setFrameBuffer(normalPass.getRenderFrameBuffer()); + renderManager.getRenderer().clearBuffers(true, true, true); + renderManager.setForcedTechnique("PreNormalPass"); + renderManager.renderViewPortQueues(viewPort, false); + renderManager.setForcedTechnique(null); + renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer()); + } + + @Override + protected Material getMaterial() { + return material; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + this.renderManager = renderManager; + this.viewPort = vp; + int screenWidth = w; + int screenHeight = h; + postRenderPasses = new ArrayList(); + + normalPass = new Pass(); + normalPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth); + + + frustumNearFar = new Vector2f(); + + float farY = (vp.getCamera().getFrustumTop() / vp.getCamera().getFrustumNear()) * vp.getCamera().getFrustumFar(); + float farX = farY * ((float) screenWidth / (float) screenHeight); + frustumCorner = new Vector3f(farX, farY, vp.getCamera().getFrustumFar()); + frustumNearFar.x = vp.getCamera().getFrustumNear(); + frustumNearFar.y = vp.getCamera().getFrustumFar(); + + + + + + //ssao Pass + ssaoMat = new Material(manager, "Common/MatDefs/SSAO/ssao.j3md"); + ssaoMat.setTexture("Normals", normalPass.getRenderedTexture()); + Texture random = manager.loadTexture("Common/MatDefs/SSAO/Textures/random.png"); + random.setWrap(Texture.WrapMode.Repeat); + ssaoMat.setTexture("RandomMap", random); + + ssaoPass = new Pass() { + + @Override + public boolean requiresDepthAsTexture() { + return true; + } + }; + + ssaoPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth, 1, ssaoMat); + ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear); + ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear); + postRenderPasses.add(ssaoPass); + material = new Material(manager, "Common/MatDefs/SSAO/ssaoBlur.j3md"); + material.setTexture("SSAOMap", ssaoPass.getRenderedTexture()); + + ssaoMat.setVector3("FrustumCorner", frustumCorner); + ssaoMat.setFloat("SampleRadius", sampleRadius); + ssaoMat.setFloat("Intensity", intensity); + ssaoMat.setFloat("Scale", scale); + ssaoMat.setFloat("Bias", bias); + material.setBoolean("UseAo", useAo); + material.setBoolean("UseOnlyAo", useOnlyAo); + ssaoMat.setVector2("FrustumNearFar", frustumNearFar); + material.setVector2("FrustumNearFar", frustumNearFar); + ssaoMat.setParam("Samples", VarType.Vector2Array, samples); + + float xScale = 1.0f / w; + float yScale = 1.0f / h; + + float blurScale = 2f; + material.setFloat("XScale", blurScale * xScale); + material.setFloat("YScale", blurScale * yScale); + + } + + @Override + protected void cleanUpFilter(Renderer r) { + normalPass.cleanup(r); + } + + /** + * Return the bias
      + * see {@link #setBias(float bias)} + * @return bias + */ + public float getBias() { + return bias; + } + + /** + * Sets the the width of the occlusion cone considered by the occludee default is 0.1f + * @param bias + */ + public void setBias(float bias) { + this.bias = bias; + if (ssaoMat != null) { + ssaoMat.setFloat("Bias", bias); + } + } + + /** + * returns the ambient occlusion intensity + * @return intensity + */ + public float getIntensity() { + return intensity; + } + + /** + * Sets the Ambient occlusion intensity default is 1.2f + * @param intensity + */ + public void setIntensity(float intensity) { + this.intensity = intensity; + if (ssaoMat != null) { + ssaoMat.setFloat("Intensity", intensity); + } + + } + + /** + * returns the sample radius
      + * see {link setSampleRadius(float sampleRadius)} + * @return the sample radius + */ + public float getSampleRadius() { + return sampleRadius; + } + + /** + * Sets the radius of the area where random samples will be picked dafault 5.1f + * @param sampleRadius + */ + public void setSampleRadius(float sampleRadius) { + this.sampleRadius = sampleRadius; + if (ssaoMat != null) { + ssaoMat.setFloat("SampleRadius", sampleRadius); + } + + } + + /** + * returns the scale
      + * see {@link #setScale(float scale)} + * @return scale + */ + public float getScale() { + return scale; + } + + /** + * + * Returns the distance between occluders and occludee. default 0.2f + * @param scale + */ + public void setScale(float scale) { + this.scale = scale; + if (ssaoMat != null) { + ssaoMat.setFloat("Scale", scale); + } + } + + /** + * debugging only , will be removed + * @return Whether or not + */ + public boolean isUseAo() { + return useAo; + } + + /** + * debugging only , will be removed + */ + public void setUseAo(boolean useAo) { + this.useAo = useAo; + if (material != null) { + material.setBoolean("UseAo", useAo); + } + + } + + /** + * debugging only , will be removed + * @return useOnlyAo + */ + public boolean isUseOnlyAo() { + return useOnlyAo; + } + + /** + * debugging only , will be removed + */ + public void setUseOnlyAo(boolean useOnlyAo) { + this.useOnlyAo = useOnlyAo; + if (material != null) { + material.setBoolean("UseOnlyAo", useOnlyAo); + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(sampleRadius, "sampleRadius", 5.1f); + oc.write(intensity, "intensity", 1.5f); + oc.write(scale, "scale", 0.2f); + oc.write(bias, "bias", 0.1f); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + sampleRadius = ic.readFloat("sampleRadius", 5.1f); + intensity = ic.readFloat("intensity", 1.5f); + scale = ic.readFloat("scale", 0.2f); + bias = ic.readFloat("bias", 0.1f); + } +} diff --git a/jme3-effects/src/main/java/com/jme3/water/ReflectionProcessor.java b/jme3-effects/src/main/java/com/jme3/water/ReflectionProcessor.java new file mode 100644 index 000000000..6307c474e --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/water/ReflectionProcessor.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.water; + +import com.jme3.math.Plane; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; + +/** + * Reflection Processor + * Used to render the reflected scene in an off view port + */ +public class ReflectionProcessor implements SceneProcessor { + + private RenderManager rm; + private ViewPort vp; + private Camera reflectionCam; + private FrameBuffer reflectionBuffer; + private Plane reflectionClipPlane; + + /** + * Creates a ReflectionProcessor + * @param reflectionCam the cam to use for reflection + * @param reflectionBuffer the FrameBuffer to render to + * @param reflectionClipPlane the clipping plane + */ + public ReflectionProcessor(Camera reflectionCam, FrameBuffer reflectionBuffer, Plane reflectionClipPlane) { + this.reflectionCam = reflectionCam; + this.reflectionBuffer = reflectionBuffer; + this.reflectionClipPlane = reflectionClipPlane; + } + + public void initialize(RenderManager rm, ViewPort vp) { + this.rm = rm; + this.vp = vp; + } + + public void reshape(ViewPort vp, int w, int h) { + } + + public boolean isInitialized() { + return rm != null; + } + + public void preFrame(float tpf) { + } + + public void postQueue(RenderQueue rq) { + //we need special treatement for the sky because it must not be clipped + rm.getRenderer().setFrameBuffer(reflectionBuffer); + reflectionCam.setProjectionMatrix(null); + rm.setCamera(reflectionCam, false); + rm.getRenderer().clearBuffers(true, true, true); + //Rendering the sky whithout clipping + rm.getRenderer().setDepthRange(1, 1); + vp.getQueue().renderQueue(RenderQueue.Bucket.Sky, rm, reflectionCam, true); + rm.getRenderer().setDepthRange(0, 1); + //setting the clip plane to the cam + reflectionCam.setClipPlane(reflectionClipPlane, Plane.Side.Positive);//,1 + rm.setCamera(reflectionCam, false); + + } + + public void postFrame(FrameBuffer out) { + } + + public void cleanup() { + } + + /** + * Internal use only
      + * returns the frame buffer + * @return + */ + public FrameBuffer getReflectionBuffer() { + return reflectionBuffer; + } + + /** + * Internal use only
      + * sets the frame buffer + * @param reflectionBuffer + */ + public void setReflectionBuffer(FrameBuffer reflectionBuffer) { + this.reflectionBuffer = reflectionBuffer; + } + + /** + * returns the reflection cam + * @return + */ + public Camera getReflectionCam() { + return reflectionCam; + } + + /** + * sets the reflection cam + * @param reflectionCam + */ + public void setReflectionCam(Camera reflectionCam) { + this.reflectionCam = reflectionCam; + } + + /** + * returns the reflection clip plane + * @return + */ + public Plane getReflectionClipPlane() { + return reflectionClipPlane; + } + + /** + * Sets the reflection clip plane + * @param reflectionClipPlane + */ + public void setReflectionClipPlane(Plane reflectionClipPlane) { + this.reflectionClipPlane = reflectionClipPlane; + } +} diff --git a/jme3-effects/src/main/java/com/jme3/water/SimpleWaterProcessor.java b/jme3-effects/src/main/java/com/jme3/water/SimpleWaterProcessor.java new file mode 100644 index 000000000..51bec6a8b --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/water/SimpleWaterProcessor.java @@ -0,0 +1,607 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.water; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.ui.Picture; +import com.jme3.util.TempVars; + +/** + * + * Simple Water renders a simple plane that use reflection and refraction to look like water. + * It's pretty basic, but much faster than the WaterFilter + * It's useful if you aim low specs hardware and still want a good looking water. + * Usage is : + * + * SimpleWaterProcessor waterProcessor = new SimpleWaterProcessor(assetManager); + * //setting the scene to use for reflection + * waterProcessor.setReflectionScene(mainScene); + * //setting the light position + * waterProcessor.setLightPosition(lightPos); + * + * //setting the water plane + * Vector3f waterLocation=new Vector3f(0,-20,0); + * waterProcessor.setPlane(new Plane(Vector3f.UNIT_Y, waterLocation.dot(Vector3f.UNIT_Y))); + * //setting the water color + * waterProcessor.setWaterColor(ColorRGBA.Brown); + * + * //creating a quad to render water to + * Quad quad = new Quad(400,400); + * + * //the texture coordinates define the general size of the waves + * quad.scaleTextureCoordinates(new Vector2f(6f,6f)); + * + * //creating a geom to attach the water material + * Geometry water=new Geometry("water", quad); + * water.setLocalTranslation(-200, -20, 250); + * water.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); + * //finally setting the material + * water.setMaterial(waterProcessor.getMaterial()); + * + * //attaching the water to the root node + * rootNode.attachChild(water); + * + * @author Normen Hansen & Rémy Bouquet + */ +public class SimpleWaterProcessor implements SceneProcessor { + + protected RenderManager rm; + protected ViewPort vp; + protected Spatial reflectionScene; + protected ViewPort reflectionView; + protected ViewPort refractionView; + protected FrameBuffer reflectionBuffer; + protected FrameBuffer refractionBuffer; + protected Camera reflectionCam; + protected Camera refractionCam; + protected Texture2D reflectionTexture; + protected Texture2D refractionTexture; + protected Texture2D depthTexture; + protected Texture2D normalTexture; + protected Texture2D dudvTexture; + protected int renderWidth = 512; + protected int renderHeight = 512; + protected Plane plane = new Plane(Vector3f.UNIT_Y, Vector3f.ZERO.dot(Vector3f.UNIT_Y)); + protected float speed = 0.05f; + protected Ray ray = new Ray(); + protected Vector3f targetLocation = new Vector3f(); + protected AssetManager manager; + protected Material material; + protected float waterDepth = 1; + protected float waterTransparency = 0.4f; + protected boolean debug = false; + private Picture dispRefraction; + private Picture dispReflection; + private Picture dispDepth; + private Plane reflectionClipPlane; + private Plane refractionClipPlane; + private float refractionClippingOffset = 0.3f; + private float reflectionClippingOffset = -5f; + private float distortionScale = 0.2f; + private float distortionMix = 0.5f; + private float texScale = 1f; + + + /** + * Creates a SimpleWaterProcessor + * @param manager the asset manager + */ + public SimpleWaterProcessor(AssetManager manager) { + this.manager = manager; + material = new Material(manager, "Common/MatDefs/Water/SimpleWater.j3md"); + material.setFloat("waterDepth", waterDepth); + material.setFloat("waterTransparency", waterTransparency / 10); + material.setColor("waterColor", ColorRGBA.White); + material.setVector3("lightPos", new Vector3f(1, -1, 1)); + + material.setFloat("distortionScale", distortionScale); + material.setFloat("distortionMix", distortionMix); + material.setFloat("texScale", texScale); + updateClipPlanes(); + + } + + public void initialize(RenderManager rm, ViewPort vp) { + this.rm = rm; + this.vp = vp; + + loadTextures(manager); + createTextures(); + applyTextures(material); + + createPreViews(); + + material.setVector2("FrustumNearFar", new Vector2f(vp.getCamera().getFrustumNear(), vp.getCamera().getFrustumFar())); + + if (debug) { + dispRefraction = new Picture("dispRefraction"); + dispRefraction.setTexture(manager, refractionTexture, false); + dispReflection = new Picture("dispRefraction"); + dispReflection.setTexture(manager, reflectionTexture, false); + dispDepth = new Picture("depthTexture"); + dispDepth.setTexture(manager, depthTexture, false); + } + } + + public void reshape(ViewPort vp, int w, int h) { + } + + public boolean isInitialized() { + return rm != null; + } + float time = 0; + float savedTpf = 0; + + public void preFrame(float tpf) { + time = time + (tpf * speed); + if (time > 1f) { + time = 0; + } + material.setFloat("time", time); + savedTpf = tpf; + } + + public void postQueue(RenderQueue rq) { + Camera sceneCam = rm.getCurrentCamera(); + + //update refraction cam + refractionCam.setLocation(sceneCam.getLocation()); + refractionCam.setRotation(sceneCam.getRotation()); + refractionCam.setFrustum(sceneCam.getFrustumNear(), + sceneCam.getFrustumFar(), + sceneCam.getFrustumLeft(), + sceneCam.getFrustumRight(), + sceneCam.getFrustumTop(), + sceneCam.getFrustumBottom()); + refractionCam.setParallelProjection(sceneCam.isParallelProjection()); + + //update reflection cam + WaterUtils.updateReflectionCam(reflectionCam, plane, sceneCam); + + //Rendering reflection and refraction + rm.renderViewPort(reflectionView, savedTpf); + rm.renderViewPort(refractionView, savedTpf); + rm.getRenderer().setFrameBuffer(vp.getOutputFrameBuffer()); + rm.setCamera(sceneCam, false); + + } + + public void postFrame(FrameBuffer out) { + if (debug) { + displayMap(rm.getRenderer(), dispRefraction, 64); + displayMap(rm.getRenderer(), dispReflection, 256); + displayMap(rm.getRenderer(), dispDepth, 448); + } + } + + public void cleanup() { + } + + //debug only : displays maps + protected void displayMap(Renderer r, Picture pic, int left) { + Camera cam = vp.getCamera(); + rm.setCamera(cam, true); + int h = cam.getHeight(); + + pic.setPosition(left, h / 20f); + + pic.setWidth(128); + pic.setHeight(128); + pic.updateGeometricState(); + rm.renderGeometry(pic); + rm.setCamera(cam, false); + } + + protected void loadTextures(AssetManager manager) { + normalTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/water_normalmap.png"); + dudvTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/dudv_map.jpg"); + normalTexture.setWrap(WrapMode.Repeat); + dudvTexture.setWrap(WrapMode.Repeat); + } + + protected void createTextures() { + reflectionTexture = new Texture2D(renderWidth, renderHeight, Format.RGBA8); + refractionTexture = new Texture2D(renderWidth, renderHeight, Format.RGBA8); + depthTexture = new Texture2D(renderWidth, renderHeight, Format.Depth); + } + + protected void applyTextures(Material mat) { + mat.setTexture("water_reflection", reflectionTexture); + mat.setTexture("water_refraction", refractionTexture); + mat.setTexture("water_depthmap", depthTexture); + mat.setTexture("water_normalmap", normalTexture); + mat.setTexture("water_dudvmap", dudvTexture); + } + + protected void createPreViews() { + reflectionCam = new Camera(renderWidth, renderHeight); + refractionCam = new Camera(renderWidth, renderHeight); + + // create a pre-view. a view that is rendered before the main view + reflectionView = new ViewPort("Reflection View", reflectionCam); + reflectionView.setClearFlags(true, true, true); + reflectionView.setBackgroundColor(ColorRGBA.Black); + // create offscreen framebuffer + reflectionBuffer = new FrameBuffer(renderWidth, renderHeight, 1); + //setup framebuffer to use texture + reflectionBuffer.setDepthBuffer(Format.Depth); + reflectionBuffer.setColorTexture(reflectionTexture); + + //set viewport to render to offscreen framebuffer + reflectionView.setOutputFrameBuffer(reflectionBuffer); + reflectionView.addProcessor(new ReflectionProcessor(reflectionCam, reflectionBuffer, reflectionClipPlane)); + // attach the scene to the viewport to be rendered + reflectionView.attachScene(reflectionScene); + + // create a pre-view. a view that is rendered before the main view + refractionView = new ViewPort("Refraction View", refractionCam); + refractionView.setClearFlags(true, true, true); + refractionView.setBackgroundColor(ColorRGBA.Black); + // create offscreen framebuffer + refractionBuffer = new FrameBuffer(renderWidth, renderHeight, 1); + //setup framebuffer to use texture + refractionBuffer.setDepthBuffer(Format.Depth); + refractionBuffer.setColorTexture(refractionTexture); + refractionBuffer.setDepthTexture(depthTexture); + //set viewport to render to offscreen framebuffer + refractionView.setOutputFrameBuffer(refractionBuffer); + refractionView.addProcessor(new RefractionProcessor()); + // attach the scene to the viewport to be rendered + refractionView.attachScene(reflectionScene); + } + + protected void destroyViews() { + // rm.removePreView(reflectionView); + rm.removePreView(refractionView); + } + + /** + * Get the water material from this processor, apply this to your water quad. + * @return + */ + public Material getMaterial() { + return material; + } + + /** + * Sets the reflected scene, should not include the water quad! + * Set before adding processor. + * @param spat + */ + public void setReflectionScene(Spatial spat) { + reflectionScene = spat; + } + + /** + * returns the width of the reflection and refraction textures + * @return + */ + public int getRenderWidth() { + return renderWidth; + } + + /** + * returns the height of the reflection and refraction textures + * @return + */ + public int getRenderHeight() { + return renderHeight; + } + + /** + * Set the reflection Texture render size, + * set before adding the processor! + * @param width + * @param height + */ + public void setRenderSize(int width, int height) { + renderWidth = width; + renderHeight = height; + } + + /** + * returns the water plane + * @return + */ + public Plane getPlane() { + return plane; + } + + /** + * Set the water plane for this processor. + * @param plane + */ + public void setPlane(Plane plane) { + this.plane.setConstant(plane.getConstant()); + this.plane.setNormal(plane.getNormal()); + updateClipPlanes(); + } + + /** + * Set the water plane using an origin (location) and a normal (reflection direction). + * @param origin Set to 0,-6,0 if your water quad is at that location for correct reflection + * @param normal Set to 0,1,0 (Vector3f.UNIT_Y) for normal planar water + */ + public void setPlane(Vector3f origin, Vector3f normal) { + this.plane.setOriginNormal(origin, normal); + updateClipPlanes(); + } + + private void updateClipPlanes() { + reflectionClipPlane = plane.clone(); + reflectionClipPlane.setConstant(reflectionClipPlane.getConstant() + reflectionClippingOffset); + refractionClipPlane = plane.clone(); + refractionClipPlane.setConstant(refractionClipPlane.getConstant() + refractionClippingOffset); + + } + + /** + * Set the light Position for the processor + * @param position + */ + //TODO maybe we should provide a convenient method to compute position from direction + public void setLightPosition(Vector3f position) { + material.setVector3("lightPos", position); + } + + /** + * Set the color that will be added to the refraction texture. + * @param color + */ + public void setWaterColor(ColorRGBA color) { + material.setColor("waterColor", color); + } + + /** + * Higher values make the refraction texture shine through earlier. + * Default is 4 + * @param depth + */ + public void setWaterDepth(float depth) { + waterDepth = depth; + material.setFloat("waterDepth", depth); + } + + /** + * return the water depth + * @return + */ + public float getWaterDepth() { + return waterDepth; + } + + /** + * returns water transparency + * @return + */ + public float getWaterTransparency() { + return waterTransparency; + } + + /** + * sets the water transparency default os 0.1f + * @param waterTransparency + */ + public void setWaterTransparency(float waterTransparency) { + this.waterTransparency = Math.max(0, waterTransparency); + material.setFloat("waterTransparency", waterTransparency / 10); + } + + /** + * Sets the speed of the wave animation, default = 0.05f. + * @param speed + */ + public void setWaveSpeed(float speed) { + this.speed = speed; + } + + /** + * returns the speed of the wave animation. + * @return the speed + */ + public float getWaveSpeed(){ + return speed; + } + + /** + * Sets the scale of distortion by the normal map, default = 0.2 + */ + public void setDistortionScale(float value) { + distortionScale = value; + material.setFloat("distortionScale", distortionScale); + } + + /** + * Sets how the normal and dudv map are mixed to create the wave effect, default = 0.5 + */ + public void setDistortionMix(float value) { + distortionMix = value; + material.setFloat("distortionMix", distortionMix); + } + + /** + * 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) { + texScale = value; + material.setFloat("texScale", texScale); + } + + /** + * returns the scale of distortion by the normal map, default = 0.2 + * + * @return the distortion scale + */ + public float getDistortionScale() { + return distortionScale; + } + + /** + * returns how the normal and dudv map are mixed to create the wave effect, + * default = 0.5 + * + * @return the distortion mix + */ + public float getDistortionMix() { + return distortionMix; + } + + /** + * returns 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. + * + * @return the textures scale + */ + public float getTexScale() { + return texScale; + } + + + /** + * retruns true if the waterprocessor is in debug mode + * @return + */ + public boolean isDebug() { + return debug; + } + + /** + * set to true to display reflection and refraction textures in the GUI for debug purpose + * @param debug + */ + public void setDebug(boolean debug) { + this.debug = debug; + } + + /** + * Creates a quad with the water material applied to it. + * @param width + * @param height + * @return + */ + public Geometry createWaterGeometry(float width, float height) { + Quad quad = new Quad(width, height); + Geometry geom = new Geometry("WaterGeometry", quad); + geom.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); + geom.setMaterial(material); + return geom; + } + + /** + * returns the reflection clipping plane offset + * @return + */ + public float getReflectionClippingOffset() { + return reflectionClippingOffset; + } + + /** + * sets the reflection clipping plane offset + * set a nagetive value to lower the clipping plane for relection texture rendering. + * @param reflectionClippingOffset + */ + public void setReflectionClippingOffset(float reflectionClippingOffset) { + this.reflectionClippingOffset = reflectionClippingOffset; + updateClipPlanes(); + } + + /** + * returns the refraction clipping plane offset + * @return + */ + public float getRefractionClippingOffset() { + return refractionClippingOffset; + } + + /** + * Sets the refraction clipping plane offset + * set a positive value to raise the clipping plane for refraction texture rendering + * @param refractionClippingOffset + */ + public void setRefractionClippingOffset(float refractionClippingOffset) { + this.refractionClippingOffset = refractionClippingOffset; + updateClipPlanes(); + } + + /** + * Refraction Processor + */ + public class RefractionProcessor implements SceneProcessor { + + RenderManager rm; + ViewPort vp; + + public void initialize(RenderManager rm, ViewPort vp) { + this.rm = rm; + this.vp = vp; + } + + public void reshape(ViewPort vp, int w, int h) { + } + + public boolean isInitialized() { + return rm != null; + } + + public void preFrame(float tpf) { + refractionCam.setClipPlane(refractionClipPlane, Plane.Side.Negative);//,-1 + + } + + public void postQueue(RenderQueue rq) { + } + + public void postFrame(FrameBuffer out) { + } + + public void cleanup() { + } + } +} diff --git a/jme3-effects/src/main/java/com/jme3/water/WaterFilter.java b/jme3-effects/src/main/java/com/jme3/water/WaterFilter.java new file mode 100644 index 000000000..14c9b4f2f --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/water/WaterFilter.java @@ -0,0 +1,1119 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.water; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.post.Filter; +import com.jme3.post.Filter.Pass; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.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 com.jme3.util.TempVars; +import java.io.IOException; + +/** + * The WaterFilter is a 2D post process that simulate water. + * It renders water above and under water. + * See this blog post for more info http://jmonkeyengine.org/2011/01/15/new-advanced-water-effect-for-jmonkeyengine-3/ + * + * + * @author Rémy Bouquet aka Nehon + */ +public class WaterFilter extends Filter { + + private Pass reflectionPass; + protected Spatial reflectionScene; + protected ViewPort reflectionView; + private Texture2D normalTexture; + private Texture2D foamTexture; + private Texture2D causticsTexture; + private Texture2D heightTexture; + private Plane plane; + private Camera reflectionCam; + protected Ray ray = new Ray(); + private Vector3f targetLocation = new Vector3f(); + private ReflectionProcessor reflectionProcessor; + private Matrix4f biasMatrix = new Matrix4f(0.5f, 0.0f, 0.0f, 0.5f, + 0.0f, 0.5f, 0.0f, 0.5f, + 0.0f, 0.0f, 0.0f, 0.5f, + 0.0f, 0.0f, 0.0f, 1.0f); + private Matrix4f textureProjMatrix = new Matrix4f(); + private boolean underWater; + private RenderManager renderManager; + private ViewPort viewPort; + private float time = 0; + //properties + private float speed = 1; + private Vector3f lightDirection = new Vector3f(0, -1, 0); + private ColorRGBA lightColor = ColorRGBA.White; + private float waterHeight = 0.0f; + private ColorRGBA waterColor = new ColorRGBA(0.0078f, 0.3176f, 0.5f, 1.0f); + private ColorRGBA deepWaterColor = new ColorRGBA(0.0039f, 0.00196f, 0.145f, 1.0f); + private Vector3f colorExtinction = new Vector3f(5.0f, 20.0f, 30.0f); + private float waterTransparency = 0.1f; + private float maxAmplitude = 1.5f; + private float shoreHardness = 0.1f; + private boolean useFoam = true; + private float foamIntensity = 0.5f; + private float foamHardness = 1.0f; + private Vector3f foamExistence = new Vector3f(0.45f, 4.35f, 1.5f); + private float waveScale = 0.005f; + private float sunScale = 3.0f; + private float shininess = 0.7f; + private Vector2f windDirection = new Vector2f(0.0f, -1.0f); + private int reflectionMapSize = 512; + private boolean useRipples = true; + private float normalScale = 3.0f; + private boolean useHQShoreline = true; + private boolean useSpecular = true; + private boolean useRefraction = true; + private float refractionStrength = 0.0f; + private float refractionConstant = 0.5f; + private float reflectionDisplace = 30; + private float underWaterFogDistance = 120; + private boolean useCaustics = true; + private float causticsIntensity = 0.5f; + //positional attributes + private Vector3f center; + private float radius; + private AreaShape shapeType = AreaShape.Circular; + + public enum AreaShape{ + Circular, + Square + } + + /** + * Create a Water Filter + */ + public WaterFilter() { + super("WaterFilter"); + } + + public WaterFilter(Node reflectionScene, Vector3f lightDirection) { + super("WaterFilter"); + this.reflectionScene = reflectionScene; + this.lightDirection = lightDirection; + } + + @Override + protected boolean isRequiresDepthTexture() { + return true; + } + + @Override + protected void preFrame(float tpf) { + time = time + (tpf * speed); + material.setFloat("Time", time); + Camera sceneCam = viewPort.getCamera(); + biasMatrix.mult(sceneCam.getViewProjectionMatrix(), textureProjMatrix); + material.setMatrix4("TextureProjMatrix", textureProjMatrix); + material.setVector3("CameraPosition", sceneCam.getLocation()); + material.setFloat("WaterHeight", waterHeight); + + //update reflection cam + plane = new Plane(Vector3f.UNIT_Y, new Vector3f(0, waterHeight, 0).dot(Vector3f.UNIT_Y)); + reflectionProcessor.setReflectionClipPlane(plane); + WaterUtils.updateReflectionCam(reflectionCam, plane, sceneCam); + + + //if we're under water no need to compute reflection + if (sceneCam.getLocation().y >= waterHeight) { + boolean rtb = true; + if (!renderManager.isHandleTranslucentBucket()) { + renderManager.setHandleTranslucentBucket(true); + rtb = false; + } + renderManager.renderViewPort(reflectionView, tpf); + if (!rtb) { + renderManager.setHandleTranslucentBucket(false); + } + renderManager.setCamera(sceneCam, false); + renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer()); + + + underWater = false; + } else { + underWater = true; + } + } + + @Override + protected Material getMaterial() { + return material; + } + + private DirectionalLight findLight(Node node) { + for (Light light : node.getWorldLightList()) { + if (light instanceof DirectionalLight) { + return (DirectionalLight) light; + } + } + for (Spatial child : node.getChildren()) { + if (child instanceof Node) { + return findLight((Node) child); + } + } + + return null; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + + if (reflectionScene == null) { + reflectionScene = vp.getScenes().get(0); + DirectionalLight l = findLight((Node) reflectionScene); + if (l != null) { + lightDirection = l.getDirection(); + } + + } + + this.renderManager = renderManager; + this.viewPort = vp; + reflectionPass = new Pass(); + reflectionPass.init(renderManager.getRenderer(), reflectionMapSize, reflectionMapSize, Format.RGBA8, Format.Depth); + reflectionCam = new Camera(reflectionMapSize, reflectionMapSize); + reflectionView = new ViewPort("reflectionView", reflectionCam); + reflectionView.setClearFlags(true, true, true); + reflectionView.attachScene(reflectionScene); + reflectionView.setOutputFrameBuffer(reflectionPass.getRenderFrameBuffer()); + plane = new Plane(Vector3f.UNIT_Y, new Vector3f(0, waterHeight, 0).dot(Vector3f.UNIT_Y)); + reflectionProcessor = new ReflectionProcessor(reflectionCam, reflectionPass.getRenderFrameBuffer(), plane); + reflectionView.addProcessor(reflectionProcessor); + + normalTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/water_normalmap.dds"); + if (foamTexture == null) { + foamTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/foam.jpg"); + } + if (causticsTexture == null) { + causticsTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/caustics.jpg"); + } + heightTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/heightmap.jpg"); + + normalTexture.setWrap(WrapMode.Repeat); + foamTexture.setWrap(WrapMode.Repeat); + causticsTexture.setWrap(WrapMode.Repeat); + heightTexture.setWrap(WrapMode.Repeat); + + material = new Material(manager, "Common/MatDefs/Water/Water.j3md"); + material.setTexture("HeightMap", heightTexture); + material.setTexture("CausticsMap", causticsTexture); + material.setTexture("FoamMap", foamTexture); + material.setTexture("NormalMap", normalTexture); + material.setTexture("ReflectionMap", reflectionPass.getRenderedTexture()); + + material.setFloat("WaterTransparency", waterTransparency); + material.setFloat("NormalScale", normalScale); + material.setFloat("R0", refractionConstant); + material.setFloat("MaxAmplitude", maxAmplitude); + material.setVector3("LightDir", lightDirection); + material.setColor("LightColor", lightColor); + material.setFloat("ShoreHardness", shoreHardness); + material.setFloat("RefractionStrength", refractionStrength); + material.setFloat("WaveScale", waveScale); + material.setVector3("FoamExistence", foamExistence); + material.setFloat("SunScale", sunScale); + material.setVector3("ColorExtinction", colorExtinction); + material.setFloat("Shininess", shininess); + material.setColor("WaterColor", waterColor); + material.setColor("DeepWaterColor", deepWaterColor); + material.setVector2("WindDirection", windDirection); + material.setFloat("FoamHardness", foamHardness); + material.setBoolean("UseRipples", useRipples); + material.setBoolean("UseHQShoreline", useHQShoreline); + material.setBoolean("UseSpecular", useSpecular); + material.setBoolean("UseFoam", useFoam); + material.setBoolean("UseCaustics", useCaustics); + material.setBoolean("UseRefraction", useRefraction); + material.setFloat("ReflectionDisplace", reflectionDisplace); + material.setFloat("FoamIntensity", foamIntensity); + material.setFloat("UnderWaterFogDistance", underWaterFogDistance); + material.setFloat("CausticsIntensity", causticsIntensity); + if (center != null) { + material.setVector3("Center", center); + material.setFloat("Radius", radius * radius); + material.setBoolean("SquareArea", shapeType==AreaShape.Square); + } + + + } + + @Override + protected void cleanUpFilter(Renderer r) { + reflectionPass.cleanup(r); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + + oc.write(speed, "speed", 1f); + oc.write(lightDirection, "lightDirection", new Vector3f(0, -1, 0)); + oc.write(lightColor, "lightColor", ColorRGBA.White); + oc.write(waterHeight, "waterHeight", 0.0f); + oc.write(waterColor, "waterColor", new ColorRGBA(0.0078f, 0.3176f, 0.5f, 1.0f)); + oc.write(deepWaterColor, "deepWaterColor", new ColorRGBA(0.0039f, 0.00196f, 0.145f, 1.0f)); + + oc.write(colorExtinction, "colorExtinction", new Vector3f(5.0f, 20.0f, 30.0f)); + oc.write(waterTransparency, "waterTransparency", 0.1f); + oc.write(maxAmplitude, "maxAmplitude", 1.5f); + oc.write(shoreHardness, "shoreHardness", 0.1f); + oc.write(useFoam, "useFoam", true); + + oc.write(foamIntensity, "foamIntensity", 0.5f); + oc.write(foamHardness, "foamHardness", 1.0f); + + oc.write(foamExistence, "foamExistence", new Vector3f(0.45f, 4.35f, 1.5f)); + oc.write(waveScale, "waveScale", 0.005f); + + oc.write(sunScale, "sunScale", 3.0f); + oc.write(shininess, "shininess", 0.7f); + oc.write(windDirection, "windDirection", new Vector2f(0.0f, -1.0f)); + oc.write(reflectionMapSize, "reflectionMapSize", 512); + oc.write(useRipples, "useRipples", true); + + oc.write(normalScale, "normalScale", 3.0f); + oc.write(useHQShoreline, "useHQShoreline", true); + + oc.write(useSpecular, "useSpecular", true); + + oc.write(useRefraction, "useRefraction", true); + oc.write(refractionStrength, "refractionStrength", 0.0f); + oc.write(refractionConstant, "refractionConstant", 0.5f); + oc.write(reflectionDisplace, "reflectionDisplace", 30f); + oc.write(underWaterFogDistance, "underWaterFogDistance", 120f); + oc.write(causticsIntensity, "causticsIntensity", 0.5f); + + oc.write(useCaustics, "useCaustics", true); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + speed = ic.readFloat("speed", 1f); + lightDirection = (Vector3f) ic.readSavable("lightDirection", new Vector3f(0, -1, 0)); + lightColor = (ColorRGBA) ic.readSavable("lightColor", ColorRGBA.White); + waterHeight = ic.readFloat("waterHeight", 0.0f); + waterColor = (ColorRGBA) ic.readSavable("waterColor", new ColorRGBA(0.0078f, 0.3176f, 0.5f, 1.0f)); + deepWaterColor = (ColorRGBA) ic.readSavable("deepWaterColor", new ColorRGBA(0.0039f, 0.00196f, 0.145f, 1.0f)); + + colorExtinction = (Vector3f) ic.readSavable("colorExtinction", new Vector3f(5.0f, 20.0f, 30.0f)); + waterTransparency = ic.readFloat("waterTransparency", 0.1f); + maxAmplitude = ic.readFloat("maxAmplitude", 1.5f); + shoreHardness = ic.readFloat("shoreHardness", 0.1f); + useFoam = ic.readBoolean("useFoam", true); + + foamIntensity = ic.readFloat("foamIntensity", 0.5f); + foamHardness = ic.readFloat("foamHardness", 1.0f); + + foamExistence = (Vector3f) ic.readSavable("foamExistence", new Vector3f(0.45f, 4.35f, 1.5f)); + waveScale = ic.readFloat("waveScale", 0.005f); + + sunScale = ic.readFloat("sunScale", 3.0f); + shininess = ic.readFloat("shininess", 0.7f); + windDirection = (Vector2f) ic.readSavable("windDirection", new Vector2f(0.0f, -1.0f)); + reflectionMapSize = ic.readInt("reflectionMapSize", 512); + useRipples = ic.readBoolean("useRipples", true); + + normalScale = ic.readFloat("normalScale", 3.0f); + useHQShoreline = ic.readBoolean("useHQShoreline", true); + + useSpecular = ic.readBoolean("useSpecular", true); + + useRefraction = ic.readBoolean("useRefraction", true); + refractionStrength = ic.readFloat("refractionStrength", 0.0f); + refractionConstant = ic.readFloat("refractionConstant", 0.5f); + reflectionDisplace = ic.readFloat("reflectionDisplace", 30f); + underWaterFogDistance = ic.readFloat("underWaterFogDistance", 120f); + causticsIntensity = ic.readFloat("causticsIntensity", 0.5f); + + useCaustics = ic.readBoolean("useCaustics", true); + + } + + /** + * gets the height of the water plane + * @return + */ + public float getWaterHeight() { + return waterHeight; + } + + /** + * Sets the height of the water plane + * default is 0.0 + * @param waterHeight + */ + public void setWaterHeight(float waterHeight) { + this.waterHeight = waterHeight; + } + + /** + * sets the scene to render in the reflection map + * @param reflectionScene + */ + public void setReflectionScene(Spatial reflectionScene) { + this.reflectionScene = reflectionScene; + } + + /** + * returns the waterTransparency value + * @return + */ + public float getWaterTransparency() { + return waterTransparency; + } + + /** + * Sets how fast will colours fade out. You can also think about this + * values as how clear water is. Therefore use smaller values (eg. 0.05) + * to have crystal clear water and bigger to achieve "muddy" water. + * default is 0.1f + * @param waterTransparency + */ + public void setWaterTransparency(float waterTransparency) { + this.waterTransparency = waterTransparency; + if (material != null) { + material.setFloat("WaterTransparency", waterTransparency); + } + } + + /** + * Returns the normal scales applied to the normal map + * @return + */ + public float getNormalScale() { + return normalScale; + } + + /** + * Sets the normal scaling factors to apply to the normal map. + * the higher the value the more small ripples will be visible on the waves. + * default is 1.0 + * @param normalScale + */ + public void setNormalScale(float normalScale) { + this.normalScale = normalScale; + if (material != null) { + material.setFloat("NormalScale", normalScale); + } + } + + /** + * returns the refractoin constant + * @return + */ + public float getRefractionConstant() { + return refractionConstant; + } + + /** + * This is a constant related to the index of refraction (IOR) used to compute the fresnel term. + * F = R0 + (1-R0)( 1 - N.V)^5 + * where F is the fresnel term, R0 the constant, N the normal vector and V tne view vector. + * It usually depend on the material you are lookinh through (here water). + * Default value is 0.3f + * In practice, the lowest the value and the less the reflection can be seen on water + * @param refractionConstant + */ + public void setRefractionConstant(float refractionConstant) { + this.refractionConstant = refractionConstant; + if (material != null) { + material.setFloat("R0", refractionConstant); + } + } + + /** + * return the maximum wave amplitude + * @return + */ + public float getMaxAmplitude() { + return maxAmplitude; + } + + /** + * Sets the maximum waves amplitude + * default is 1.0 + * @param maxAmplitude + */ + public void setMaxAmplitude(float maxAmplitude) { + this.maxAmplitude = maxAmplitude; + if (material != null) { + material.setFloat("MaxAmplitude", maxAmplitude); + } + } + + /** + * gets the light direction + * @return + */ + public Vector3f getLightDirection() { + return lightDirection; + } + + /** + * Sets the light direction + * @param lightDirection + */ + public void setLightDirection(Vector3f lightDirection) { + this.lightDirection = lightDirection; + if (material != null) { + material.setVector3("LightDir", lightDirection); + } + } + + /** + * returns the light color + * @return + */ + public ColorRGBA getLightColor() { + return lightColor; + } + + /** + * Sets the light color to use + * default is white + * @param lightColor + */ + public void setLightColor(ColorRGBA lightColor) { + this.lightColor = lightColor; + if (material != null) { + material.setColor("LightColor", lightColor); + } + } + + /** + * Return the shoreHardeness + * @return + */ + public float getShoreHardness() { + return shoreHardness; + } + + /** + * The smaller this value is, the softer the transition between + * shore and water. If you want hard edges use very big value. + * Default is 0.1f. + * @param shoreHardness + */ + public void setShoreHardness(float shoreHardness) { + this.shoreHardness = shoreHardness; + if (material != null) { + material.setFloat("ShoreHardness", shoreHardness); + } + } + + /** + * returns the foam hardness + * @return + */ + public float getFoamHardness() { + return foamHardness; + } + + /** + * Sets the foam hardness : How much the foam will blend with the shore to avoid hard edged water plane. + * Default is 1.0 + * @param foamHardness + */ + public void setFoamHardness(float foamHardness) { + this.foamHardness = foamHardness; + if (material != null) { + material.setFloat("FoamHardness", foamHardness); + } + } + + /** + * returns the refractionStrenght + * @return + */ + public float getRefractionStrength() { + return refractionStrength; + } + + /** + * This value modifies current fresnel term. If you want to weaken + * reflections use bigger value. If you want to empasize them use + * value smaller then 0. Default is 0.0f. + * @param refractionStrength + */ + public void setRefractionStrength(float refractionStrength) { + this.refractionStrength = refractionStrength; + if (material != null) { + material.setFloat("RefractionStrength", refractionStrength); + } + } + + /** + * returns the scale factor of the waves height map + * @return + */ + public float getWaveScale() { + return waveScale; + } + + /** + * Sets the scale factor of the waves height map + * the smaller the value the bigger the waves + * default is 0.005f + * @param waveScale + */ + public void setWaveScale(float waveScale) { + this.waveScale = waveScale; + if (material != null) { + material.setFloat("WaveScale", waveScale); + } + } + + /** + * returns the foam existance vector + * @return + */ + public Vector3f getFoamExistence() { + return foamExistence; + } + + /** + * Describes at what depth foam starts to fade out and + * at what it is completely invisible. The third value is at + * what height foam for waves appear (+ waterHeight). + * default is (0.45, 4.35, 1.0); + * @param foamExistence + */ + public void setFoamExistence(Vector3f foamExistence) { + this.foamExistence = foamExistence; + if (material != null) { + material.setVector3("FoamExistence", foamExistence); + } + } + + /** + * gets the scale of the sun + * @return + */ + public float getSunScale() { + return sunScale; + } + + /** + * Sets the scale of the sun for specular effect + * @param sunScale + */ + public void setSunScale(float sunScale) { + this.sunScale = sunScale; + if (material != null) { + material.setFloat("SunScale", sunScale); + } + } + + /** + * Returns the color exctinction vector of the water + * @return + */ + public Vector3f getColorExtinction() { + return colorExtinction; + } + + /** + * Return at what depth the refraction color extinct + * the first value is for red + * the second is for green + * the third is for blue + * Play with thos parameters to "trouble" the water + * default is (5.0, 20.0, 30.0f); + * @param colorExtinction + */ + public void setColorExtinction(Vector3f colorExtinction) { + this.colorExtinction = colorExtinction; + if (material != null) { + material.setVector3("ColorExtinction", colorExtinction); + } + } + + /** + * Sets the foam texture + * @param foamTexture + */ + public void setFoamTexture(Texture2D foamTexture) { + this.foamTexture = foamTexture; + foamTexture.setWrap(WrapMode.Repeat); + if (material != null) { + material.setTexture("FoamMap", foamTexture); + } + } + + /** + * Sets the height texture + * @param heightTexture + */ + public void setHeightTexture(Texture2D heightTexture) { + this.heightTexture = heightTexture; + heightTexture.setWrap(WrapMode.Repeat); + if (material != null) { + material.setTexture("HeightMap", heightTexture); + } + } + + /** + * Sets the normal Texture + * @param normalTexture + */ + public void setNormalTexture(Texture2D normalTexture) { + this.normalTexture = normalTexture; + normalTexture.setWrap(WrapMode.Repeat); + if (material != null) { + material.setTexture("NormalMap", normalTexture); + } + } + + /** + * return the shininess factor of the water + * @return + */ + public float getShininess() { + return shininess; + } + + /** + * Sets the shinines factor of the water + * default is 0.7f + * @param shininess + */ + public void setShininess(float shininess) { + this.shininess = shininess; + if (material != null) { + material.setFloat("Shininess", shininess); + } + } + + /** + * retruns the speed of the waves + * @return + */ + public float getSpeed() { + return speed; + } + + /** + * Set the speed of the waves (0.0 is still) default is 1.0 + * @param speed + */ + public void setSpeed(float speed) { + this.speed = speed; + } + + /** + * returns the color of the water + * + * @return + */ + public ColorRGBA getWaterColor() { + return waterColor; + } + + /** + * Sets the color of the water + * see setDeepWaterColor for deep water color + * default is (0.0078f, 0.5176f, 0.5f,1.0f) (greenish blue) + * @param waterColor + */ + public void setWaterColor(ColorRGBA waterColor) { + this.waterColor = waterColor; + if (material != null) { + material.setColor("WaterColor", waterColor); + } + } + + /** + * returns the deep water color + * @return + */ + public ColorRGBA getDeepWaterColor() { + return deepWaterColor; + } + + /** + * sets the deep water color + * see setWaterColor for general color + * default is (0.0039f, 0.00196f, 0.145f,1.0f) (very dark blue) + * @param deepWaterColor + */ + public void setDeepWaterColor(ColorRGBA deepWaterColor) { + this.deepWaterColor = deepWaterColor; + if (material != null) { + material.setColor("DeepWaterColor", deepWaterColor); + } + } + + /** + * returns the wind direction + * @return + */ + public Vector2f getWindDirection() { + return windDirection; + } + + /** + * sets the wind direction + * the direction where the waves move + * default is (0.0f, -1.0f) + * @param windDirection + */ + public void setWindDirection(Vector2f windDirection) { + this.windDirection = windDirection; + if (material != null) { + material.setVector2("WindDirection", windDirection); + } + } + + /** + * returns the size of the reflection map + * @return + */ + public int getReflectionMapSize() { + return reflectionMapSize; + } + + /** + * Sets the size of the reflection map + * default is 512, the higher, the better quality, but the slower the effect. + * @param reflectionMapSize + */ + public void setReflectionMapSize(int reflectionMapSize) { + this.reflectionMapSize = reflectionMapSize; + //if reflection pass is already initialized we must update it + if(reflectionPass != null){ + reflectionPass.init(renderManager.getRenderer(), reflectionMapSize, reflectionMapSize, Format.RGBA8, Format.Depth); + reflectionCam.resize(reflectionMapSize, reflectionMapSize, true); + reflectionProcessor.setReflectionBuffer(reflectionPass.getRenderFrameBuffer()); + material.setTexture("ReflectionMap", reflectionPass.getRenderedTexture()); + } + + } + + /** + * Whether or not the water uses foam + * @return true if the water uses foam + */ + public boolean isUseFoam() { + return useFoam; + } + + /** + * set to true to use foam with water + * default true + * @param useFoam + */ + public void setUseFoam(boolean useFoam) { + this.useFoam = useFoam; + if (material != null) { + material.setBoolean("UseFoam", useFoam); + } + + } + + /** + * sets the texture to use to render caustics on the ground underwater + * @param causticsTexture + */ + public void setCausticsTexture(Texture2D causticsTexture) { + this.causticsTexture = causticsTexture; + if (material != null) { + material.setTexture("causticsMap", causticsTexture); + } + } + + /** + * Whether or not caustics are rendered + * @return true if caustics are rendered + */ + public boolean isUseCaustics() { + return useCaustics; + } + + /** + * set to true if you want caustics to be rendered on the ground underwater, false otherwise + * @param useCaustics + */ + public void setUseCaustics(boolean useCaustics) { + this.useCaustics = useCaustics; + if (material != null) { + material.setBoolean("UseCaustics", useCaustics); + } + } + + /** + * Whether or not the shader is set to use high-quality shoreline. + * @return true if high-quality shoreline is enabled + */ + public boolean isUseHQShoreline() { + return useHQShoreline; + } + + public void setUseHQShoreline(boolean useHQShoreline) { + this.useHQShoreline = useHQShoreline; + if (material != null) { + material.setBoolean("UseHQShoreline", useHQShoreline); + } + + } + + /** + * Whether or not the water uses the refraction + * @return true if the water uses refraction + */ + public boolean isUseRefraction() { + return useRefraction; + } + + /** + * set to true to use refraction (default is true) + * @param useRefraction + */ + public void setUseRefraction(boolean useRefraction) { + this.useRefraction = useRefraction; + if (material != null) { + material.setBoolean("UseRefraction", useRefraction); + } + + } + + /** + * Whether or not the water uses ripples + * @return true if the water is set to use ripples + */ + public boolean isUseRipples() { + return useRipples; + } + + /** + * + * Set to true to use ripples + * @param useRipples + */ + public void setUseRipples(boolean useRipples) { + this.useRipples = useRipples; + if (material != null) { + material.setBoolean("UseRipples", useRipples); + } + + } + + /** + * Whether or not the water is using specular + * @return true if the water is set to use specular + */ + public boolean isUseSpecular() { + return useSpecular; + } + + /** + * Set to true to use specular lightings on the water + * @param useSpecular + */ + public void setUseSpecular(boolean useSpecular) { + this.useSpecular = useSpecular; + if (material != null) { + material.setBoolean("UseSpecular", useSpecular); + } + } + + /** + * returns the foam intensity + * @return + */ + public float getFoamIntensity() { + return foamIntensity; + } + + /** + * sets the foam intensity default is 0.5f + * @param foamIntensity + */ + public void setFoamIntensity(float foamIntensity) { + this.foamIntensity = foamIntensity; + if (material != null) { + material.setFloat("FoamIntensity", foamIntensity); + + } + } + + /** + * returns the reflection displace + * see {@link #setReflectionDisplace(float) } + * @return + */ + public float getReflectionDisplace() { + return reflectionDisplace; + } + + /** + * Sets the reflection displace. define how troubled will look the reflection in the water. default is 30 + * @param reflectionDisplace + */ + public void setReflectionDisplace(float reflectionDisplace) { + this.reflectionDisplace = reflectionDisplace; + if (material != null) { + material.setFloat("ReflectionDisplace", reflectionDisplace); + } + } + + /** + * Whether or not the camera is under the water level + * @return true if the camera is under the water level + */ + public boolean isUnderWater() { + return underWater; + } + + /** + * returns the distance of the fog when under water + * @return + */ + public float getUnderWaterFogDistance() { + return underWaterFogDistance; + } + + /** + * sets the distance of the fog when under water. + * default is 120 (120 world units) use a high value to raise the view range under water + * @param underWaterFogDistance + */ + public void setUnderWaterFogDistance(float underWaterFogDistance) { + this.underWaterFogDistance = underWaterFogDistance; + if (material != null) { + material.setFloat("UnderWaterFogDistance", underWaterFogDistance); + } + } + + /** + * get the intensity of caustics under water + * @return + */ + public float getCausticsIntensity() { + return causticsIntensity; + } + + /** + * sets the intensity of caustics under water. goes from 0 to 1, default is 0.5f + * @param causticsIntensity + */ + public void setCausticsIntensity(float causticsIntensity) { + this.causticsIntensity = causticsIntensity; + if (material != null) { + material.setFloat("CausticsIntensity", causticsIntensity); + } + } + + /** + * returns the center of this effect + * @return the center of this effect + */ + public Vector3f getCenter() { + return center; + } + + /** + * Set the center of the effect. + * By default the water will extent to the entire scene. + * By setting a center and a radius you can restrain it to a portion of the scene. + * @param center the center of the effect + */ + public void setCenter(Vector3f center) { + this.center = center; + if (material != null) { + material.setVector3("Center", center); + } + } + + /** + * returns the radius of this effect + * @return the radius of this effect + */ + public float getRadius() { + return radius; + + } + + /** + * Set the radius of the effect. + * By default the water will extent to the entire scene. + * By setting a center and a radius you can restrain it to a portion of the scene. + * @param radius the radius of the effect + */ + public void setRadius(float radius) { + this.radius = radius; + if (material != null) { + material.setFloat("Radius", radius * radius); + } + } + + /** + * returns the shape of the water area + * @return the shape of the water area + */ + public AreaShape getShapeType() { + return shapeType; + } + + /** + * Set the shape of the water area (Circular (default) or Square). + * if the shape is square the radius is considered as an extent. + * @param shapeType the shape type + */ + public void setShapeType(AreaShape shapeType) { + this.shapeType = shapeType; + if (material != null) { + material.setBoolean("SquareArea", shapeType==AreaShape.Square); + } + } + + +} diff --git a/jme3-effects/src/main/java/com/jme3/water/WaterUtils.java b/jme3-effects/src/main/java/com/jme3/water/WaterUtils.java new file mode 100644 index 000000000..f232a6512 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/water/WaterUtils.java @@ -0,0 +1,53 @@ +/* + * 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.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.util.TempVars; + +/** + * + * @author Nehon + */ +public class WaterUtils { + + public static void updateReflectionCam(Camera reflectionCam, Plane plane, Camera sceneCam){ + + TempVars vars = TempVars.get(); + //Temp vects for reflection cam orientation calculation + Vector3f sceneTarget = vars.vect1; + Vector3f reflectDirection = vars.vect2; + Vector3f reflectUp = vars.vect3; + Vector3f reflectLeft = vars.vect4; + Vector3f camLoc = vars.vect5; + camLoc = plane.reflect(sceneCam.getLocation(), camLoc); + reflectionCam.setLocation(camLoc); + reflectionCam.setFrustum(sceneCam.getFrustumNear(), + sceneCam.getFrustumFar(), + sceneCam.getFrustumLeft(), + sceneCam.getFrustumRight(), + sceneCam.getFrustumTop(), + sceneCam.getFrustumBottom()); + reflectionCam.setParallelProjection(sceneCam.isParallelProjection()); + + sceneTarget.set(sceneCam.getLocation()).addLocal(sceneCam.getDirection()); + reflectDirection = plane.reflect(sceneTarget, reflectDirection); + reflectDirection.subtractLocal(camLoc); + + sceneTarget.set(sceneCam.getLocation()).subtractLocal(sceneCam.getUp()); + reflectUp = plane.reflect(sceneTarget, reflectUp); + reflectUp.subtractLocal(camLoc); + + sceneTarget.set(sceneCam.getLocation()).addLocal(sceneCam.getLeft()); + reflectLeft = plane.reflect(sceneTarget, reflectLeft); + reflectLeft.subtractLocal(camLoc); + + reflectionCam.setAxes(reflectLeft, reflectUp, reflectDirection); + + vars.release(); + } +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomExtract.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomExtract.j3md new file mode 100644 index 000000000..8158b6c8e --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomExtract.j3md @@ -0,0 +1,38 @@ +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 { + } + + 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 { + } + + Defines { + HAS_GLOWMAP : GlowMap + DO_EXTRACT : Extract + } + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomFinal.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomFinal.j3md new file mode 100644 index 000000000..2c614fdc7 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomFinal.j3md @@ -0,0 +1,29 @@ +MaterialDef Bloom Final { + + MaterialParameters { + Int NumSamples + Texture2D Texture + Texture2D BloomTex + Float BloomIntensity + } + + Technique { + VertexShader GLSL150: Common/MatDefs/Post/Post15.vert + FragmentShader GLSL150: Common/MatDefs/Post/bloomFinal15.frag + + WorldParameters { + } + + Defines { + RESOLVE_MS : NumSamples + } + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL100: Common/MatDefs/Post/bloomFinal.frag + + WorldParameters { + } + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag new file mode 100644 index 000000000..e7520601e --- /dev/null +++ b/jme3-effects/src/main/resources/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_ResolutionInverse; + +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_ResolutionInverse; + + 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/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.j3md new file mode 100644 index 000000000..91ad18de4 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.j3md @@ -0,0 +1,43 @@ +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 { + WorldViewMatrix + ResolutionInverse + } + + Defines { + RESOLVE_MS : NumSamples + RESOLVE_DEPTH_MS : NumSamplesDepth + } + } + + + Technique { + VertexShader GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL100: Common/MatDefs/Post/CartoonEdge.frag + + WorldParameters { + WorldViewMatrix + ResolutionInverse + } + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge15.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge15.frag new file mode 100644 index 000000000..7481954a9 --- /dev/null +++ b/jme3-effects/src/main/resources/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_ResolutionInverse; + +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) * g_ResolutionInverse; + 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/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose.frag new file mode 100644 index 000000000..24eb80c22 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose.frag @@ -0,0 +1,10 @@ +uniform sampler2D m_Texture; +uniform sampler2D m_CompositeTexture; +varying vec2 texCoord; + +void main() { + vec4 texVal = texture2D(m_Texture, texCoord); + vec4 compositeVal = texture2D(m_CompositeTexture, texCoord); + gl_FragColor = mix(compositeVal,texVal,texVal.a); +} + diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose.j3md new file mode 100644 index 000000000..bcd796aa8 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose.j3md @@ -0,0 +1,31 @@ +MaterialDef Default GUI { + + MaterialParameters { + Int NumSamples + Int NumSamplesDepth + Texture2D Texture + Texture2D CompositeTexture + } + + Technique { + VertexShader GLSL150: Common/MatDefs/Post/Post15.vert + FragmentShader GLSL150: Common/MatDefs/Post/Compose15.frag + + WorldParameters { + } + + Defines { + RESOLVE_MS : NumSamples + } + + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL100: Common/MatDefs/Post/Compose.frag + + WorldParameters { + } + + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose15.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose15.frag new file mode 100644 index 000000000..3a4032f91 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose15.frag @@ -0,0 +1,14 @@ +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +uniform COLORTEXTURE m_CompositeTexture; +in vec2 texCoord; + +out vec4 finalColor; + +void main() { + vec4 texVal = getColor(m_Texture, texCoord); + vec4 compositeVal = getColor(m_CompositeTexture, texCoord); + finalColor = mix(compositeVal,texVal,texVal.a); +} + diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag new file mode 100644 index 000000000..fc700f883 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag @@ -0,0 +1,51 @@ +uniform sampler2D m_Texture; +varying vec2 texCoord; + +uniform vec4 m_LineColor; +uniform vec4 m_PaperColor; +uniform float m_ColorInfluenceLine; +uniform float m_ColorInfluencePaper; + +uniform float m_FillValue; +uniform float m_Luminance1; +uniform float m_Luminance2; +uniform float m_Luminance3; +uniform float m_Luminance4; +uniform float m_Luminance5; + +uniform float m_LineDistance; +uniform float m_LineThickness; + +void main() { + vec4 texVal = texture2D(m_Texture, texCoord); + float linePixel = 0.0; + + float lum = texVal.r*0.2126 + texVal.g*0.7152 + texVal.b*0.0722; + + if (lum < m_Luminance1){ + if (mod(gl_FragCoord.x + gl_FragCoord.y, m_LineDistance * 2.0) < m_LineThickness) + linePixel = 1.0; + } + if (lum < m_Luminance2){ + if (mod(gl_FragCoord.x - gl_FragCoord.y, m_LineDistance * 2.0) < m_LineThickness) + linePixel = 1.0; + } + if (lum < m_Luminance3){ + if (mod(gl_FragCoord.x + gl_FragCoord.y - m_LineDistance, m_LineDistance) < m_LineThickness) + linePixel = 1.0; + } + if (lum < m_Luminance4){ + if (mod(gl_FragCoord.x - gl_FragCoord.y - m_LineDistance, m_LineDistance) < m_LineThickness) + linePixel = 1.0; + } + if (lum < m_Luminance5){ // No line, make a blob instead + linePixel = m_FillValue; + } + + // Mix line color with existing color information + vec4 lineColor = mix(m_LineColor, texVal, m_ColorInfluenceLine); + // Mix paper color with existing color information + vec4 paperColor = mix(m_PaperColor, texVal, m_ColorInfluencePaper); + + gl_FragColor = mix(paperColor, lineColor, linePixel); +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.j3md new file mode 100644 index 000000000..dadcd77e1 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.j3md @@ -0,0 +1,36 @@ +MaterialDef CrossHatch { + + MaterialParameters { + Int NumSamples + Texture2D Texture; + Vector4 LineColor; + Vector4 PaperColor; + Float ColorInfluenceLine; + Float ColorInfluencePaper; + Float FillValue; + Float Luminance1; + Float Luminance2; + Float Luminance3; + Float Luminance4; + Float Luminance5; + Float LineThickness; + Float LineDistance; + } + + Technique { + VertexShader GLSL150: Common/MatDefs/Post/Post15.vert + FragmentShader GLSL150: Common/MatDefs/Post/CrossHatch15.frag + + WorldParameters { + } + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL100: Common/MatDefs/Post/CrossHatch.frag + + WorldParameters { + WorldViewProjectionMatrix + } + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch15.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch15.frag new file mode 100644 index 000000000..8daa4f7fd --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch15.frag @@ -0,0 +1,53 @@ +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +in vec2 texCoord; + +uniform vec4 m_LineColor; +uniform vec4 m_PaperColor; +uniform float m_ColorInfluenceLine; +uniform float m_ColorInfluencePaper; + +uniform float m_FillValue; +uniform float m_Luminance1; +uniform float m_Luminance2; +uniform float m_Luminance3; +uniform float m_Luminance4; +uniform float m_Luminance5; + +uniform float m_LineDistance; +uniform float m_LineThickness; + +void main() { + vec4 texVal = getColor(m_Texture, texCoord); + float linePixel = 0; + + float lum = texVal.r*0.2126 + texVal.g*0.7152 + texVal.b*0.0722; + + if (lum < m_Luminance1){ + if (mod(gl_FragCoord.x + gl_FragCoord.y, m_LineDistance * 2.0) < m_LineThickness) + linePixel = 1; + } + if (lum < m_Luminance2){ + if (mod(gl_FragCoord.x - gl_FragCoord.y, m_LineDistance * 2.0) < m_LineThickness) + linePixel = 1; + } + if (lum < m_Luminance3){ + if (mod(gl_FragCoord.x + gl_FragCoord.y - m_LineDistance, m_LineDistance) < m_LineThickness) + linePixel = 1; + } + if (lum < m_Luminance4){ + if (mod(gl_FragCoord.x - gl_FragCoord.y - m_LineDistance, m_LineDistance) < m_LineThickness) + linePixel = 1; + } + if (lum < m_Luminance5){ // No line, make a blob instead + linePixel = m_FillValue; + } + + // Mix line color with existing color information + vec4 lineColor = mix(m_LineColor, texVal, m_ColorInfluenceLine); + // Mix paper color with existing color information + vec4 paperColor = mix(m_PaperColor, texVal, m_ColorInfluencePaper); + + gl_FragColor = mix(paperColor, lineColor, linePixel); +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthOfField.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthOfField.frag new file mode 100644 index 000000000..576fecac6 --- /dev/null +++ b/jme3-effects/src/main/resources/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/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthOfField.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthOfField.j3md new file mode 100644 index 000000000..cd190253a --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthOfField.j3md @@ -0,0 +1,34 @@ +MaterialDef Depth Of Field { + + MaterialParameters { + Int NumSamples + Int NumSamplesDepth + Texture2D Texture + Texture2D DepthTexture + Float FocusRange; + Float FocusDistance; + Float XScale; + Float YScale; + } + + Technique { + VertexShader GLSL150: Common/MatDefs/Post/Post15.vert + FragmentShader GLSL150: Common/MatDefs/Post/DepthOfField15.frag + + WorldParameters { + } + + Defines { + RESOLVE_MS : NumSamples + RESOLVE_DEPTH_MS : NumSamplesDepth + } + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL100: Common/MatDefs/Post/DepthOfField.frag + + WorldParameters { + } + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthOfField15.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthOfField15.frag new file mode 100644 index 000000000..80cf3c530 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthOfField15.frag @@ -0,0 +1,91 @@ +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +uniform DEPTHTEXTURE 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 = getColor( m_Texture, texCoord ); + + float zBuffer = getDepth( 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 += getColor( m_Texture, vec2(x - 2.0 * xScale, y - 2.0 * yScale) ); + sum += getColor( m_Texture, vec2(x - 0.0 * xScale, y - 2.0 * yScale) ); + sum += getColor( m_Texture, vec2(x + 2.0 * xScale, y - 2.0 * yScale) ); + sum += getColor( m_Texture, vec2(x - 1.0 * xScale, y - 1.0 * yScale) ); + sum += getColor( m_Texture, vec2(x + 1.0 * xScale, y - 1.0 * yScale) ); + sum += getColor( m_Texture, vec2(x - 2.0 * xScale, y - 0.0 * yScale) ); + sum += getColor( m_Texture, vec2(x + 2.0 * xScale, y - 0.0 * yScale) ); + sum += getColor( m_Texture, vec2(x - 1.0 * xScale, y + 1.0 * yScale) ); + sum += getColor( m_Texture, vec2(x + 1.0 * xScale, y + 1.0 * yScale) ); + sum += getColor( m_Texture, vec2(x - 2.0 * xScale, y + 2.0 * yScale) ); + sum += getColor( m_Texture, vec2(x - 0.0 * xScale, y + 2.0 * yScale) ); + sum += getColor( 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; + } +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag new file mode 100644 index 000000000..2fea421ab --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag @@ -0,0 +1,88 @@ +#extension GL_EXT_gpu_shader4 : enable + +uniform sampler2D m_Texture; +uniform vec2 g_ResolutionInverse; + +uniform float m_VxOffset; +uniform float m_SpanMax; +uniform float m_ReduceMul; + +varying vec2 texCoord; +varying vec4 posPos; + +#define FxaaTex(t, p) texture2D(t, p) + +#if __VERSION__ >= 130 + #define OffsetVec(a, b) ivec2(a, b) + #define FxaaTexOff(t, p, o, r) textureOffset(t, p, o) +#elif defined(GL_EXT_gpu_shader4) + #define OffsetVec(a, b) ivec2(a, b) + #define FxaaTexOff(t, p, o, r) texture2DLodOffset(t, p, 0.0, o) +#else + #define OffsetVec(a, b) vec2(a, b) + #define FxaaTexOff(t, p, o, r) texture2D(t, p + o * r) +#endif + +vec3 FxaaPixelShader( + vec4 posPos, // Output of FxaaVertexShader interpolated across screen. + sampler2D tex, // Input texture. + vec2 rcpFrame) // Constant {1.0/frameWidth, 1.0/frameHeight}. +{ + + #define FXAA_REDUCE_MIN (1.0/128.0) + //#define FXAA_REDUCE_MUL (1.0/8.0) + //#define FXAA_SPAN_MAX 8.0 + + vec3 rgbNW = FxaaTex(tex, posPos.zw).xyz; + vec3 rgbNE = FxaaTexOff(tex, posPos.zw, OffsetVec(1,0), rcpFrame.xy).xyz; + vec3 rgbSW = FxaaTexOff(tex, posPos.zw, OffsetVec(0,1), rcpFrame.xy).xyz; + vec3 rgbSE = FxaaTexOff(tex, posPos.zw, OffsetVec(1,1), rcpFrame.xy).xyz; + + vec3 rgbM = FxaaTex(tex, posPos.xy).xyz; + + vec3 luma = vec3(0.299, 0.587, 0.114); + float lumaNW = dot(rgbNW, luma); + float lumaNE = dot(rgbNE, luma); + float lumaSW = dot(rgbSW, luma); + float lumaSE = dot(rgbSE, luma); + float lumaM = dot(rgbM, luma); + + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + vec2 dir; + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + + float dirReduce = max( + (lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * m_ReduceMul), + FXAA_REDUCE_MIN); + float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce); + dir = min(vec2( m_SpanMax, m_SpanMax), + max(vec2(-m_SpanMax, -m_SpanMax), + dir * rcpDirMin)) * rcpFrame.xy; + + vec3 rgbA = (1.0/2.0) * ( + FxaaTex(tex, posPos.xy + dir * vec2(1.0/3.0 - 0.5)).xyz + + FxaaTex(tex, posPos.xy + dir * vec2(2.0/3.0 - 0.5)).xyz); + vec3 rgbB = rgbA * (1.0/2.0) + (1.0/4.0) * ( + FxaaTex(tex, posPos.xy + dir * vec2(0.0/3.0 - 0.5)).xyz + + FxaaTex(tex, posPos.xy + dir * vec2(3.0/3.0 - 0.5)).xyz); + + float lumaB = dot(rgbB, luma); + + if ((lumaB < lumaMin) || (lumaB > lumaMax)) + { + return rgbA; + } + else + { + return rgbB; + } +} + +void main() +{ + + gl_FragColor = vec4(FxaaPixelShader(posPos, m_Texture, g_ResolutionInverse), 1.0); +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.j3md new file mode 100644 index 000000000..61e2b79ef --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.j3md @@ -0,0 +1,17 @@ +MaterialDef FXAA { + MaterialParameters { + Int NumSamples + Texture2D Texture + Float SubPixelShift + Float VxOffset + Float SpanMax + Float ReduceMul + } + Technique { + VertexShader GLSL100: Common/MatDefs/Post/FXAA.vert + FragmentShader GLSL100: Common/MatDefs/Post/FXAA.frag + WorldParameters { + ResolutionInverse + } + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.vert b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.vert new file mode 100644 index 000000000..e0887a638 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.vert @@ -0,0 +1,18 @@ +uniform mat4 g_WorldViewProjectionMatrix; +uniform vec2 g_ResolutionInverse; + +uniform float m_SubPixelShift; + +attribute vec4 inPosition; +attribute vec2 inTexCoord; + +varying vec2 texCoord; +varying vec4 posPos; + +void main() { + vec2 pos = inPosition.xy * 2.0 - 1.0; + gl_Position = vec4(pos, 0.0, 1.0); + texCoord = inTexCoord; + posPos.xy = inTexCoord.xy; + posPos.zw = inTexCoord.xy - (g_ResolutionInverse * vec2(0.5 + m_SubPixelShift)); +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Fade.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Fade.frag new file mode 100644 index 000000000..b616239d0 --- /dev/null +++ b/jme3-effects/src/main/resources/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/jme3-effects/src/main/resources/Common/MatDefs/Post/Fade.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Fade.j3md new file mode 100644 index 000000000..46d36e49b --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Fade.j3md @@ -0,0 +1,28 @@ +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 { + } + + Defines { + RESOLVE_MS : NumSamples + } + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL100: Common/MatDefs/Post/Fade.frag + + WorldParameters { + } + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Fade15.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Fade15.frag new file mode 100644 index 000000000..c99de34ad --- /dev/null +++ b/jme3-effects/src/main/resources/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/jme3-effects/src/main/resources/Common/MatDefs/Post/Fog.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Fog.frag new file mode 100644 index 000000000..7321b1516 --- /dev/null +++ b/jme3-effects/src/main/resources/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/jme3-effects/src/main/resources/Common/MatDefs/Post/Fog.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Fog.j3md new file mode 100644 index 000000000..fc966af11 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Fog.j3md @@ -0,0 +1,33 @@ +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 { + } + + Defines { + RESOLVE_MS : NumSamples + RESOLVE_DEPTH_MS : NumSamplesDepth + } + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL100: Common/MatDefs/Post/Fog.frag + + WorldParameters { + } + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Fog15.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Fog15.frag new file mode 100644 index 000000000..65a340723 --- /dev/null +++ b/jme3-effects/src/main/resources/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/jme3-effects/src/main/resources/Common/MatDefs/Post/GammaCorrection.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/GammaCorrection.frag new file mode 100644 index 000000000..90b2adedc --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/GammaCorrection.frag @@ -0,0 +1,23 @@ +uniform sampler2D m_Texture; +varying vec2 texCoord; + +uniform float m_gamma; + +vec3 gamma(vec3 L,float gamma) +{ + return pow(L, vec3(1.0 / gamma)); +} + +void main() { + vec4 texVal = texture2D(m_Texture, texCoord); + + if(m_gamma > 0.0) + { + texVal.rgb = gamma(texVal.rgb , m_gamma); + } + #ifdef COMPUTE_LUMA + texVal.a = dot(texVal.rgb, vec3(0.299, 0.587, 0.114)); + #endif + + gl_FragColor = texVal; +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/GammaCorrection.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/GammaCorrection.j3md new file mode 100644 index 000000000..973679efd --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/GammaCorrection.j3md @@ -0,0 +1,33 @@ +MaterialDef GammaCorrection { + + MaterialParameters { + Int NumSamples + Texture2D Texture + Float gamma + Boolean computeLuma + } + + Technique { + VertexShader GLSL150: Common/MatDefs/Post/Post15.vert + FragmentShader GLSL150: Common/MatDefs/Post/GammaCorrection15.frag + + WorldParameters { + } + + Defines { + COMPUTE_LUMA : computeLuma + } + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL100: Common/MatDefs/Post/GammaCorrection.frag + + WorldParameters { + } + + Defines { + COMPUTE_LUMA : computeLuma + } + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/GammaCorrection15.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/GammaCorrection15.frag new file mode 100644 index 000000000..b7861908a --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/GammaCorrection15.frag @@ -0,0 +1,26 @@ +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +in vec2 texCoord; + +uniform float m_gamma; + +vec3 gamma(vec3 L,float gamma) +{ + return pow(L, vec3(1.0 / gamma)); +} + +void main() { + vec4 texVal = texture2D(m_Texture, texCoord); + + if(m_gamma > 0.0) + { + texVal.rgb = gamma(texVal.rgb , m_gamma); + } + + #ifdef COMPUTE_LUMA + texVal.a = dot(texVal.rgb, vec3(0.299, 0.587, 0.114)); + #endif + + gl_FragColor = texVal; +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/LightScattering.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/LightScattering.frag new file mode 100644 index 000000000..7e1c542f0 --- /dev/null +++ b/jme3-effects/src/main/resources/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; +uniform vec3 m_LightPosition; + +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 - m_LightPosition.xy; + vec2 scaledCoord; + vec4 res = vec4(0.0); + for(int i=0; i= m_ExposureCutoff ) { + color = pow(color, vec4(m_ExposurePow)); + }else{ + color = vec4(0.0); + } + #endif + + #ifdef HAS_GLOWMAP + vec4 glowColor = texture2D( m_GlowMap, texCoord ); + glowColor = pow(glowColor, vec4(m_ExposurePow)); + color += glowColor; + #endif + + outFragColor = color; +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/bloomFinal.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/bloomFinal.frag new file mode 100644 index 000000000..9499e589c --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/bloomFinal.frag @@ -0,0 +1,12 @@ +uniform sampler2D m_Texture; +uniform sampler2D m_BloomTex; +uniform float m_BloomIntensity; + +varying vec2 texCoord; + +void main(){ + vec4 colorRes = texture2D(m_Texture, texCoord); + vec4 bloom = texture2D(m_BloomTex, texCoord); + gl_FragColor = bloom * m_BloomIntensity + colorRes; +} + diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/bloomFinal15.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/bloomFinal15.frag new file mode 100644 index 000000000..34268c565 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/bloomFinal15.frag @@ -0,0 +1,15 @@ +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; + +uniform sampler2D m_BloomTex; +uniform float m_BloomIntensity; + +in vec2 texCoord; + +void main(){ + vec4 colorRes = getColor(m_Texture,texCoord); + vec4 bloom = texture2D(m_BloomTex, texCoord); + gl_FragColor = bloom * m_BloomIntensity + colorRes; +} + diff --git a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/Textures/random.png b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/Textures/random.png new file mode 100644 index 000000000..e19b7f594 Binary files /dev/null and b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/Textures/random.png differ diff --git a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.frag b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.frag new file mode 100644 index 000000000..82121067b --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.frag @@ -0,0 +1,20 @@ +varying vec3 normal; +varying vec2 texCoord; + + +#ifdef DIFFUSEMAP_ALPHA + uniform sampler2D m_DiffuseMap; + uniform float m_AlphaDiscardThreshold; +#endif + +void main(void) +{ + + #ifdef DIFFUSEMAP_ALPHA + if(texture2D(m_DiffuseMap,texCoord).a 0.0001){ + foam += ((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity * m_FoamIntensity * 0.3 * + saturate((level - (m_WaterHeight + m_FoamExistence.z)) / (m_MaxAmplitude - m_FoamExistence.z))).rgb; + } + foam *= m_LightColor.rgb; + #endif + + + + vec3 specular = vec3(0.0); + vec3 color ; + float fogFactor; + + if(position.y>level){ + #ifdef ENABLE_SPECULAR + if(step(0.9999,sceneDepth)==1.0){ + vec3 lightDir=normalize(m_LightDir); + vec3 mirrorEye = (2.0 * dot(eyeVecNorm, normal) * normal - eyeVecNorm); + float dotSpec = saturate(dot(mirrorEye.xyz, -lightDir) * 0.5 + 0.5); + specular = vec3((1.0 - fresnel) * saturate(-lightDir.y) * ((pow(dotSpec, 512.0)) * (m_Shininess * 1.8 + 0.2))); + specular += specular * 25.0 * saturate(m_Shininess - 0.05); + specular=specular * m_LightColor.rgb * 100.0; + } + #endif + float fogIntensity= 8.0 * m_WaterTransparency; + fogFactor = exp2( -fogIntensity * fogIntensity * cameraDepth * 0.03 * LOG2 ); + fogFactor = clamp(fogFactor, 0.0, 1.0); + color =mix(m_DeepWaterColor.rgb,refraction,fogFactor); + specular=specular*fogFactor; + color = saturate(color + max(specular, foam )); + }else{ + vec3 caustics = vec3(0.0); + #ifdef ENABLE_CAUSTICS + vec2 windDirection=m_WindDirection; + texC = (position.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * windDirection + sin(m_Time + position.x) * 0.01; + vec2 texCoord2 = (position.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * windDirection + sin(m_Time + position.z) * 0.01; + caustics += (texture2D(m_CausticsMap, texC)+ texture2D(m_CausticsMap, texCoord2)).rgb; + caustics=saturate(mix(m_WaterColor.rgb,caustics,m_CausticsIntensity)); + color=mix(color2,caustics,m_CausticsIntensity); + #else + color=color2; + #endif + + float fogDepth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - sceneDepth* (m_FrustumNearFar.y-m_FrustumNearFar.x)); + float fogIntensity= 18 * m_WaterTransparency; + fogFactor = exp2( -fogIntensity * fogIntensity * fogDepth * fogDepth * LOG2 ); + fogFactor = clamp(fogFactor, 0.0, 1.0); + color =mix(m_DeepWaterColor.rgb,color,fogFactor); + } + + return vec4(color, 1.0); +} + +void main(){ + float sceneDepth = texture2D(m_DepthTexture, texCoord).r; + float isAtFarPlane = step(0.99998, sceneDepth); + + vec3 color2 = texture2D(m_Texture, texCoord).rgb; + vec3 color = color2; + + vec3 position = getPosition(sceneDepth,texCoord); + float level = m_WaterHeight; + + // If we are underwater let's go to under water function + if(level >= m_CameraPosition.y){ + #ifdef ENABLE_AREA + vec3 dist = m_CameraPosition-m_Center; + if(isOverExtent(m_CameraPosition, m_Center, m_Radius)){ + gl_FragColor = vec4(color2, 1.0); + return; + } + #endif + gl_FragColor = underWater(); + return ; + } + + #ifdef ENABLE_AREA + if(isOverExtent(position, m_Center, m_Radius)){ + gl_FragColor = vec4(color2, 1.0); + return; + } + #endif + + //#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)); + texC = clamp(texC,0.0,1.0); + refraction = texture2D(m_Texture, texC).rgb; + #endif + + vec3 waterPosition = surfacePoint.xyz; + waterPosition.y -= (level - m_WaterHeight); + vec4 texCoordProj = m_TextureProjMatrix * vec4(waterPosition, 1.0); + + texCoordProj.x = texCoordProj.x + m_ReflectionDisplace * normal.x; + texCoordProj.z = texCoordProj.z + m_ReflectionDisplace * normal.z; + texCoordProj /= texCoordProj.w; + texCoordProj.y = 1.0 - texCoordProj.y; + + vec3 reflection = texture2D(m_ReflectionMap, texCoordProj.xy).rgb; + + float fresnel = fresnelTerm(normal, eyeVecNorm); + + float depthN = depth * m_WaterTransparency; + float waterCol = saturate(length(m_LightColor.rgb) / m_SunScale); + refraction = mix(mix(refraction, m_WaterColor.rgb * waterCol, saturate(depthN / visibility)), + m_DeepWaterColor.rgb * waterCol, saturate(depth2 / m_ColorExtinction)); + + vec3 foam = vec3(0.0); + #ifdef ENABLE_FOAM + texC = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * m_WindDirection + sin(m_Time * 0.001 + position.x) * 0.005; + vec2 texCoord2 = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.1 * m_WindDirection + sin(m_Time * 0.001 + position.z) * 0.005; + + if(depth2 < m_FoamExistence.x){ + foam = (texture2D(m_FoamMap, texC).r + texture2D(m_FoamMap, texCoord2)).rgb * m_FoamIntensity; + }else if(depth2 < m_FoamExistence.y){ + foam = mix((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity, vec4(0.0), + (depth2 - m_FoamExistence.x) / (m_FoamExistence.y - m_FoamExistence.x)).rgb; + } + + if(m_MaxAmplitude - m_FoamExistence.z > 0.0001){ + foam += ((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity * m_FoamIntensity * 0.3 * + saturate((level - (m_WaterHeight + m_FoamExistence.z)) / (m_MaxAmplitude - m_FoamExistence.z))).rgb; + } + foam *= m_LightColor.rgb; + #endif + + vec3 specular =vec3(0.0); + #ifdef ENABLE_SPECULAR + vec3 lightDir=normalize(m_LightDir); + vec3 mirrorEye = (2.0 * dot(eyeVecNorm, normal) * normal - eyeVecNorm); + float dotSpec = saturate(dot(mirrorEye.xyz, -lightDir) * 0.5 + 0.5); + specular = vec3((1.0 - fresnel) * saturate(-lightDir.y) * ((pow(dotSpec, 512.0)) * (m_Shininess * 1.8 + 0.2))); + specular += specular * 25.0 * saturate(m_Shininess - 0.05); + //foam does not shine + specular=specular * m_LightColor.rgb - (5.0 * foam); + #endif + + color = mix(refraction, reflection, fresnel); + color = mix(refraction, color, saturate(depth * m_ShoreHardness)); + color = saturate(color + max(specular, foam )); + color = mix(refraction, color, saturate(depth* m_FoamHardness)); + + + // XXX: HACK ALERT: + // We trick the GeForces to think they have + // to calculate the derivatives for all these pixels by using step()! + // That way we won't get pixels around the edges of the terrain, + // Where the derivatives are undefined + if(position.y > level){ + color = color2; + } + + gl_FragColor = vec4(color,1.0); + +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md new file mode 100644 index 000000000..b6a7525f5 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md @@ -0,0 +1,92 @@ +MaterialDef Advanced Water { + + MaterialParameters { + Int NumSamples + Int NumSamplesDepth + Texture2D FoamMap + Texture2D CausticsMap + Texture2D NormalMap + Texture2D ReflectionMap + Texture2D HeightMap + Texture2D Texture + Texture2D DepthTexture + Vector3 CameraPosition + Float Time + Vector3 frustumCorner + Matrix4 TextureProjMatrix + Float WaterHeight + Vector3 LightDir + Float WaterTransparency + Float NormalScale + Float R0 + Float MaxAmplitude + Color LightColor + Float ShoreHardness + Float FoamHardness + Float RefractionStrength + Float WaveScale + Vector3 FoamExistence + Float SunScale + Vector3 ColorExtinction + Float Shininess + Color WaterColor + Color DeepWaterColor + Vector2 WindDirection + Float ReflectionDisplace + Float FoamIntensity + Float CausticsIntensity + Float UnderWaterFogDistance + + Boolean UseRipples + Boolean UseHQShoreline + Boolean UseSpecular + Boolean UseFoam + Boolean UseCaustics + Boolean UseRefraction + + Float Radius + Vector3 Center + Boolean SquareArea + + } + + Technique { + VertexShader GLSL150 : Common/MatDefs/Post/Post15.vert + FragmentShader GLSL150 : Common/MatDefs/Water/Water15.frag + + WorldParameters { + ViewProjectionMatrixInverse + } + + Defines { + RESOLVE_MS : NumSamples + RESOLVE_DEPTH_MS : NumSamplesDepth + ENABLE_RIPPLES : UseRipples + ENABLE_HQ_SHORELINE : UseHQShoreline + ENABLE_SPECULAR : UseSpecular + ENABLE_FOAM : UseFoam + ENABLE_CAUSTICS : UseCaustics + ENABLE_REFRACTION : UseRefraction + ENABLE_AREA : Center + SQUARE_AREA : SquareArea + } + } + + Technique { + VertexShader GLSL100 : Common/MatDefs/Post/Post.vert + FragmentShader GLSL120 : Common/MatDefs/Water/Water.frag + + WorldParameters { + } + Defines { + ENABLE_RIPPLES : UseRipples + ENABLE_HQ_SHORELINE : UseHQShoreline + ENABLE_SPECULAR : UseSpecular + ENABLE_FOAM : UseFoam + ENABLE_CAUSTICS : UseCaustics + ENABLE_REFRACTION : UseRefraction + ENABLE_AREA : Center + SQUARE_AREA : SquareArea + } + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Water/Water15.frag b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water15.frag new file mode 100644 index 000000000..e6601e25a --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water15.frag @@ -0,0 +1,439 @@ +#import "Common/ShaderLib/MultiSample.glsllib" +#import "Common/ShaderLib/WaterUtil.glsllib" + +// Water pixel shader +// Copyright (C) JMonkeyEngine 3.0 +// by Remy Bouquet (nehon) for JMonkeyEngine 3.0 +// original HLSL version by Wojciech Toman 2009 + +uniform COLORTEXTURE m_Texture; +uniform DEPTHTEXTURE m_DepthTexture; + + +uniform sampler2D m_HeightMap; +uniform sampler2D m_NormalMap; +uniform sampler2D m_FoamMap; +uniform sampler2D m_CausticsMap; +uniform sampler2D m_ReflectionMap; + +uniform mat4 g_ViewProjectionMatrixInverse; +uniform mat4 m_TextureProjMatrix; +uniform vec3 m_CameraPosition; + +uniform float m_WaterHeight; +uniform float m_Time; +uniform float m_WaterTransparency; +uniform float m_NormalScale; +uniform float m_R0; +uniform float m_MaxAmplitude; +uniform vec3 m_LightDir; +uniform vec4 m_LightColor; +uniform float m_ShoreHardness; +uniform float m_FoamHardness; +uniform float m_RefractionStrength; +uniform vec3 m_FoamExistence; +uniform vec3 m_ColorExtinction; +uniform float m_Shininess; +uniform vec4 m_WaterColor; +uniform vec4 m_DeepWaterColor; +uniform vec2 m_WindDirection; +uniform float m_SunScale; +uniform float m_WaveScale; +uniform float m_UnderWaterFogDistance; +uniform float m_CausticsIntensity; + +#ifdef ENABLE_AREA +uniform vec3 m_Center; +uniform float m_Radius; +#endif + + +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 = g_ViewProjectionMatrixInverse * pos; + return pos.xyz / pos.w; +} + +// Function calculating fresnel term. +// - normal - normalized normal vector +// - eyeVec - normalized eye vector +float fresnelTerm(in vec3 normal,in vec3 eyeVec){ + float angle = 1.0 - max(0.0, dot(normal, eyeVec)); + float fresnel = angle * angle; + fresnel = fresnel * fresnel; + fresnel = fresnel * angle; + return saturate(fresnel * (1.0 - saturate(m_R0)) + m_R0 - m_RefractionStrength); +} + +vec2 m_FrustumNearFar=vec2(1.0,m_UnderWaterFogDistance); +const float LOG2 = 1.442695; + +vec4 underWater(int sampleNum){ + + + float sceneDepth = fetchTextureSample(m_DepthTexture, texCoord, sampleNum).r; + vec3 color2 = fetchTextureSample(m_Texture, texCoord, sampleNum).rgb; + + vec3 position = getPosition(sceneDepth, texCoord); + float level = m_WaterHeight; + + vec3 eyeVec = position - m_CameraPosition; + + // Find intersection with water surface + vec3 eyeVecNorm = normalize(eyeVec); + float t = (level - m_CameraPosition.y) / eyeVecNorm.y; + vec3 surfacePoint = m_CameraPosition + eyeVecNorm * t; + + vec2 texC = vec2(0.0); + + float cameraDepth = length(m_CameraPosition - surfacePoint); + texC = (surfacePoint.xz + eyeVecNorm.xz) * scale + m_Time * 0.03 * m_WindDirection; + float bias = texture(m_HeightMap, texC).r; + level += bias * m_MaxAmplitude; + t = (level - m_CameraPosition.y) / eyeVecNorm.y; + surfacePoint = m_CameraPosition + eyeVecNorm * t; + eyeVecNorm = normalize(m_CameraPosition - surfacePoint); + + // Find normal of water surface + float normal1 = textureOffset(m_HeightMap, texC, ivec2(-1.0, 0.0)).r; + float normal2 = textureOffset(m_HeightMap, texC, ivec2( 1.0, 0.0)).r; + float normal3 = textureOffset(m_HeightMap, texC, ivec2( 0.0, -1.0)).r; + float normal4 = textureOffset(m_HeightMap, texC, ivec2( 0.0, 1.0)).r; + + vec3 myNormal = normalize(vec3((normal1 - normal2) * m_MaxAmplitude,m_NormalScale,(normal3 - normal4) * m_MaxAmplitude)); + vec3 normal = myNormal*-1.0; + float fresnel = fresnelTerm(normal, eyeVecNorm); + + vec3 refraction = color2; + #ifdef ENABLE_REFRACTION + texC = texCoord.xy *sin (fresnel+1.0); + texC = clamp(texC,0.0,1.0); + #ifdef RESOLVE_MS + ivec2 iTexC = ivec2(texC * textureSize(m_Texture)); + refraction = texelFetch(m_Texture, iTexC, sampleNum).rgb; + #else + ivec2 iTexC = ivec2(texC * textureSize(m_Texture, 0)); + refraction = texelFetch(m_Texture, iTexC, 0).rgb; + #endif + #endif + + float waterCol = saturate(length(m_LightColor.rgb) / m_SunScale); + refraction = mix(mix(refraction, m_DeepWaterColor.rgb * waterCol, m_WaterTransparency), m_WaterColor.rgb* waterCol,m_WaterTransparency); + + vec3 foam = vec3(0.0); + #ifdef ENABLE_FOAM + texC = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * m_WindDirection + sin(m_Time * 0.001 + position.x) * 0.005; + vec2 texCoord2 = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.1 * m_WindDirection + sin(m_Time * 0.001 + position.z) * 0.005; + + if(m_MaxAmplitude - m_FoamExistence.z> 0.0001){ + foam += ((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity * m_FoamIntensity * 0.3 * + saturate((level - (m_WaterHeight + m_FoamExistence.z)) / (m_MaxAmplitude - m_FoamExistence.z))).rgb; + } + foam *= m_LightColor.rgb; + #endif + + + + vec3 specular = vec3(0.0); + vec3 color ; + float fogFactor; + + if(position.y>level){ + #ifdef ENABLE_SPECULAR + if(step(0.9999,sceneDepth)==1.0){ + vec3 lightDir=normalize(m_LightDir); + vec3 mirrorEye = (2.0 * dot(eyeVecNorm, normal) * normal - eyeVecNorm); + float dotSpec = saturate(dot(mirrorEye.xyz, -lightDir) * 0.5 + 0.5); + specular = vec3((1.0 - fresnel) * saturate(-lightDir.y) * ((pow(dotSpec, 512.0)) * (m_Shininess * 1.8 + 0.2))); + specular += specular * 25.0 * saturate(m_Shininess - 0.05); + specular=specular * m_LightColor.rgb * 100.0; + } + #endif + float fogIntensity= 8.0 * m_WaterTransparency; + fogFactor = exp2( -fogIntensity * fogIntensity * cameraDepth * 0.03 * LOG2 ); + fogFactor = clamp(fogFactor, 0.0, 1.0); + color =mix(m_DeepWaterColor.rgb,refraction,fogFactor); + specular=specular*fogFactor; + color = saturate(color + max(specular, foam )); + }else{ + vec3 caustics = vec3(0.0); + #ifdef ENABLE_CAUSTICS + vec2 windDirection=m_WindDirection; + texC = (position.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * windDirection + sin(m_Time + position.x) * 0.01; + vec2 texCoord2 = (position.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * windDirection + sin(m_Time + position.z) * 0.01; + caustics += (texture2D(m_CausticsMap, texC)+ texture2D(m_CausticsMap, texCoord2)).rgb; + caustics=saturate(mix(m_WaterColor.rgb,caustics,m_CausticsIntensity)); + color=mix(color2,caustics,m_CausticsIntensity); + #else + color=color2; + #endif + + float fogDepth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - sceneDepth* (m_FrustumNearFar.y-m_FrustumNearFar.x)); + float fogIntensity= 18 * m_WaterTransparency; + fogFactor = exp2( -fogIntensity * fogIntensity * fogDepth * fogDepth * LOG2 ); + fogFactor = clamp(fogFactor, 0.0, 1.0); + color =mix(m_DeepWaterColor.rgb,color,fogFactor); + } + + return vec4(color, 1.0); +} + + +// NOTE: This will be called even for single-sampling +vec4 main_multiSample(int sampleNum){ + // If we are underwater let's call the underwater function + if(m_WaterHeight >= m_CameraPosition.y){ + #ifdef ENABLE_AREA + if(isOverExtent(m_CameraPosition, m_Center, m_Radius)){ + return fetchTextureSample(m_Texture, texCoord, sampleNum); + } + #endif + return underWater(sampleNum); + } + + float sceneDepth = fetchTextureSample(m_DepthTexture, texCoord, sampleNum).r; + vec3 color2 = fetchTextureSample(m_Texture, texCoord, sampleNum).rgb; + + vec3 color = color2; + vec3 position = getPosition(sceneDepth, texCoord); + + #ifdef ENABLE_AREA + if(isOverExtent(position, m_Center, m_Radius)){ + return vec4(color2, 1.0); + } + #endif + + float level = m_WaterHeight; + + float isAtFarPlane = step(0.99998, sceneDepth); + //#ifndef ENABLE_RIPPLES + // This optimization won't work on NVIDIA cards if ripples are enabled + if(position.y > level + m_MaxAmplitude + isAtFarPlane * 100.0){ + + return vec4(color2, 1.0); + } + //#endif + + vec3 eyeVec = position - m_CameraPosition; + float cameraDepth = m_CameraPosition.y - position.y; + + // Find intersection with water surface + vec3 eyeVecNorm = normalize(eyeVec); + float t = (level - m_CameraPosition.y) / eyeVecNorm.y; + vec3 surfacePoint = m_CameraPosition + eyeVecNorm * t; + + vec2 texC = vec2(0.0); + int samples = 1; + #ifdef ENABLE_HQ_SHORELINE + samples = 10; + #endif + + float biasFactor = 1.0 / samples; + for (int i = 0; i < samples; i++){ + texC = (surfacePoint.xz + eyeVecNorm.xz * biasFactor) * scale + m_Time * 0.03 * m_WindDirection; + + float bias = texture(m_HeightMap, texC).r; + + bias *= biasFactor; + level += bias * m_MaxAmplitude; + t = (level - m_CameraPosition.y) / eyeVecNorm.y; + surfacePoint = m_CameraPosition + eyeVecNorm * t; + } + + float depth = length(position - surfacePoint); + float depth2 = surfacePoint.y - position.y; + + // XXX: HACK ALERT: Increase water depth to infinity if at far plane + // Prevents "foam on horizon" issue + // For best results, replace the "100.0" below with the + // highest value in the m_ColorExtinction vec3 + depth += isAtFarPlane * 100.0; + depth2 += isAtFarPlane * 100.0; + + eyeVecNorm = normalize(m_CameraPosition - surfacePoint); + + // Find normal of water surface + float normal1 = textureOffset(m_HeightMap, texC, ivec2(-1.0, 0.0)).r; + float normal2 = textureOffset(m_HeightMap, texC, ivec2( 1.0, 0.0)).r; + float normal3 = textureOffset(m_HeightMap, texC, ivec2( 0.0, -1.0)).r; + float normal4 = textureOffset(m_HeightMap, texC, ivec2( 0.0, 1.0)).r; + + vec3 myNormal = normalize(vec3((normal1 - normal2) * m_MaxAmplitude,m_NormalScale,(normal3 - normal4) * m_MaxAmplitude)); + vec3 normal = vec3(0.0); + + #ifdef ENABLE_RIPPLES + texC = surfacePoint.xz * 0.8 + m_WindDirection * m_Time* 1.6; + mat3 tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC); + vec3 normal0a = normalize(tangentFrame*(2.0 * texture(m_NormalMap, texC).xyz - 1.0)); + + texC = surfacePoint.xz * 0.4 + m_WindDirection * m_Time* 0.8; + tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC); + vec3 normal1a = normalize(tangentFrame*(2.0 * texture(m_NormalMap, texC).xyz - 1.0)); + + texC = surfacePoint.xz * 0.2 + m_WindDirection * m_Time * 0.4; + tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC); + vec3 normal2a = normalize(tangentFrame*(2.0 * texture(m_NormalMap, texC).xyz - 1.0)); + + texC = surfacePoint.xz * 0.1 + m_WindDirection * m_Time * 0.2; + tangentFrame = computeTangentFrame(myNormal, eyeVecNorm, texC); + vec3 normal3a = normalize(tangentFrame*(2.0 * texture(m_NormalMap, texC).xyz - 1.0)); + + normal = normalize(normal0a * normalModifier.x + normal1a * normalModifier.y +normal2a * normalModifier.z + normal3a * normalModifier.w); + // XXX: Here's another way to fix the terrain edge issue, + // But it requires GLSL 1.3 and still looks kinda incorrect + // around edges + normal = isnan(normal.x) ? myNormal : normal; + //if (position.y > level){ + // gl_FragColor = vec4(color2 + normal*0.0001, 1.0); + // return; + //} + #else + normal = myNormal; + #endif + + vec3 refraction = color2; + #ifdef ENABLE_REFRACTION + // texC = texCoord.xy+ m_ReflectionDisplace * normal.x; + texC = texCoord.xy; + texC += sin(m_Time*1.8 + 3.0 * abs(position.y))* (refractionScale * min(depth2, 1.0)); + texC = clamp(texC,vec2(0.0),vec2(0.999)); + #ifdef RESOLVE_MS + ivec2 iTexC = ivec2(texC * textureSize(m_Texture)); + refraction = texelFetch(m_Texture, iTexC, sampleNum).rgb; + #else + ivec2 iTexC = ivec2(texC * textureSize(m_Texture, 0)); + refraction = texelFetch(m_Texture, iTexC, 0).rgb; + #endif + #endif + + vec3 waterPosition = surfacePoint.xyz; + waterPosition.y -= (level - m_WaterHeight); + vec4 texCoordProj = m_TextureProjMatrix * vec4(waterPosition, 1.0); + + texCoordProj.x = texCoordProj.x + m_ReflectionDisplace * normal.x; + texCoordProj.z = texCoordProj.z + m_ReflectionDisplace * normal.z; + texCoordProj /= texCoordProj.w; + texCoordProj.y = 1.0 - texCoordProj.y; + + vec3 reflection = texture(m_ReflectionMap, texCoordProj.xy).rgb; + + float fresnel = fresnelTerm(normal, eyeVecNorm); + + float depthN = depth * m_WaterTransparency; + float waterCol = saturate(length(m_LightColor.rgb) / m_SunScale); + refraction = mix(mix(refraction, m_WaterColor.rgb * waterCol, saturate(depthN / visibility)), + m_DeepWaterColor.rgb * waterCol, saturate(depth2 / m_ColorExtinction)); + + + + + vec3 foam = vec3(0.0); + #ifdef ENABLE_FOAM + texC = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.05 * m_WindDirection + sin(m_Time * 0.001 + position.x) * 0.005; + vec2 texCoord2 = (surfacePoint.xz + eyeVecNorm.xz * 0.1) * 0.05 + m_Time * 0.1 * m_WindDirection + sin(m_Time * 0.001 + position.z) * 0.005; + + if(depth2 < m_FoamExistence.x){ + foam = (texture2D(m_FoamMap, texC).r + texture2D(m_FoamMap, texCoord2)).rgb * vec3(m_FoamIntensity); + }else if(depth2 < m_FoamExistence.y){ + foam = mix((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity , vec4(0.0), + (depth2 - m_FoamExistence.x) / (m_FoamExistence.y - m_FoamExistence.x)).rgb; + } + + + if(m_MaxAmplitude - m_FoamExistence.z> 0.0001){ + foam += ((texture2D(m_FoamMap, texC) + texture2D(m_FoamMap, texCoord2)) * m_FoamIntensity * m_FoamIntensity * 0.3 * + saturate((level - (m_WaterHeight + m_FoamExistence.z)) / (m_MaxAmplitude - m_FoamExistence.z))).rgb; + } + foam *= m_LightColor.rgb; + #endif + + vec3 specular = vec3(0.0); + #ifdef ENABLE_SPECULAR + vec3 lightDir=normalize(m_LightDir); + vec3 mirrorEye = (2.0 * dot(eyeVecNorm, normal) * normal - eyeVecNorm); + float dotSpec = saturate(dot(mirrorEye.xyz, -lightDir) * 0.5 + 0.5); + specular = vec3((1.0 - fresnel) * saturate(-lightDir.y) * ((pow(dotSpec, 512.0)) * (m_Shininess * 1.8 + 0.2))); + specular += specular * 25.0 * saturate(m_Shininess - 0.05); + //foam does not shine + specular=specular * m_LightColor.rgb - (5.0 * foam); + #endif + + color = mix(refraction, reflection, fresnel); + color = mix(refraction, color, saturate(depth * m_ShoreHardness)); + color = saturate(color + max(specular, foam )); + color = mix(refraction, color, saturate(depth* m_FoamHardness)); + + + // XXX: HACK ALERT: + // We trick the GeForces to think they have + // to calculate the derivatives for all these pixels by using step()! + // That way we won't get pixels around the edges of the terrain, + // Where the derivatives are undefined + return vec4(mix(color, color2, step(level, position.y)), 1.0); +} + +void main(){ + #ifdef RESOLVE_MS + vec4 color = vec4(0.0); + for (int i = 0; i < m_NumSamples; i++){ + color += main_multiSample(i); + } + outFragColor = color / m_NumSamples; + #else + outFragColor = main_multiSample(0); + #endif +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Water/simple_water.frag b/jme3-effects/src/main/resources/Common/MatDefs/Water/simple_water.frag new file mode 100644 index 000000000..ae9b10875 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Water/simple_water.frag @@ -0,0 +1,130 @@ +/* +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 float m_distortionScale; +uniform float m_distortionMix; +uniform float m_texScale; +/*uniform float m_distortionScale; +uniform float m_distortionMix; +uniform float m_texScale; +*/ +uniform vec2 m_FrustumNearFar; +uniform float m_waterTransparency; + + + +varying vec4 lightDir; //lightpos +varying vec2 waterTex1; //moving texcoords +varying vec2 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, waterTex2 * vec2(m_texScale)); + vec4 fdist = texture2D(m_water_dudvmap, waterTex1 + vec2(disdis) * vec2(m_distortionMix)); + fdist =normalize( fdist * 2.0 - 1.0)* vec4(m_distortionScale); + + //load normalmap + vec4 nmap = texture2D(m_water_normalmap, waterTex1 + vec2(disdis) * vec2(m_distortionMix)); + nmap = (nmap-ofive) * two; + // nmap = nmap*2.0-1.0; + vec4 vNorm = normalize(nmap); + + + vec4 projCoord = position / position.w; + projCoord =(projCoord+1.0)*0.5 + fdist; + projCoord = clamp(projCoord, 0.001, 0.999); + + //load reflection,refraction and depth texture + vec4 refl = texture2D(m_water_reflection, vec2(projCoord.x,1.0-projCoord.y)); + vec4 refr = texture2D(m_water_refraction, vec2(projCoord)); + vec4 wdepth =readDepth(vec2(projCoord)); + + wdepth = vec4(pow(wdepth.x, m_waterDepth)); + vec4 invdepth = 1.0 - wdepth; + + + // Blinn - Phong + // vec4 H = (viewt - lightTS); + // vec4 specular =vec4(pow(max(dot(H, vNorm), 0.0), exponent)); + +// Standard Phong + + // vec4 R =reflect(-L, vNorm); + // vec4 specular =vec4( pow(max(dot(R, E), 0.0),exponent)); + + + //calculate specular highlight + vec4 L=normalize(viewLightDir); + vec4 E=normalize(viewCamDir); + vec4 vRef = normalize(reflect(-L,vNorm)); + float stemp =max(0.0, dot( vRef,E) ); + //initializing to 0 to avoid artifacts on old intel cards + vec4 specular = vec4(0.0,0.0,0.0,0.0); + if(stemp>0.0){ + stemp = pow(stemp, exponent); + specular = vec4(stemp); + } + + + + vec4 fresnelTerm = vec4(0.02+0.97*pow((1.0-dot(normalize(viewt), vNorm)),5.0)); + + + + fresnelTerm=fresnelTerm*invdepth*m_waterTransparency; + fresnelTerm=clamp(fresnelTerm,0.0,1.0); + + refr*=(fresnelTerm); + refr *= invdepth; + refr= refr+ m_waterColor*wdepth*fresnelTerm; + + gl_FragColor =(refr+ refl*(1.0-fresnelTerm))+specular; +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Water/simple_water.vert b/jme3-effects/src/main/resources/Common/MatDefs/Water/simple_water.vert new file mode 100644 index 000000000..b6ec06675 --- /dev/null +++ b/jme3-effects/src/main/resources/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 vec2 waterTex1; +varying vec2 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; + + + vec2 t1 = vec2(0.0, -m_time); + vec2 t2 = vec2(0.0, m_time); + + waterTex1 = inTexCoord + t1; + waterTex2 = inTexCoord + t2; + + position = g_WorldViewProjectionMatrix * inPosition; + gl_Position = position; +} diff --git a/jme3-examples/src/main/java/jme3test/TestChooser.java b/jme3-examples/src/main/java/jme3test/TestChooser.java new file mode 100644 index 000000000..b72ab725a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/TestChooser.java @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.system.JmeContext; +import java.awt.*; +import java.awt.event.*; +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Vector; +import java.util.jar.JarFile; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + + +/** + * Class with a main method that displays a dialog to choose any jME demo to be + * started. + */ +public class TestChooser extends JDialog { + private static final Logger logger = Logger.getLogger(TestChooser.class + .getName()); + + private static final long serialVersionUID = 1L; + + /** + * Only accessed from EDT + */ + private Object[] selectedClass = null; + private boolean showSetting = true; + + /** + * Constructs a new TestChooser that is initially invisible. + */ + public TestChooser() throws HeadlessException { + super((JFrame) null, "TestChooser"); + } + + /** + * @param classes + * vector that receives the found classes + * @return classes vector, list of all the classes in a given package (must + * be found in classpath). + */ + protected Vector 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.fine("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.fine("Searching for Demo classes in \"" + url + "\"."); + URLConnection urlConnection = url.openConnection(); + if (urlConnection instanceof JarURLConnection) { + JarURLConnection conn = (JarURLConnection) urlConnection; + + JarFile jfile = conn.getJarFile(); + Enumeration e = jfile.entries(); + while (e.hasMoreElements()) { + ZipEntry entry = (ZipEntry) e.nextElement(); + Class result = load(entry.getName()); + if (result != null && !classes.contains(result)) { + classes.add(result); + } + } + } + } catch (IOException e) { + logger.logp(Level.SEVERE, this.getClass().toString(), + "find(pckgname, recursive, classes)", "Exception", e); + } catch (Exception e) { + logger.logp(Level.SEVERE, this.getClass().toString(), + "find(pckgname, recursive, classes)", "Exception", e); + } + } + return classes; + } + + /** + * Load a class specified by a file- or entry-name + * + * @param name + * name of a file or entry + * @return class file that was denoted by the name, null if no class or does + * not contain a main method + */ + private Class load(String name) { + if (name.endsWith(".class") + && name.indexOf("Test") >= 0 + && name.indexOf('$') < 0) { + String classname = name.substring(0, name.length() + - ".class".length()); + + if (classname.startsWith("/")) { + classname = classname.substring(1); + } + classname = classname.replace('/', '.'); + + try { + final Class cls = Class.forName(classname); + cls.getMethod("main", new Class[] { String[].class }); + if (!getClass().equals(cls)) { + return cls; + } + } catch (NoClassDefFoundError e) { + // class has unresolved dependencies + return null; + } catch (ClassNotFoundException e) { + // class not in classpath + return null; + } catch (NoSuchMethodException e) { + // class does not have a main method + return null; + } catch (UnsupportedClassVersionError e){ + // unsupported version + return null; + } + } + return null; + } + + /** + * Used to descent in directories, loads classes via {@link #load} + * + * @param directory + * where to search for class files + * @param allClasses + * add loaded classes to this collection + * @param packageName + * current package name for the diven directory + * @param recursive + * true to descent into subdirectories + */ + private void addAllFilesInDirectory(File directory, + Collection allClasses, String packageName, boolean recursive) { + // Get the list of the files contained in the package + File[] files = directory.listFiles(getFileFilter()); + if (files != null) { + for (int i = 0; i < files.length; i++) { + // we are only interested in .class files + if (files[i].isDirectory()) { + if (recursive) { + addAllFilesInDirectory(files[i], allClasses, + packageName + files[i].getName() + ".", true); + } + } else { + Class result = load(packageName + files[i].getName()); + if (result != null && !allClasses.contains(result)) { + allClasses.add(result); + } + } + } + } + } + + /** + * @return FileFilter for searching class files (no inner classes, only + * those with "Test" in the name) + */ + private FileFilter getFileFilter() { + return new FileFilter() { + public boolean accept(File pathname) { + return (pathname.isDirectory() && !pathname.getName().startsWith(".")) + || (pathname.getName().endsWith(".class") + && (pathname.getName().indexOf("Test") >= 0) + && pathname.getName().indexOf('$') < 0); + } + }; + } + + private void startApp(final Object[] appClass){ + if (appClass == null){ + JOptionPane.showMessageDialog(rootPane, + "Please select a test from the list", + "Error", + JOptionPane.ERROR_MESSAGE); + return; + } + + new Thread(new Runnable(){ + public void run(){ + for (int i = 0; i < appClass.length; i++) { + Class clazz = (Class)appClass[i]; + try { + Object app = clazz.newInstance(); + if (app instanceof Application) { + if (app instanceof SimpleApplication) { + final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class); + settingMethod.invoke(app, showSetting); + } + final Method mainMethod = clazz.getMethod("start"); + mainMethod.invoke(app); + Field contextField = Application.class.getDeclaredField("context"); + contextField.setAccessible(true); + JmeContext context = null; + while (context == null) { + context = (JmeContext) contextField.get(app); + Thread.sleep(100); + } + while (!context.isCreated()) { + Thread.sleep(100); + } + while (context.isCreated()) { + Thread.sleep(100); + } + } else { + final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass()); + mainMethod.invoke(app, new Object[]{new String[0]}); + } + // wait for destroy + System.gc(); + } catch (IllegalAccessException ex) { + logger.log(Level.SEVERE, "Cannot access constructor: "+clazz.getName(), ex); + } catch (IllegalArgumentException ex) { + logger.log(Level.SEVERE, "main() had illegal argument: "+clazz.getName(), ex); + } catch (InvocationTargetException ex) { + logger.log(Level.SEVERE, "main() method had exception: "+clazz.getName(), ex); + } catch (InstantiationException ex) { + logger.log(Level.SEVERE, "Failed to create app: "+clazz.getName(), ex); + } catch (NoSuchMethodException ex){ + logger.log(Level.SEVERE, "Test class doesn't have main method: "+clazz.getName(), ex); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Cannot start test: "+clazz.getName(), ex); + ex.printStackTrace(); + } + } + } + }).start(); + } + + /** + * Code to create components and action listeners. + * + * @param classes + * what Classes to show in the list box + */ + private void setup(Vector classes) { + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(mainPanel, BorderLayout.CENTER); + mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + + final FilteredJList list = new FilteredJList(); + list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + DefaultListModel model = new DefaultListModel(); + for (Class c : classes) { + model.addElement(c); + } + list.setModel(model); + + mainPanel.add(createSearchPanel(list), BorderLayout.NORTH); + mainPanel.add(new JScrollPane(list), BorderLayout.CENTER); + + list.getSelectionModel().addListSelectionListener( + new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + selectedClass = list.getSelectedValues(); + } + }); + list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2 && selectedClass != null) { + startApp(selectedClass); + } + } + }); + list.addKeyListener(new KeyAdapter() { + @Override + public void keyTyped(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + startApp(selectedClass); + } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + dispose(); + } + } + }); + + final JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + mainPanel.add(buttonPanel, BorderLayout.PAGE_END); + + final JButton okButton = new JButton("Ok"); + okButton.setMnemonic('O'); + buttonPanel.add(okButton); + getRootPane().setDefaultButton(okButton); + okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + startApp(selectedClass); + } + }); + + final JButton cancelButton = new JButton("Cancel"); + cancelButton.setMnemonic('C'); + buttonPanel.add(cancelButton); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dispose(); + } + }); + + pack(); + center(); + } + + private class FilteredJList extends JList { + private static final long serialVersionUID = 1L; + + private String filter; + private ListModel originalModel; + + public void setModel(ListModel m) { + originalModel = m; + super.setModel(m); + } + + private void update() { + if (filter == null || filter.length() == 0) { + super.setModel(originalModel); + } + + DefaultListModel v = new DefaultListModel(); + for (int i = 0; i < originalModel.getSize(); i++) { + Object o = originalModel.getElementAt(i); + String s = String.valueOf(o).toLowerCase(); + if (s.contains(filter)) { + v.addElement(o); + } + } + super.setModel(v); + if (v.getSize() == 1) { + setSelectedIndex(0); + } + revalidate(); + } + + public String getFilter() { + return filter; + } + + public void setFilter(String filter) { + this.filter = filter.toLowerCase(); + update(); + } + } + + /** + * center the frame. + */ + private void center() { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension frameSize = this.getSize(); + if (frameSize.height > screenSize.height) { + frameSize.height = screenSize.height; + } + if (frameSize.width > screenSize.width) { + frameSize.width = screenSize.width; + } + this.setLocation((screenSize.width - frameSize.width) / 2, + (screenSize.height - frameSize.height) / 2); + } + + /** + * Start the chooser. + * + * @param args + * command line parameters + */ + public static void main(final String[] args) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + } + new TestChooser().start(args); + } + + protected void start(String[] args) { + final Vector classes = new Vector(); + logger.fine("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) { + selectedClass = classes.getSelectedValues(); + startApp(selectedClass); + } + }); + final JCheckBox showSettingCheck = new JCheckBox("Show Setting"); + showSettingCheck.setSelected(true); + showSettingCheck.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + showSetting = showSettingCheck.isSelected(); + } + }); + jtf.setPreferredSize(new Dimension(100, 25)); + search.add(jtf, BorderLayout.CENTER); + search.add(showSettingCheck, BorderLayout.EAST); + return search; + } +} diff --git a/jme3-examples/src/main/java/jme3test/animation/SubtitleTrack.java b/jme3-examples/src/main/java/jme3test/animation/SubtitleTrack.java new file mode 100644 index 000000000..177050118 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/animation/SubtitleTrack.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.animation; + +import com.jme3.cinematic.events.GuiTrack; +import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.elements.render.TextRenderer; + +/** + * + * @author Nehon + */ +public class SubtitleTrack extends GuiTrack{ + private String text=""; + + public SubtitleTrack(Nifty nifty, String screen,float initialDuration, String text) { + super(nifty, screen, initialDuration); + this.text=text; + } + + @Override + public void onPlay() { + super.onPlay(); + nifty.getScreen(screen).findElementByName("text").getRenderer(TextRenderer.class).setText(text); + } + + + + + + + + +} diff --git a/jme3-examples/src/main/java/jme3test/animation/TestCameraMotionPath.java b/jme3-examples/src/main/java/jme3test/animation/TestCameraMotionPath.java new file mode 100644 index 000000000..ffe65a888 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/animation/TestCameraMotionPath.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.animation; + +import com.jme3.animation.LoopMode; +import com.jme3.app.SimpleApplication; +import com.jme3.cinematic.MotionPath; +import com.jme3.cinematic.MotionPathListener; +import com.jme3.cinematic.events.MotionEvent; +import com.jme3.cinematic.events.MotionEvent; +import com.jme3.font.BitmapText; +import com.jme3.input.ChaseCamera; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Spline.SplineType; +import com.jme3.math.Vector3f; +import com.jme3.scene.CameraNode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.CameraControl.ControlDirection; +import com.jme3.scene.shape.Box; + +public class TestCameraMotionPath extends SimpleApplication { + + private Spatial teapot; + private boolean active = true; + private boolean playing = false; + private MotionPath path; + private MotionEvent cameraMotionControl; + private ChaseCamera chaser; + private CameraNode camNode; + + public static void main(String[] args) { + TestCameraMotionPath app = new TestCameraMotionPath(); + app.start(); + } + + @Override + public void simpleInitApp() { + createScene(); + cam.setLocation(new Vector3f(8.4399185f, 11.189463f, 14.267577f)); + camNode = new CameraNode("Motion cam", cam); + camNode.setControlDir(ControlDirection.SpatialToCamera); + camNode.setEnabled(false); + path = new MotionPath(); + path.setCycle(true); + path.addWayPoint(new Vector3f(20, 3, 0)); + path.addWayPoint(new Vector3f(0, 3, 20)); + path.addWayPoint(new Vector3f(-20, 3, 0)); + path.addWayPoint(new Vector3f(0, 3, -20)); + path.setCurveTension(0.83f); + path.enableDebugShape(assetManager, rootNode); + + cameraMotionControl = new MotionEvent(camNode, path); + cameraMotionControl.setLoopMode(LoopMode.Loop); + //cameraMotionControl.setDuration(15f); + cameraMotionControl.setLookAt(teapot.getWorldTranslation(), Vector3f.UNIT_Y); + cameraMotionControl.setDirectionType(MotionEvent.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(MotionEvent 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.setEnabled(false); + } else { + playing = true; + chaser.setEnabled(false); + camNode.setEnabled(true); + cameraMotionControl.play(); + } + } + + if (name.equals("SwitchPathInterpolation") && keyPressed) { + if (path.getPathSplineType() == SplineType.CatmullRom) { + path.setPathSplineType(SplineType.Linear); + } else { + path.setPathSplineType(SplineType.CatmullRom); + } + } + + if (name.equals("tensionUp") && keyPressed) { + path.setCurveTension(path.getCurveTension() + 0.1f); + System.err.println("Tension : " + path.getCurveTension()); + } + if (name.equals("tensionDown") && keyPressed) { + path.setCurveTension(path.getCurveTension() - 0.1f); + System.err.println("Tension : " + path.getCurveTension()); + } + + + } + }; + + inputManager.addListener(acl, "display_hidePath", "play_stop", "SwitchPathInterpolation", "tensionUp", "tensionDown"); + + } +} diff --git a/jme3-examples/src/main/java/jme3test/animation/TestCinematic.java b/jme3-examples/src/main/java/jme3test/animation/TestCinematic.java new file mode 100644 index 000000000..f0200bc91 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/animation/TestCinematic.java @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.animation; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.AnimationFactory; +import com.jme3.animation.LoopMode; +import com.jme3.app.SimpleApplication; +import com.jme3.cinematic.Cinematic; +import com.jme3.cinematic.MotionPath; +import com.jme3.cinematic.PlayState; +import com.jme3.cinematic.events.*; +import com.jme3.font.BitmapText; +import com.jme3.input.ChaseCamera; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.niftygui.NiftyJmeDisplay; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.FadeFilter; +import com.jme3.renderer.Caps; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.CameraNode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.shadow.PssmShadowRenderer; +import de.lessvoid.nifty.Nifty; + +public class TestCinematic extends SimpleApplication { + + private Spatial model; + private Spatial teapot; + private MotionPath path; + private MotionEvent cameraMotionEvent; + private Cinematic cinematic; + private ChaseCamera chaseCam; + private FilterPostProcessor fpp; + private FadeFilter fade; + private float time = 0; + + public static void main(String[] args) { + TestCinematic app = new TestCinematic(); + app.start(); + + + + } + + @Override + public void simpleInitApp() { + //just some text + NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(getAssetManager(), + getInputManager(), + getAudioRenderer(), + getGuiViewPort()); + Nifty nifty; + nifty = niftyDisplay.getNifty(); + nifty.fromXmlWithoutStartScreen("Interface/Nifty/CinematicTest.xml"); + getGuiViewPort().addProcessor(niftyDisplay); + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + final BitmapText text = new BitmapText(guiFont, false); + text.setSize(guiFont.getCharSet().getRenderedSize()); + text.setText("Press enter to play/pause cinematic"); + text.setLocalTranslation((cam.getWidth() - text.getLineWidth()) / 2, cam.getHeight(), 0); + guiNode.attachChild(text); + + + createScene(); + + cinematic = new Cinematic(rootNode, 20); + stateManager.attach(cinematic); + + createCameraMotion(); + + //creating spatial animation for the teapot + AnimationFactory factory = new AnimationFactory(20, "teapotAnim"); + factory.addTimeTranslation(0, new Vector3f(10, 0, 10)); + factory.addTimeTranslation(20, new Vector3f(10, 0, -10)); + factory.addTimeScale(10, new Vector3f(4, 4, 4)); + factory.addTimeScale(20, new Vector3f(1, 1, 1)); + factory.addTimeRotationAngles(20, 0, 4 * FastMath.TWO_PI, 0); + AnimControl control = new AnimControl(); + control.addAnim(factory.buildAnimation()); + teapot.addControl(control); + + //fade in + cinematic.addCinematicEvent(0, new FadeEvent(true)); + // cinematic.activateCamera(0, "aroundCam"); + cinematic.addCinematicEvent(0, new AnimationEvent(teapot, "teapotAnim", LoopMode.DontLoop)); + cinematic.addCinematicEvent(0, cameraMotionEvent); + cinematic.addCinematicEvent(0, new SoundEvent("Sound/Environment/Nature.ogg", LoopMode.Loop)); + cinematic.addCinematicEvent(3f, new SoundEvent("Sound/Effects/kick.wav")); + cinematic.addCinematicEvent(3, new SubtitleTrack(nifty, "start", 3, "jMonkey engine really kicks A...")); + cinematic.addCinematicEvent(5.1f, new SoundEvent("Sound/Effects/Beep.ogg", 1)); + cinematic.addCinematicEvent(2, new AnimationEvent(model, "Walk", LoopMode.Loop)); + cinematic.activateCamera(0, "topView"); + // cinematic.activateCamera(10, "aroundCam"); + + //fade out + cinematic.addCinematicEvent(19, new FadeEvent(false)); +// cinematic.addCinematicEvent(19, new AbstractCinematicEvent() { +// +// @Override +// public void onPlay() { +// fade.setDuration(1f / cinematic.getSpeed()); +// fade.fadeOut(); +// +// } +// +// @Override +// public void onUpdate(float tpf) { +// } +// +// @Override +// public void onStop() { +// } +// +// @Override +// public void onPause() { +// } +// }); + + cinematic.addListener(new CinematicEventListener() { + + public void onPlay(CinematicEvent cinematic) { + chaseCam.setEnabled(false); + System.out.println("play"); + } + + public void onPause(CinematicEvent cinematic) { + System.out.println("pause"); + } + + public void onStop(CinematicEvent cinematic) { + chaseCam.setEnabled(true); + fade.setValue(1); + System.out.println("stop"); + } + }); + + //cinematic.setSpeed(2); + flyCam.setEnabled(false); + chaseCam = new ChaseCamera(cam, model, inputManager); + initInputs(); + + } + + private void createCameraMotion() { + + CameraNode camNode = cinematic.bindCamera("topView", cam); + camNode.setLocalTranslation(new Vector3f(0, 50, 0)); + camNode.lookAt(teapot.getLocalTranslation(), Vector3f.UNIT_Y); + + CameraNode camNode2 = cinematic.bindCamera("aroundCam", cam); + path = new MotionPath(); + path.setCycle(true); + path.addWayPoint(new Vector3f(20, 3, 0)); + path.addWayPoint(new Vector3f(0, 3, 20)); + path.addWayPoint(new Vector3f(-20, 3, 0)); + path.addWayPoint(new Vector3f(0, 3, -20)); + path.setCurveTension(0.83f); + cameraMotionEvent = new MotionEvent(camNode2, path); + cameraMotionEvent.setLoopMode(LoopMode.Loop); + cameraMotionEvent.setLookAt(model.getWorldTranslation(), Vector3f.UNIT_Y); + cameraMotionEvent.setDirectionType(MotionEvent.Direction.LookAt); + + } + + private void createScene() { + + model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + model.center(); + model.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(model); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Cyan); + + teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + teapot.setLocalTranslation(10, 0, 10); + teapot.setMaterial(mat); + teapot.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(teapot); + + Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + matSoil.setBoolean("UseMaterialColors", true); + matSoil.setColor("Ambient", ColorRGBA.Gray); + matSoil.setColor("Diffuse", ColorRGBA.Green); + matSoil.setColor("Specular", ColorRGBA.Black); + + Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -6.0f, 0), 50, 1, 50)); + soil.setMaterial(matSoil); + soil.setShadowMode(ShadowMode.Receive); + rootNode.attachChild(soil); + DirectionalLight light = new DirectionalLight(); + light.setDirection(new Vector3f(0, -1, -1).normalizeLocal()); + light.setColor(ColorRGBA.White.mult(1.5f)); + rootNode.addLight(light); + + fpp = new FilterPostProcessor(assetManager); + fade = new FadeFilter(); + fpp.addFilter(fade); + + if (renderer.getCaps().contains(Caps.GLSL100)) { + PssmShadowRenderer pssm = new PssmShadowRenderer(assetManager, 512, 1); + pssm.setDirection(new Vector3f(0, -1, -1).normalizeLocal()); + pssm.setShadowIntensity(0.4f); + viewPort.addProcessor(pssm); + viewPort.addProcessor(fpp); + } + } + + private void initInputs() { + inputManager.addMapping("togglePause", new KeyTrigger(keyInput.KEY_RETURN)); + inputManager.addMapping("navFwd", new KeyTrigger(keyInput.KEY_RIGHT)); + inputManager.addMapping("navBack", new KeyTrigger(keyInput.KEY_LEFT)); + ActionListener acl = new ActionListener() { + + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("togglePause") && keyPressed) { + if (cinematic.getPlayState() == PlayState.Playing) { + cinematic.pause(); + time = cinematic.getTime(); + } else { + cinematic.play(); + } + } + + if (cinematic.getPlayState() != PlayState.Playing) { + if (name.equals("navFwd") && keyPressed) { + time += 0.25; + FastMath.clamp(time, 0, cinematic.getInitialDuration()); + cinematic.setTime(time); + } + if (name.equals("navBack") && keyPressed) { + time -= 0.25; + FastMath.clamp(time, 0, cinematic.getInitialDuration()); + cinematic.setTime(time); + } + + } + } + }; + inputManager.addListener(acl, "togglePause", "navFwd", "navBack"); + } + + private class FadeEvent extends AbstractCinematicEvent { + + boolean in = true; + float value = 0; + + public FadeEvent(boolean in) { + super(1); + this.in = in; + value = in ? 0 : 1; + } + + @Override + public void onPlay() { + + fade.setDuration(1f / cinematic.getSpeed()); + if (in) { + fade.fadeIn(); + } else { + fade.fadeOut(); + } + fade.setValue(value); + + } + + @Override + public void setTime(float time) { + super.setTime(time); + if (time >= fade.getDuration()) { + value = in ? 1 : 0; + fade.setValue(value); + } else { + value = time; + if (in) { + fade.setValue(time / cinematic.getSpeed()); + } else { + fade.setValue(1 - time / cinematic.getSpeed()); + } + } + } + + @Override + public void onUpdate(float tpf) { + } + + @Override + public void onStop() { + } + + @Override + public void onPause() { + value = fade.getValue(); + fade.pause(); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/animation/TestJaime.java b/jme3-examples/src/main/java/jme3test/animation/TestJaime.java new file mode 100644 index 000000000..330788d08 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/animation/TestJaime.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.animation; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.AnimationFactory; +import com.jme3.animation.LoopMode; +import com.jme3.app.DebugKeysAppState; +import com.jme3.app.FlyCamAppState; +import com.jme3.app.ResetStatsState; +import com.jme3.app.SimpleApplication; +import com.jme3.app.StatsAppState; +import com.jme3.cinematic.Cinematic; +import com.jme3.cinematic.MotionPath; +import com.jme3.cinematic.PlayState; +import com.jme3.cinematic.events.AnimationEvent; +import com.jme3.cinematic.events.MotionEvent; +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.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.FXAAFilter; +import com.jme3.post.ssao.SSAOFilter; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.CameraNode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Quad; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.shadow.SpotLightShadowRenderer; + +/** + * + * @author Nehon + */ +public class TestJaime extends SimpleApplication { + + + Cinematic cinematic; + + public static void main(String... argv){ + TestJaime app = new TestJaime(); + app.start(); + } + + + + @Override + public void simpleInitApp() { + stateManager.detach(stateManager.getState(FlyCamAppState.class)); + stateManager.detach(stateManager.getState(ResetStatsState.class)); + stateManager.detach(stateManager.getState(DebugKeysAppState.class)); + stateManager.detach(stateManager.getState(StatsAppState.class)); + final Node jaime = LoadModel(); + + setupLights(); + setupCamera(); + setupFloor(); + setupCinematic(jaime); + setupInput(); + } + + public Node LoadModel() { + Node jaime = (Node)assetManager.loadModel("Models/Jaime/Jaime.j3o"); + jaime.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + rootNode.attachChild(jaime); + return jaime; + } + + public void setupLights() { + AmbientLight al = new AmbientLight(); + al.setColor(new ColorRGBA(0.1f, 0.1f, 0.1f, 1)); + rootNode.addLight(al); + + SpotLight sl = new SpotLight(); + sl.setColor(ColorRGBA.White.mult(1.0f)); + sl.setPosition(new Vector3f(1.2074411f, 10.6868908f, 4.1489987f)); + sl.setDirection(sl.getPosition().mult(-1)); + sl.setSpotOuterAngle(0.1f); + sl.setSpotInnerAngle(0.004f); + rootNode.addLight(sl); + + //pointlight to fake indirect light coming from the ground + PointLight pl = new PointLight(); + pl.setColor(ColorRGBA.White.mult(1.5f)); + pl.setPosition(new Vector3f(0, 0, 1)); + pl.setRadius(2); + rootNode.addLight(pl); + + SpotLightShadowRenderer shadows = new SpotLightShadowRenderer(assetManager, 1024); + shadows.setLight(sl); + shadows.setShadowIntensity(0.3f); + shadows.setEdgeFilteringMode(EdgeFilteringMode.PCF8); + viewPort.addProcessor(shadows); + + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + SSAOFilter filter = new SSAOFilter(0.10997847f,0.440001f,0.39999998f,-0.008000026f);; + fpp.addFilter(filter); + fpp.addFilter(new FXAAFilter()); + fpp.addFilter(new FXAAFilter()); + + viewPort.addProcessor(fpp); + } + + public void setupCamera() { + flyCam.setEnabled(false); + } + + public void setupCinematic(final Node jaime) { + cinematic = new Cinematic(rootNode, 60); + stateManager.attach(cinematic); + + jaime.move(0, 0, -3); + AnimationFactory af = new AnimationFactory(0.7f, "JumpForward"); + af.addTimeTranslation(0, new Vector3f(0, 0, -3)); + af.addTimeTranslation(0.35f, new Vector3f(0, 1, -1.5f)); + af.addTimeTranslation(0.7f, new Vector3f(0, 0, 0)); + jaime.getControl(AnimControl.class).addAnim(af.buildAnimation()); + + cinematic.enqueueCinematicEvent(new AnimationEvent(jaime, "Idle",3, LoopMode.DontLoop)); + float jumpStart = cinematic.enqueueCinematicEvent(new AnimationEvent(jaime, "JumpStart", LoopMode.DontLoop)); + cinematic.addCinematicEvent(jumpStart+0.2f, new AnimationEvent(jaime, "JumpForward", LoopMode.DontLoop,1)); + cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "JumpEnd", LoopMode.DontLoop)); + cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "Punches", LoopMode.DontLoop)); + cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "SideKick", LoopMode.DontLoop)); + float camStart = cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "Taunt", LoopMode.DontLoop)); + cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "Idle",1, LoopMode.DontLoop)); + cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "Wave", LoopMode.DontLoop)); + cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "Idle", LoopMode.DontLoop)); + + CameraNode camNode = cinematic.bindCamera("cam", cam); + camNode.setLocalTranslation(new Vector3f(1.1f, 1.2f, 2.9f)); + camNode.lookAt(new Vector3f(0, 0.5f, 0), Vector3f.UNIT_Y); + + MotionPath path = new MotionPath(); + path.addWayPoint(new Vector3f(1.1f, 1.2f, 2.9f)); + path.addWayPoint(new Vector3f(0f, 1.2f, 3.0f)); + path.addWayPoint(new Vector3f(-1.1f, 1.2f, 2.9f)); + path.enableDebugShape(assetManager, rootNode); + path.setCurveTension(0.8f); + + MotionEvent camMotion = new MotionEvent(camNode, path,6); + camMotion.setDirectionType(MotionEvent.Direction.LookAt); + camMotion.setLookAt(new Vector3f(0, 0.5f, 0), Vector3f.UNIT_Y); + cinematic.addCinematicEvent(camStart, camMotion); + cinematic.activateCamera(0, "cam"); + + + cinematic.fitDuration(); + cinematic.setSpeed(1.2f); + cinematic.setLoopMode(LoopMode.Loop); + cinematic.play(); + } + + public void setupFloor() { + Quad q = new Quad(20, 20); + q.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(10)); + Geometry geom = new Geometry("floor", q); + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setColor("Diffuse", ColorRGBA.White); + mat.setColor("Specular", ColorRGBA.White); + mat.setColor("Ambient", ColorRGBA.Black); + mat.setBoolean("UseMaterialColors", true); + mat.setFloat("Shininess", 0); + geom.setMaterial(mat); + + geom.rotate(-FastMath.HALF_PI, 0, 0); + geom.center(); + geom.setShadowMode(RenderQueue.ShadowMode.Receive); + rootNode.attachChild(geom); + } + + public void setupInput() { + inputManager.addMapping("start", new KeyTrigger(KeyInput.KEY_PAUSE)); + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if(name.equals("start") && isPressed){ + if(cinematic.getPlayState() != PlayState.Playing){ + cinematic.play(); + }else{ + cinematic.pause(); + } + } + } + }, "start"); + } + + + +} diff --git a/jme3-examples/src/main/java/jme3test/animation/TestMotionPath.java b/jme3-examples/src/main/java/jme3test/animation/TestMotionPath.java new file mode 100644 index 000000000..57584f7c5 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/animation/TestMotionPath.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.animation; + +import com.jme3.animation.LoopMode; +import com.jme3.app.SimpleApplication; +import com.jme3.cinematic.MotionPath; +import com.jme3.cinematic.MotionPathListener; +import com.jme3.cinematic.events.MotionEvent; +import com.jme3.cinematic.events.MotionEvent; +import com.jme3.font.BitmapText; +import com.jme3.input.ChaseCamera; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Spline.SplineType; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; + +public class TestMotionPath extends SimpleApplication { + + private Spatial teapot; + private boolean active = true; + private boolean playing = false; + private MotionPath path; + private MotionEvent 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 MotionEvent(teapot,path); + motionControl.setDirectionType(MotionEvent.Direction.PathAndRotation); + motionControl.setRotation(new Quaternion().fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_Y)); + motionControl.setInitialDuration(10f); + motionControl.setSpeed(2f); + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + final BitmapText wayPointsText = new BitmapText(guiFont, false); + wayPointsText.setSize(guiFont.getCharSet().getRenderedSize()); + + guiNode.attachChild(wayPointsText); + + path.addListener(new MotionPathListener() { + + public void onWayPointReach(MotionEvent 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); +// motionControl.setSpeed(-3f); +// motionControl.setLoopMode(LoopMode.Loop); +// path.setCycle(true); + + + // chaser.setEnabled(false); + chaser.registerWithInput(inputManager); + initInputs(); + + } + + private void createScene() { + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setFloat("Shininess", 1f); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Ambient", ColorRGBA.Black); + mat.setColor("Diffuse", ColorRGBA.DarkGray); + mat.setColor("Specular", ColorRGBA.White.mult(0.6f)); + Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + matSoil.setBoolean("UseMaterialColors", true); + matSoil.setColor("Ambient", ColorRGBA.Black); + matSoil.setColor("Diffuse", ColorRGBA.Black); + matSoil.setColor("Specular", ColorRGBA.Black); + teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + teapot.setName("Teapot"); + teapot.setLocalScale(3); + teapot.setMaterial(mat); + + + rootNode.attachChild(teapot); + Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -1.0f, 0), 50, 1, 50)); + soil.setMaterial(matSoil); + + rootNode.attachChild(soil); + DirectionalLight light = new DirectionalLight(); + light.setDirection(new Vector3f(0, -1, 0).normalizeLocal()); + light.setColor(ColorRGBA.White.mult(1.5f)); + rootNode.addLight(light); + } + + private void initInputs() { + inputManager.addMapping("display_hidePath", new KeyTrigger(KeyInput.KEY_P)); + inputManager.addMapping("SwitchPathInterpolation", new KeyTrigger(KeyInput.KEY_I)); + inputManager.addMapping("tensionUp", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("tensionDown", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addMapping("play_stop", new KeyTrigger(KeyInput.KEY_SPACE)); + ActionListener acl = new ActionListener() { + + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("display_hidePath") && keyPressed) { + if (active) { + active = false; + path.disableDebugShape(); + } else { + active = true; + path.enableDebugShape(assetManager, rootNode); + } + } + if (name.equals("play_stop") && keyPressed) { + if (playing) { + playing = false; + motionControl.stop(); + } else { + playing = true; + motionControl.play(); + } + } + + if (name.equals("SwitchPathInterpolation") && keyPressed) { + if (path.getPathSplineType() == SplineType.CatmullRom){ + path.setPathSplineType(SplineType.Linear); + } else { + path.setPathSplineType(SplineType.CatmullRom); + } + } + + if (name.equals("tensionUp") && keyPressed) { + path.setCurveTension(path.getCurveTension() + 0.1f); + System.err.println("Tension : " + path.getCurveTension()); + } + if (name.equals("tensionDown") && keyPressed) { + path.setCurveTension(path.getCurveTension() - 0.1f); + System.err.println("Tension : " + path.getCurveTension()); + } + + + } + }; + + inputManager.addListener(acl, "display_hidePath", "play_stop", "SwitchPathInterpolation", "tensionUp", "tensionDown"); + + } +} diff --git a/jme3-examples/src/main/java/jme3test/app/TestAppStateLifeCycle.java b/jme3-examples/src/main/java/jme3test/app/TestAppStateLifeCycle.java new file mode 100644 index 000000000..f22424252 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestAppStateLifeCycle.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.app; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; + + +/** + * Tests the app state lifecycles. + * + * @author Paul Speed + */ +public class TestAppStateLifeCycle extends SimpleApplication { + + public static void main(String[] args){ + TestAppStateLifeCycle app = new TestAppStateLifeCycle(); + app.start(); + } + + @Override + public void simpleInitApp() { + Box b = new Box(Vector3f.ZERO, 1, 1, 1); + Geometry geom = new Geometry("Box", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + geom.setMaterial(mat); + rootNode.attachChild(geom); + + System.out.println("Attaching test state."); + stateManager.attach(new TestState()); + } + + @Override + public void simpleUpdate(float tpf) { + + if(stateManager.getState(TestState.class) != null) { + System.out.println("Detaching test state."); + stateManager.detach(stateManager.getState(TestState.class)); + System.out.println("Done"); + } + } + + public class TestState extends AbstractAppState { + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + System.out.println("Initialized"); + } + + @Override + public void stateAttached(AppStateManager stateManager) { + super.stateAttached(stateManager); + System.out.println("Attached"); + } + + @Override + public void update(float tpf) { + super.update(tpf); + System.out.println("update"); + } + + @Override + public void render(RenderManager rm) { + super.render(rm); + System.out.println("render"); + } + + @Override + public void postRender() { + super.postRender(); + System.out.println("postRender"); + } + + @Override + public void stateDetached(AppStateManager stateManager) { + super.stateDetached(stateManager); + System.out.println("Detached"); + } + + @Override + public void cleanup() { + super.cleanup(); + System.out.println("Cleanup"); + } + + } +} diff --git a/jme3-examples/src/main/java/jme3test/app/TestApplication.java b/jme3-examples/src/main/java/jme3test/app/TestApplication.java new file mode 100644 index 000000000..e5306fdc8 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestApplication.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.app; + +import com.jme3.app.Application; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext.Type; + +/** + * Test Application functionality, such as create, restart, destroy, etc. + * @author Kirill + */ +public class TestApplication { + + public static void main(String[] args) throws InterruptedException{ + System.out.println("Creating application.."); + Application app = new Application(); + System.out.println("Starting application in LWJGL mode.."); + app.start(); + System.out.println("Waiting 5 seconds"); + Thread.sleep(5000); + System.out.println("Closing application.."); + app.stop(); + + Thread.sleep(2000); + System.out.println("Starting in fullscreen mode"); + app = new Application(); + AppSettings settings = new AppSettings(true); + settings.setFullscreen(true); + settings.setResolution(-1,-1); // current width/height + app.setSettings(settings); + app.start(); + Thread.sleep(5000); + app.stop(); + + Thread.sleep(2000); + System.out.println("Creating offscreen buffer application"); + app = new Application(); + app.start(Type.OffscreenSurface); + Thread.sleep(3000); + System.out.println("Destroying offscreen buffer"); + app.stop(); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java b/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java new file mode 100644 index 000000000..cf80c9005 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.app; + +import com.jme3.app.Application; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; + +/** + * Test a bare-bones application, without SimpleApplication. + */ +public class TestBareBonesApp extends Application { + + private Geometry boxGeom; + + public static void main(String[] args){ + TestBareBonesApp app = new TestBareBonesApp(); + app.start(); + } + + @Override + public void initialize(){ + super.initialize(); + + System.out.println("Initialize"); + + // create a box + boxGeom = new Geometry("Box", new Box(Vector3f.ZERO, 2, 2, 2)); + + // load some default material + boxGeom.setMaterial(assetManager.loadMaterial("Interface/Logo/Logo.j3m")); + + // attach box to display in primary viewport + viewPort.attachScene(boxGeom); + } + + @Override + public void update(){ + super.update(); + + // do some animation + float tpf = timer.getTimePerFrame(); + boxGeom.rotate(tpf * 2, tpf * 4, tpf * 3); + + // dont forget to update the scenes + boxGeom.updateLogicalState(tpf); + boxGeom.updateGeometricState(); + + // render the viewports + renderManager.render(tpf, context.isRenderable()); + } + + @Override + public void destroy(){ + super.destroy(); + + System.out.println("Destroy"); + } +} diff --git a/jme3-examples/src/main/java/jme3test/app/TestChangeAppIcon.java b/jme3-examples/src/main/java/jme3test/app/TestChangeAppIcon.java new file mode 100644 index 000000000..64993c697 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestChangeAppIcon.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.app; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.system.AppSettings; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.logging.Logger; +import javax.imageio.ImageIO; + +public class TestChangeAppIcon extends SimpleApplication { + + private static final Logger log=Logger.getLogger(TestChangeAppIcon.class.getName()); + + public static void main(String[] args) { + TestChangeAppIcon app = new TestChangeAppIcon(); + AppSettings settings = new AppSettings(true); + + try { + Class 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) + setDisplayStatView(false); + 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/jme3-examples/src/main/java/jme3test/app/TestContextRestart.java b/jme3-examples/src/main/java/jme3test/app/TestContextRestart.java new file mode 100644 index 000000000..b7e6634e1 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestContextRestart.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/app/TestCustomAppSettings.java b/jme3-examples/src/main/java/jme3test/app/TestCustomAppSettings.java new file mode 100644 index 000000000..55ffe0278 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestCustomAppSettings.java @@ -0,0 +1,88 @@ +package jme3test.app; + +import com.jme3.scene.Mesh; +import com.jme3.system.AppSettings; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.prefs.BackingStoreException; + +public class TestCustomAppSettings { + + private static final String APPSETTINGS_KEY = "JME_AppSettingsTest"; + + private static void assertEqual(Object a, Object b) { + if (!a.equals(b)){ + throw new AssertionError(); + } + } + + /** + * Tests preference based AppSettings. + */ + private static void testPreferenceSettings() { + AppSettings settings = new AppSettings(false); + settings.putBoolean("TestBool", true); + settings.putInteger("TestInt", 123); + settings.putString("TestStr", "HelloWorld"); + settings.putFloat("TestFloat", 123.567f); + settings.put("TestObj", new Mesh()); // Objects not supported by preferences + + try { + settings.save(APPSETTINGS_KEY); + } catch (BackingStoreException ex) { + ex.printStackTrace(); + } + + AppSettings loadedSettings = new AppSettings(false); + try { + loadedSettings.load(APPSETTINGS_KEY); + } catch (BackingStoreException ex) { + ex.printStackTrace(); + } + + assertEqual(loadedSettings.getBoolean("TestBool"), true); + assertEqual(loadedSettings.getInteger("TestInt"), 123); + assertEqual(loadedSettings.getString("TestStr"), "HelloWorld"); + assertEqual(loadedSettings.get("TestFloat"), 123.567f); + } + + /** + * Test Java properties file based AppSettings. + */ + private static void testFileSettings() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + AppSettings settings = new AppSettings(false); + settings.putBoolean("TestBool", true); + settings.putInteger("TestInt", 123); + settings.putString("TestStr", "HelloWorld"); + settings.putFloat("TestFloat", 123.567f); + settings.put("TestObj", new Mesh()); // Objects not supported by file settings + + try { + settings.save(baos); + } catch (IOException ex) { + ex.printStackTrace(); + } + + AppSettings loadedSettings = new AppSettings(false); + try { + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + loadedSettings.load(bais); + } catch (IOException ex) { + ex.printStackTrace(); + } + + assertEqual(loadedSettings.getBoolean("TestBool"), true); + assertEqual(loadedSettings.getInteger("TestInt"), 123); + assertEqual(loadedSettings.getString("TestStr"), "HelloWorld"); + assertEqual(loadedSettings.get("TestFloat"), 123.567f); + } + + public static void main(String[] args){ + testPreferenceSettings(); + testFileSettings(); + System.out.println("All OK"); + } +} diff --git a/jme3-examples/src/main/java/jme3test/app/TestIDList.java b/jme3-examples/src/main/java/jme3test/app/TestIDList.java new file mode 100644 index 000000000..37f3f56ea --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestIDList.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.app; + +import com.jme3.renderer.IDList; +import java.util.*; + +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/jme3-examples/src/main/java/jme3test/app/TestReleaseDirectMemory.java b/jme3-examples/src/main/java/jme3test/app/TestReleaseDirectMemory.java new file mode 100644 index 000000000..280212b71 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestReleaseDirectMemory.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.app; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +public class TestReleaseDirectMemory extends SimpleApplication { + + public static void main(String[] args){ + TestReleaseDirectMemory app = new TestReleaseDirectMemory(); + app.start(); + } + + @Override + public void simpleInitApp() { + Box b = new Box(Vector3f.ZERO, 1, 1, 1); + Geometry geom = new Geometry("Box", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + geom.setMaterial(mat); + rootNode.attachChild(geom); + } + + @Override + public void simpleUpdate(float tpf) { + ByteBuffer buf = BufferUtils.createByteBuffer(500000); + BufferUtils.destroyDirectBuffer(buf); + + FloatBuffer buf2 = BufferUtils.createFloatBuffer(500000); + BufferUtils.destroyDirectBuffer(buf2); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/app/TestTempVars.java b/jme3-examples/src/main/java/jme3test/app/TestTempVars.java new file mode 100644 index 000000000..7700b2e96 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestTempVars.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.app; + +import com.jme3.math.Vector3f; +import com.jme3.util.TempVars; + +public class TestTempVars { + + private static final int ITERATIONS = 10000000; + private static final int NANOS_TO_MS = 1000000; + + private static final Vector3f sumCompute = new Vector3f(); + + public static void main(String[] args) { + long milliseconds, nanos; + + for (int i = 0; i < 4; i++){ + System.gc(); + } + +// sumCompute.set(0, 0, 0); +// long nanos = System.nanoTime(); +// for (int i = 0; i < ITERATIONS; i++) { +// recursiveMethod(0); +// } +// long milliseconds = (System.nanoTime() - nanos) / NANOS_TO_MS; +// System.out.println("100 million TempVars calls with 5 recursions: " + milliseconds + " ms"); +// System.out.println(sumCompute); + + sumCompute.set(0, 0, 0); + nanos = System.nanoTime(); + for (int i = 0; i < ITERATIONS; i++) { + methodThatUsesTempVars(); + } + milliseconds = (System.nanoTime() - nanos) / NANOS_TO_MS; + System.out.println("100 million TempVars calls: " + milliseconds + " ms"); + System.out.println(sumCompute); + + sumCompute.set(0, 0, 0); + nanos = System.nanoTime(); + for (int i = 0; i < ITERATIONS; i++) { + methodThatUsesAllocation(); + } + milliseconds = (System.nanoTime() - nanos) / NANOS_TO_MS; + System.out.println("100 million allocation calls: " + milliseconds + " ms"); + System.out.println(sumCompute); + + nanos = System.nanoTime(); + for (int i = 0; i < 10; i++){ + System.gc(); + } + milliseconds = (System.nanoTime() - nanos) / NANOS_TO_MS; + System.out.println("cleanup time after allocation calls: " + milliseconds + " ms"); + } + + public static void methodThatUsesAllocation(){ + Vector3f vector = new Vector3f(); + vector.set(0.1f, 0.2f, 0.3f); + sumCompute.addLocal(vector); + } + + public static void recursiveMethod(int recurse) { + TempVars vars = TempVars.get(); + { + vars.vect1.set(0.1f, 0.2f, 0.3f); + + if (recurse < 4) { + recursiveMethod(recurse + 1); + } + + sumCompute.addLocal(vars.vect1); + } + vars.release(); + } + + public static void methodThatUsesTempVars() { + TempVars vars = TempVars.get(); + { + vars.vect1.set(0.1f, 0.2f, 0.3f); + sumCompute.addLocal(vars.vect1); + } + vars.release(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/app/TestUseAfterFree.java b/jme3-examples/src/main/java/jme3test/app/TestUseAfterFree.java new file mode 100644 index 000000000..a8127469a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestUseAfterFree.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.app; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.texture.Texture; +import com.jme3.util.BufferUtils; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TestUseAfterFree extends SimpleApplication { + + private float time = 0; + private Material mat; + private Texture deletedTex; + + public static void main(String[] args) { + TestUseAfterFree app = new TestUseAfterFree(); + app.start(); + } + + @Override + public void simpleInitApp() { + Box box = new Box(1, 1, 1); + Geometry geom = new Geometry("Box", box); + mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + geom.setMaterial(mat); + rootNode.attachChild(geom); + } + + @Override + public void simpleUpdate(float tpf) { + if (time < 0) { + if (deletedTex != null) { + deletedTex.getImage().resetObject(); + } + return; + } + + time += tpf; + if (time > 5) { + System.out.println("Assiging texture to deleted object!"); + + deletedTex = assetManager.loadTexture("Interface/Logo/Monkey.png"); + BufferUtils.destroyDirectBuffer(deletedTex.getImage().getData(0)); + mat.setTexture("ColorMap", deletedTex); + + time = -1; + } + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/app/state/RootNodeState.java b/jme3-examples/src/main/java/jme3test/app/state/RootNodeState.java new file mode 100644 index 000000000..9274f999e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/state/RootNodeState.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java b/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java new file mode 100644 index 000000000..1ae7a2ada --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.app.state; + +import com.jme3.app.Application; +import com.jme3.niftygui.NiftyJmeDisplay; +import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext; + +public class TestAppStates extends Application { + + public static void main(String[] args){ + TestAppStates app = new TestAppStates(); + app.start(); + } + + @Override + public void start(JmeContext.Type contextType){ + AppSettings settings = new AppSettings(true); + settings.setResolution(1024, 768); + setSettings(settings); + + super.start(contextType); + } + + @Override + public void initialize(){ + super.initialize(); + + System.out.println("Initialize"); + + RootNodeState state = new RootNodeState(); + viewPort.attachScene(state.getRootNode()); + stateManager.attach(state); + + Spatial model = assetManager.loadModel("Models/Teapot/Teapot.obj"); + model.scale(3); + model.setMaterial(assetManager.loadMaterial("Interface/Logo/Logo.j3m")); + state.getRootNode().attachChild(model); + + NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager, + inputManager, + audioRenderer, + guiViewPort); + niftyDisplay.getNifty().fromXml("Interface/Nifty/HelloJme.xml", "start"); + guiViewPort.addProcessor(niftyDisplay); + } + + @Override + public void update(){ + super.update(); + + // do some animation + float tpf = timer.getTimePerFrame(); + + stateManager.update(tpf); + stateManager.render(renderManager); + + // render the viewports + renderManager.render(tpf, context.isRenderable()); + } + + @Override + public void destroy(){ + super.destroy(); + + System.out.println("Destroy"); + } +} diff --git a/jme3-examples/src/main/java/jme3test/asset/TestAbsoluteLocators.java b/jme3-examples/src/main/java/jme3test/asset/TestAbsoluteLocators.java new file mode 100644 index 000000000..306670a6a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/asset/TestAbsoluteLocators.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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, "jpg"); + am.registerLoader(WAVLoader.class, "wav"); + + // register absolute locator + am.registerLocator("/", ClasspathLocator.class); + + // find a sound + AudioData audio = am.loadAudio("Sound/Effects/Gun.wav"); + + // find a texture + Texture tex = am.loadTexture("Textures/Terrain/Pond/Pond.jpg"); + + if (audio == null) + throw new RuntimeException("Cannot find audio!"); + else + System.out.println("Audio loaded from Sounds/Effects/Gun.wav"); + + if (tex == null) + throw new RuntimeException("Cannot find texture!"); + else + System.out.println("Texture loaded from Textures/Terrain/Pond/Pond.jpg"); + + System.out.println("Success!"); + } +} diff --git a/jme3-examples/src/main/java/jme3test/asset/TestAssetCache.java b/jme3-examples/src/main/java/jme3test/asset/TestAssetCache.java new file mode 100644 index 000000000..2f86e9abf --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/asset/TestAssetCache.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.asset; + +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetProcessor; +import com.jme3.asset.CloneableAssetProcessor; +import com.jme3.asset.CloneableSmartAsset; +import com.jme3.asset.cache.AssetCache; +import com.jme3.asset.cache.SimpleAssetCache; +import com.jme3.asset.cache.WeakRefAssetCache; +import com.jme3.asset.cache.WeakRefCloneAssetCache; +import java.util.ArrayList; +import java.util.List; + +public class TestAssetCache { + + /** + * Counter for asset keys + */ + private static int counter = 0; + + /** + * Dummy data is an asset having 10 KB to put a dent in the garbage collector + */ + private static class DummyData implements CloneableSmartAsset { + + private AssetKey key; + private byte[] data = new byte[10 * 1024]; + + @Override + public Object clone(){ + try { + DummyData clone = (DummyData) super.clone(); + clone.data = data.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + public byte[] getData(){ + return data; + } + + public AssetKey getKey() { + return key; + } + + public void setKey(AssetKey key) { + this.key = key; + } + } + + /** + * Dummy key is indexed by a generated ID + */ + private static class DummyKey extends AssetKey implements Cloneable { + + private int id = 0; + + public DummyKey(){ + super("."); + id = counter++; + } + + public DummyKey(int id){ + super("."); + this.id = id; + } + + @Override + public int hashCode(){ + return id; + } + + @Override + public boolean equals(Object other){ + return ((DummyKey)other).id == id; + } + + @Override + public DummyKey clone(){ + return new DummyKey(id); + } + + @Override + public String toString() { + return "ID=" + id; + } + } + + private static void runTest(boolean cloneAssets, boolean smartCache, boolean keepRefs, int limit) { + counter = 0; + List refs = new ArrayList(limit); + + AssetCache cache; + AssetProcessor proc = null; + + if (cloneAssets) { + proc = new CloneableAssetProcessor(); + } + + if (smartCache) { + if (cloneAssets) { + cache = new WeakRefCloneAssetCache(); + } else { + cache = new WeakRefAssetCache(); + } + } else { + cache = new SimpleAssetCache(); + } + + System.gc(); + System.gc(); + System.gc(); + System.gc(); + + long memory = Runtime.getRuntime().freeMemory(); + + while (counter < limit){ + // Create a key + DummyKey key = new DummyKey(); + + // Create some data + DummyData data = new DummyData(); + + // Post process the data before placing it in the cache + if (proc != null){ + data = (DummyData) proc.postProcess(key, data); + } + + if (data.key != null){ + // Keeping a hard reference to the key in the cache + // means the asset will never be collected => bug + throw new AssertionError(); + } + + cache.addToCache(key, data); + + // Get the asset from the cache + AssetKey keyToGet = key.clone(); + + // NOTE: Commented out because getFromCache leaks the original key +// DummyData someLoaded = (DummyData) cache.getFromCache(keyToGet); +// if (someLoaded != data){ +// // Failed to get the same asset from the cache => bug +// // Since a hard reference to the key is kept, +// // it cannot be collected at this point. +// throw new AssertionError(); +// } + + // Clone the asset + if (proc != null){ + // Data is now the clone! + data = (DummyData) proc.createClone(data); + if (smartCache) { + // Registering a clone is only needed + // if smart cache is used. + cache.registerAssetClone(keyToGet, data); + // The clone of the asset must have the same key as the original + // otherwise => bug + if (data.key != key){ + throw new AssertionError(); + } + } + } + + // Keep references to the asset => *should* prevent + // collections of the asset in the cache thus causing + // an out of memory error. + if (keepRefs){ + // Prevent the saved references from taking too much memory .. + if (cloneAssets) { + data.data = null; + } + refs.add(data); + } + + if ((counter % 1000) == 0){ + long newMem = Runtime.getRuntime().freeMemory(); + System.out.println("Allocated objects: " + counter); + System.out.println("Allocated memory: " + ((memory - newMem)/(1024*1024)) + " MB" ); + memory = newMem; + } + } + } + + public static void main(String[] args){ + // Test cloneable smart asset + System.out.println("====== Running Cloneable Smart Asset Test ======"); + runTest(true, true, false, 100000); + + // Test non-cloneable smart asset + System.out.println("====== Running Non-cloneable Smart Asset Test ======"); + runTest(false, true, false, 100000); + } +} diff --git a/jme3-examples/src/main/java/jme3test/asset/TestCustomLoader.java b/jme3-examples/src/main/java/jme3test/asset/TestCustomLoader.java new file mode 100644 index 000000000..81e3a10d3 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/asset/TestCustomLoader.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.asset; + +import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.system.JmeSystem; + +/** + * Demonstrates loading a file from a custom {@link AssetLoader} + */ +public class TestCustomLoader { + public static void main(String[] args){ + AssetManager assetManager = JmeSystem.newAssetManager(); + assetManager.registerLocator("/", ClasspathLocator.class); + assetManager.registerLoader(TextLoader.class, "fnt"); + System.out.println(assetManager.loadAsset("Interface/Fonts/Console.fnt")); + } +} diff --git a/jme3-examples/src/main/java/jme3test/asset/TestManyLocators.java b/jme3-examples/src/main/java/jme3test/asset/TestManyLocators.java new file mode 100644 index 000000000..97cf48f13 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/asset/TestManyLocators.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.asset; + +import com.jme3.asset.*; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.asset.plugins.HttpZipLocator; +import com.jme3.asset.plugins.UrlLocator; +import com.jme3.asset.plugins.ZipLocator; + +public class TestManyLocators { + public static void main(String[] args){ + AssetManager am = new DesktopAssetManager(); + + am.registerLocator("http://www.jmonkeyengine.com/wp-content/uploads/2010/09/", + UrlLocator.class); + + am.registerLocator("town.zip", ZipLocator.class); + am.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip", + HttpZipLocator.class); + + + am.registerLocator("/", ClasspathLocator.class); + + + + // Try loading from Core-Data source package + AssetInfo a = am.locateAsset(new AssetKey("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/jme3-examples/src/main/java/jme3test/asset/TestOnlineJar.java b/jme3-examples/src/main/java/jme3test/asset/TestOnlineJar.java new file mode 100644 index 000000000..e12769cd8 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/asset/TestOnlineJar.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.asset; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.asset.plugins.HttpZipLocator; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture; + +/** + * This tests loading a file from a jar stored online. + * @author Kirill Vainer + */ +public class TestOnlineJar extends SimpleApplication { + + public static void main(String[] args){ + TestOnlineJar app = new TestOnlineJar(); + app.start(); + } + + @Override + public void simpleInitApp() { + // create a simple plane/quad + Quad quadMesh = new Quad(1, 1); + quadMesh.updateGeometry(1, 1, true); + + Geometry quad = new Geometry("Textured Quad", quadMesh); + assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/town.zip", + HttpZipLocator.class); + + TextureKey key = new TextureKey("grass.jpg", false); + key.setGenerateMips(true); + Texture tex = assetManager.loadTexture(key); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", tex); + quad.setMaterial(mat); + + float aspect = tex.getImage().getWidth() / (float) tex.getImage().getHeight(); + quad.setLocalScale(new Vector3f(aspect * 1.5f, 1.5f, 1)); + quad.center(); + + rootNode.attachChild(quad); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/asset/TestUrlLoading.java b/jme3-examples/src/main/java/jme3test/asset/TestUrlLoading.java new file mode 100644 index 000000000..29015a73a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/asset/TestUrlLoading.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.asset; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.asset.plugins.UrlLocator; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture; + +/** + * Load an image and display it from the internet using the UrlLocator. + * @author Kirill Vainer + */ +public class TestUrlLoading extends SimpleApplication { + + public static void main(String[] args){ + TestUrlLoading app = new TestUrlLoading(); + app.start(); + } + + @Override + public void simpleInitApp() { + // create a simple plane/quad + Quad quadMesh = new Quad(1, 1); + quadMesh.updateGeometry(1, 1, true); + + Geometry quad = new Geometry("Textured Quad", quadMesh); + + assetManager.registerLocator("https://jmonkeyengine.googlecode.com/svn/BookSamples/assets/Textures/", + UrlLocator.class); + TextureKey key = new TextureKey("mucha-window.png", 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/jme3-examples/src/main/java/jme3test/asset/TextLoader.java b/jme3-examples/src/main/java/jme3test/asset/TextLoader.java new file mode 100644 index 000000000..27cf33995 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/asset/TextLoader.java @@ -0,0 +1,25 @@ +package jme3test.asset; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import java.io.IOException; +import java.util.Scanner; + +/** + * An example implementation of {@link AssetLoader} to load text + * files as strings. + */ +public class TextLoader implements AssetLoader { + public Object load(AssetInfo assetInfo) throws IOException { + Scanner scan = new Scanner(assetInfo.openStream()); + StringBuilder sb = new StringBuilder(); + try { + while (scan.hasNextLine()) { + sb.append(scan.nextLine()).append('\n'); + } + } finally { + scan.close(); + } + return sb.toString(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/audio/TestAmbient.java b/jme3-examples/src/main/java/jme3test/audio/TestAmbient.java new file mode 100644 index 000000000..cf90cc96a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/audio/TestAmbient.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.audio; + +import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioNode; +import com.jme3.audio.Environment; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; + +public class TestAmbient extends SimpleApplication { + + private AudioNode nature, waves; + + public static void main(String[] args) { + TestAmbient test = new TestAmbient(); + test.start(); + } + + @Override + public void simpleInitApp() { + float[] eax = new float[]{15, 38.0f, 0.300f, -1000, -3300, 0, + 1.49f, 0.54f, 1.00f, -2560, 0.162f, 0.00f, 0.00f, + 0.00f, -229, 0.088f, 0.00f, 0.00f, 0.00f, 0.125f, 1.000f, + 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f}; + Environment env = new Environment(eax); + audioRenderer.setEnvironment(env); + + waves = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", false); + waves.setPositional(true); + waves.setLocalTranslation(new Vector3f(0, 0,0)); + waves.setMaxDistance(100); + waves.setRefDistance(5); + + nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", true); + nature.setPositional(false); + nature.setVolume(3); + + waves.playInstance(); + nature.play(); + + // just a blue box to mark the spot + Box box1 = new Box(Vector3f.ZERO, .5f, .5f, .5f); + Geometry player = new Geometry("Player", box1); + Material mat1 = new Material(assetManager, + "Common/MatDefs/Misc/Unshaded.j3md"); + mat1.setColor("Color", ColorRGBA.Blue); + player.setMaterial(mat1); + rootNode.attachChild(player); + } + + @Override + public void simpleUpdate(float tpf) { + } +} diff --git a/jme3-examples/src/main/java/jme3test/audio/TestDoppler.java b/jme3-examples/src/main/java/jme3test/audio/TestDoppler.java new file mode 100644 index 000000000..60136bcff --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/audio/TestDoppler.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.audio; + +import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioNode; +import com.jme3.audio.Environment; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import org.lwjgl.openal.AL10; +import org.lwjgl.openal.AL11; + +/** + * Test Doppler Effect + */ +public class TestDoppler extends SimpleApplication { + + private AudioNode ufo; + + private float x = 20, z = 0; + private float rate = -0.05f; + private float xDist = 20; + private float zDist = 5; + private float angle = FastMath.TWO_PI; + + public static void main(String[] args){ + TestDoppler test = new TestDoppler(); + test.start(); + } + + @Override + public void simpleInitApp(){ + audioRenderer.setEnvironment(Environment.Dungeon); + AL10.alDistanceModel(AL11.AL_EXPONENT_DISTANCE); + + ufo = new AudioNode(assetManager, "Sound/Effects/Beep.ogg", false); + ufo.setPositional(true); + ufo.setLooping(true); + ufo.setReverbEnabled(true); + ufo.setRefDistance(100000000); + ufo.setMaxDistance(100000000); + ufo.play(); + } + + @Override + public void simpleUpdate(float tpf){ + //float x = (float) (Math.cos(angle) * xDist); + float dx = (float) Math.sin(angle) * xDist; + + //float z = (float) (Math.sin(angle) * zDist); + float dz = (float)(-Math.cos(angle) * zDist); + + x += dx * tpf * 0.05f; + z += dz * tpf * 0.05f; + + angle += tpf * rate; + + if (angle > FastMath.TWO_PI){ + angle = FastMath.TWO_PI; + rate = -rate; + }else if (angle < -0){ + angle = -0; + rate = -rate; + } + + ufo.setVelocity(new Vector3f(dx, 0, dz)); + ufo.setLocalTranslation(x, 0, z); + ufo.updateGeometricState(); + + System.out.println("LOC: " + (int)x +", " + (int)z + + ", VEL: " + (int)dx + ", " + (int)dz); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.form b/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.form new file mode 100644 index 000000000..2834858a0 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.form @@ -0,0 +1,117 @@ + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java b/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java new file mode 100644 index 000000000..50fe7456a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.audio; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.audio.*; +import com.jme3.audio.AudioSource.Status; +import com.jme3.audio.plugins.OGGLoader; +import com.jme3.audio.plugins.WAVLoader; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeSystem; +import java.io.*; +import javax.swing.JFileChooser; + +public class TestMusicPlayer extends javax.swing.JFrame { + + private AudioRenderer ar; + private AudioData musicData; + private AudioNode musicSource; + private float musicLength = 0; + private float curTime = 0; + private Listener listener = new Listener(); + + public TestMusicPlayer() { + initComponents(); + setLocationRelativeTo(null); + initAudioPlayer(); + } + + private void initAudioPlayer(){ + AppSettings settings = new AppSettings(true); + settings.setRenderer(null); // disable rendering + settings.setAudioRenderer("LWJGL"); + ar = JmeSystem.newAudioRenderer(settings); + ar.initialize(); + ar.setListener(listener); + AudioContext.setAudioRenderer(ar); + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //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(); + AssetLoader loader = null; + if(selected.getName().endsWith(".wav")){ + loader = new WAVLoader(); + }else{ + loader = new OGGLoader(); + } + + AudioKey key = new AudioKey(selected.getName(), true, true); + try{ + musicData = (AudioData) loader.load(new AssetInfo(null, key) { + @Override + public InputStream openStream() { + try{ + return new FileInputStream(selected); + }catch (FileNotFoundException ex){ + ex.printStackTrace(); + } + return null; + } + }); + }catch (IOException ex){ + ex.printStackTrace(); + } + + musicSource = new AudioNode(musicData, key); + musicLength = musicData.getDuration(); + updateTime(); + } + }//GEN-LAST:event_btnOpenActionPerformed + + private void updateTime(){ + int max = (int) (musicLength * 100); + int pos = (int) (curTime * 100); + sldBar.setMaximum(max); + sldBar.setValue(pos); + + int minutesTotal = (int) (musicLength / 60); + int secondsTotal = (int) (musicLength % 60); + int minutesNow = (int) (curTime / 60); + int secondsNow = (int) (curTime % 60); + String txt = String.format("%01d:%02d-%01d:%02d", minutesNow, secondsNow, + minutesTotal, secondsTotal); + lblTime.setText(txt); + } + + private void btnPlayActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnPlayActionPerformed + if (musicSource == null){ + btnOpenActionPerformed(evt); + return; + } + + if (musicSource.getStatus() == Status.Playing){ + musicSource.setPitch(1); + ar.pauseSource(musicSource); + }else{ + musicSource.setPitch(1); + musicSource.play(); + } + }//GEN-LAST:event_btnPlayActionPerformed + + private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing + ar.cleanup(); + }//GEN-LAST:event_formWindowClosing + + private void sldVolumeStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sldVolumeStateChanged + listener.setVolume( (float) sldVolume.getValue() / 100f); + ar.setListener(listener); + }//GEN-LAST:event_sldVolumeStateChanged + + private void btnStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnStopActionPerformed + if (musicSource != null){ + musicSource.setPitch(1); + ar.stopSource(musicSource); + } + }//GEN-LAST:event_btnStopActionPerformed + + private void btnFFActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnFFActionPerformed + if (musicSource.getStatus() == Status.Playing){ + musicSource.setPitch(2); + } + }//GEN-LAST:event_btnFFActionPerformed + + private void sldBarStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sldBarStateChanged + if (musicSource != null && !sldBar.getValueIsAdjusting()){ + curTime = sldBar.getValue() / 100f; + if (curTime < 0) + curTime = 0; + + musicSource.setTimeOffset(curTime); +// if (musicSource.getStatus() == Status.Playing){ +// musicSource.stop(); +// musicSource.play(); +// } + updateTime(); + } + }//GEN-LAST:event_sldBarStateChanged + + /** + * @param args the command line arguments + */ + public static void main(String args[]) { + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + new TestMusicPlayer().setVisible(true); + } + }); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton btnFF; + private javax.swing.JButton btnOpen; + private javax.swing.JButton btnPlay; + private javax.swing.JButton btnRewind; + private javax.swing.JButton btnStop; + private javax.swing.JLabel lblTime; + private javax.swing.JPanel pnlBar; + private javax.swing.JPanel pnlButtons; + private javax.swing.JSlider sldBar; + private javax.swing.JSlider sldVolume; + // End of variables declaration//GEN-END:variables + +} diff --git a/jme3-examples/src/main/java/jme3test/audio/TestMusicStreaming.java b/jme3-examples/src/main/java/jme3test/audio/TestMusicStreaming.java new file mode 100644 index 000000000..6d7ceb913 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/audio/TestMusicStreaming.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.audio; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.UrlLocator; +import com.jme3.audio.AudioNode; + +public class TestMusicStreaming extends SimpleApplication { + + public static void main(String[] args){ + TestMusicStreaming test = new TestMusicStreaming(); + test.start(); + } + + @Override + public void simpleInitApp(){ + assetManager.registerLocator("http://www.vorbis.com/music/", UrlLocator.class); + AudioNode audioSource = new AudioNode(assetManager, "Lumme-Badloop.ogg", true); + audioSource.setPositional(false); + audioSource.setReverbEnabled(false); + audioSource.play(); + } + + @Override + public void simpleUpdate(float tpf){} + +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/audio/TestOgg.java b/jme3-examples/src/main/java/jme3test/audio/TestOgg.java new file mode 100644 index 000000000..2d2343c11 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/audio/TestOgg.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.audio; + +import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioNode; +import com.jme3.audio.AudioSource; +import com.jme3.audio.LowPassFilter; + +public class TestOgg extends SimpleApplication { + + private AudioNode audioSource; + + public static void main(String[] args){ + TestOgg test = new TestOgg(); + test.start(); + } + + @Override + public void simpleInitApp(){ + System.out.println("Playing without filter"); + audioSource = new AudioNode(assetManager, "Sound/Effects/Foot steps.ogg", true); + audioSource.play(); + } + + @Override + public void simpleUpdate(float tpf){ + if (audioSource.getStatus() != AudioSource.Status.Playing){ + audioRenderer.deleteAudioData(audioSource.getAudioData()); + + System.out.println("Playing with low pass filter"); + audioSource = new AudioNode(assetManager, "Sound/Effects/Foot steps.ogg", true); + audioSource.setDryFilter(new LowPassFilter(1f, .1f)); + audioSource.setVolume(3); + audioSource.play(); + } + } + +} diff --git a/jme3-examples/src/main/java/jme3test/audio/TestReverb.java b/jme3-examples/src/main/java/jme3test/audio/TestReverb.java new file mode 100644 index 000000000..262f2984d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/audio/TestReverb.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.audio; + +import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioNode; +import com.jme3.audio.Environment; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; + +public class TestReverb extends SimpleApplication { + + private AudioNode audioSource; + private float time = 0; + private float nextTime = 1; + + public static void main(String[] args) { + TestReverb test = new TestReverb(); + test.start(); + } + + @Override + public void simpleInitApp() { + audioSource = new AudioNode(assetManager, "Sound/Effects/Bang.wav"); + + float[] eax = new float[]{15, 38.0f, 0.300f, -1000, -3300, 0, + 1.49f, 0.54f, 1.00f, -2560, 0.162f, 0.00f, 0.00f, 0.00f, + -229, 0.088f, 0.00f, 0.00f, 0.00f, 0.125f, 1.000f, 0.250f, + 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f}; + audioRenderer.setEnvironment(new Environment(eax)); + Environment env = Environment.Cavern; + audioRenderer.setEnvironment(env); + } + + @Override + public void simpleUpdate(float tpf) { + time += tpf; + + if (time > nextTime) { + Vector3f v = new Vector3f(); + v.setX(FastMath.nextRandomFloat()); + v.setY(FastMath.nextRandomFloat()); + v.setZ(FastMath.nextRandomFloat()); + v.multLocal(40, 2, 40); + v.subtractLocal(20, 1, 20); + + audioSource.setLocalTranslation(v); + audioSource.playInstance(); + time = 0; + nextTime = FastMath.nextRandomFloat() * 2 + 0.5f; + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/audio/TestWav.java b/jme3-examples/src/main/java/jme3test/audio/TestWav.java new file mode 100644 index 000000000..2c145ec75 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/audio/TestWav.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.audio; + +import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioNode; + +public class TestWav extends SimpleApplication { + + private float time = 0; + private AudioNode audioSource; + + public static void main(String[] args) { + TestWav test = new TestWav(); + test.start(); + } + + @Override + public void simpleUpdate(float tpf) { + time += tpf; + if (time > 1f) { + audioSource.playInstance(); + time = 0; + } + + } + + @Override + public void simpleInitApp() { + audioSource = new AudioNode(assetManager, "Sound/Effects/Gun.wav", false); + audioSource.setLooping(false); + } +} diff --git a/jme3-examples/src/main/java/jme3test/awt/AppHarness.java b/jme3-examples/src/main/java/jme3test/awt/AppHarness.java new file mode 100644 index 000000000..862f0ab74 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/awt/AppHarness.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/awt/TestApplet.java b/jme3-examples/src/main/java/jme3test/awt/TestApplet.java new file mode 100644 index 000000000..1d5019d8b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/awt/TestApplet.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/awt/TestAwtPanels.java b/jme3-examples/src/main/java/jme3test/awt/TestAwtPanels.java new file mode 100644 index 000000000..2c67867bd --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/awt/TestAwtPanels.java @@ -0,0 +1,88 @@ +package jme3test.awt; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; +import com.jme3.system.awt.AwtPanel; +import com.jme3.system.awt.AwtPanelsContext; +import com.jme3.system.awt.PaintMode; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; + +public class TestAwtPanels extends SimpleApplication { + + private static TestAwtPanels app; + private static AwtPanel panel, panel2; + private static int panelsClosed = 0; + + private static void createWindowForPanel(AwtPanel panel, int location){ + JFrame frame = new JFrame("Render Display " + location); + frame.getContentPane().setLayout(new BorderLayout()); + frame.getContentPane().add(panel, BorderLayout.CENTER); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + if (++panelsClosed == 2){ + app.stop(); + } + } + }); + frame.pack(); + frame.setLocation(location, Toolkit.getDefaultToolkit().getScreenSize().height - 400); + frame.setVisible(true); + } + + public static void main(String[] args){ + Logger.getLogger("com.jme3").setLevel(Level.WARNING); + + app = new TestAwtPanels(); + app.setShowSettings(false); + AppSettings settings = new AppSettings(true); + settings.setCustomRenderer(AwtPanelsContext.class); + settings.setFrameRate(60); + app.setSettings(settings); + app.start(); + + SwingUtilities.invokeLater(new Runnable(){ + public void run(){ + final AwtPanelsContext ctx = (AwtPanelsContext) app.getContext(); + panel = ctx.createPanel(PaintMode.Accelerated); + panel.setPreferredSize(new Dimension(400, 300)); + ctx.setInputSource(panel); + + panel2 = ctx.createPanel(PaintMode.Accelerated); + panel2.setPreferredSize(new Dimension(400, 300)); + + createWindowForPanel(panel, 300); + createWindowForPanel(panel2, 700); + } + }); + } + + @Override + public void simpleInitApp() { + flyCam.setDragToRotate(true); + + Box b = new Box(Vector3f.ZERO, 1, 1, 1); + Geometry geom = new Geometry("Box", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + geom.setMaterial(mat); + rootNode.attachChild(geom); + + panel.attachTo(true, viewPort); + guiViewPort.setClearFlags(true, true, true); + panel2.attachTo(false, guiViewPort); + } +} diff --git a/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java b/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java new file mode 100644 index 000000000..7e7b58a36 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.awt; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeCanvasContext; +import com.jme3.util.JmeFormatter; +import java.awt.BorderLayout; +import java.awt.Canvas; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.concurrent.Callable; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.Logger; +import javax.swing.*; + +public class TestCanvas { + + private static JmeCanvasContext context; + private static Canvas canvas; + private static Application app; + private static JFrame frame; + private static Container canvasPanel1, canvasPanel2; + private static Container currentPanel; + private static JTabbedPane tabbedPane; + private static final String appClass = "jme3test.post.TestRenderToTexture"; + + private static void createTabs(){ + tabbedPane = new JTabbedPane(); + + canvasPanel1 = new JPanel(); + canvasPanel1.setLayout(new BorderLayout()); + tabbedPane.addTab("jME3 Canvas 1", canvasPanel1); + + canvasPanel2 = new JPanel(); + canvasPanel2.setLayout(new BorderLayout()); + tabbedPane.addTab("jME3 Canvas 2", canvasPanel2); + + frame.getContentPane().add(tabbedPane); + + currentPanel = canvasPanel1; + } + + private static void createMenu(){ + JMenuBar menuBar = new JMenuBar(); + frame.setJMenuBar(menuBar); + + JMenu menuTortureMethods = new JMenu("Canvas Torture Methods"); + menuBar.add(menuTortureMethods); + + final JMenuItem itemRemoveCanvas = new JMenuItem("Remove Canvas"); + menuTortureMethods.add(itemRemoveCanvas); + itemRemoveCanvas.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (itemRemoveCanvas.getText().equals("Remove Canvas")){ + currentPanel.remove(canvas); + + itemRemoveCanvas.setText("Add Canvas"); + }else if (itemRemoveCanvas.getText().equals("Add Canvas")){ + currentPanel.add(canvas, BorderLayout.CENTER); + + itemRemoveCanvas.setText("Remove Canvas"); + } + } + }); + + final JMenuItem itemHideCanvas = new JMenuItem("Hide Canvas"); + menuTortureMethods.add(itemHideCanvas); + itemHideCanvas.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (itemHideCanvas.getText().equals("Hide Canvas")){ + canvas.setVisible(false); + itemHideCanvas.setText("Show Canvas"); + }else if (itemHideCanvas.getText().equals("Show Canvas")){ + canvas.setVisible(true); + itemHideCanvas.setText("Hide Canvas"); + } + } + }); + + final JMenuItem itemSwitchTab = new JMenuItem("Switch to tab #2"); + menuTortureMethods.add(itemSwitchTab); + itemSwitchTab.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e){ + if (itemSwitchTab.getText().equals("Switch to tab #2")){ + canvasPanel1.remove(canvas); + canvasPanel2.add(canvas, BorderLayout.CENTER); + currentPanel = canvasPanel2; + itemSwitchTab.setText("Switch to tab #1"); + }else if (itemSwitchTab.getText().equals("Switch to tab #1")){ + canvasPanel2.remove(canvas); + canvasPanel1.add(canvas, BorderLayout.CENTER); + currentPanel = canvasPanel1; + itemSwitchTab.setText("Switch to tab #2"); + } + } + }); + + JMenuItem itemSwitchLaf = new JMenuItem("Switch Look and Feel"); + menuTortureMethods.add(itemSwitchLaf); + itemSwitchLaf.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e){ + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Throwable t){ + t.printStackTrace(); + } + SwingUtilities.updateComponentTreeUI(frame); + frame.pack(); + } + }); + + JMenuItem itemSmallSize = new JMenuItem("Set size to (0, 0)"); + menuTortureMethods.add(itemSmallSize); + itemSmallSize.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent e){ + Dimension preferred = frame.getPreferredSize(); + frame.setPreferredSize(new Dimension(0, 0)); + frame.pack(); + frame.setPreferredSize(preferred); + } + }); + + JMenuItem itemKillCanvas = new JMenuItem("Stop/Start Canvas"); + menuTortureMethods.add(itemKillCanvas); + itemKillCanvas.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + currentPanel.remove(canvas); + app.stop(true); + + createCanvas(appClass); + currentPanel.add(canvas, BorderLayout.CENTER); + frame.pack(); + startApp(); + } + }); + + JMenuItem itemExit = new JMenuItem("Exit"); + menuTortureMethods.add(itemExit); + itemExit.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ae) { + frame.dispose(); + app.stop(); + } + }); + } + + private static void createFrame(){ + frame = new JFrame("Test"); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.addWindowListener(new WindowAdapter(){ + @Override + public void windowClosed(WindowEvent e) { + app.stop(); + } + }); + + createTabs(); + createMenu(); + } + + public static void createCanvas(String appClass){ + AppSettings settings = new AppSettings(true); + settings.setWidth(640); + settings.setHeight(480); + + try{ + Class 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(); + app.startCanvas(); + + context = (JmeCanvasContext) app.getContext(); + canvas = context.getCanvas(); + canvas.setSize(settings.getWidth(), settings.getHeight()); + } + + public static void startApp(){ + app.startCanvas(); + app.enqueue(new Callable(){ + public Void call(){ + if (app instanceof SimpleApplication){ + SimpleApplication simpleApp = (SimpleApplication) app; + simpleApp.getFlyByCamera().setDragToRotate(true); + } + return null; + } + }); + + } + + public static void main(String[] args){ + JmeFormatter formatter = new JmeFormatter(); + + Handler consoleHandler = new ConsoleHandler(); + consoleHandler.setFormatter(formatter); + + Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]); + Logger.getLogger("").addHandler(consoleHandler); + + createCanvas(appClass); + + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + } + + SwingUtilities.invokeLater(new Runnable(){ + public void run(){ + JPopupMenu.setDefaultLightWeightPopupEnabled(false); + + createFrame(); + + currentPanel.add(canvas, BorderLayout.CENTER); + frame.pack(); + startApp(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } + }); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/awt/TestSafeCanvas.java b/jme3-examples/src/main/java/jme3test/awt/TestSafeCanvas.java new file mode 100644 index 000000000..88451f5d7 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/awt/TestSafeCanvas.java @@ -0,0 +1,69 @@ +package jme3test.awt; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeCanvasContext; +import java.awt.Canvas; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import javax.swing.JFrame; + +public class TestSafeCanvas extends SimpleApplication { + + public static void main(String[] args) throws InterruptedException{ + AppSettings settings = new AppSettings(true); + settings.setWidth(640); + settings.setHeight(480); + + final TestSafeCanvas app = new TestSafeCanvas(); + app.setPauseOnLostFocus(false); + app.setSettings(settings); + app.createCanvas(); + app.startCanvas(true); + + JmeCanvasContext context = (JmeCanvasContext) app.getContext(); + Canvas canvas = context.getCanvas(); + canvas.setSize(settings.getWidth(), settings.getHeight()); + + + + Thread.sleep(3000); + + JFrame frame = new JFrame("Test"); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + app.stop(); + } + }); + frame.getContentPane().add(canvas); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + + Thread.sleep(3000); + + frame.getContentPane().remove(canvas); + + Thread.sleep(3000); + + frame.getContentPane().add(canvas); + } + + @Override + public void simpleInitApp() { + flyCam.setDragToRotate(true); + + Box b = new Box(Vector3f.ZERO, 1, 1, 1); + Geometry geom = new Geometry("Box", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + geom.setMaterial(mat); + rootNode.attachChild(geom); + } +} diff --git a/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java b/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java new file mode 100644 index 000000000..af0292864 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.batching; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.BatchNode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.WireFrustum; +import com.jme3.scene.shape.Box; +import com.jme3.system.NanoTimer; +import com.jme3.util.TangentBinormalGenerator; + +/** + * + * @author Nehon + */ +public class TestBatchNode extends SimpleApplication { + + public static void main(String[] args) { + + TestBatchNode app = new TestBatchNode(); + app.start(); + } + BatchNode batch; + WireFrustum frustum; + Geometry frustumMdl; + private Vector3f[] points; + + { + points = new Vector3f[8]; + for (int i = 0; i < points.length; i++) { + points[i] = new Vector3f(); + } + } + + @Override + public void simpleInitApp() { + timer = new NanoTimer(); + batch = new BatchNode("theBatchNode"); + + + + /** + * A cube with a color "bleeding" through transparent texture. Uses + * Texture from jme3-test-data library! + */ + Box boxshape4 = new Box(Vector3f.ZERO, 1f, 1f, 1f); + cube = new Geometry("cube1", boxshape4); + Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); + cube.setMaterial(mat); +// Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); +// mat.setColor("Diffuse", ColorRGBA.Blue); +// mat.setBoolean("UseMaterialColors", true); + /** + * A cube with a color "bleeding" through transparent texture. Uses + * Texture from jme3-test-data library! + */ + Box box = new Box(Vector3f.ZERO, 1f, 1f, 1f); + cube2 = new Geometry("cube2", box); + cube2.setMaterial(mat); + + TangentBinormalGenerator.generate(cube); + TangentBinormalGenerator.generate(cube2); + + + n = new Node("aNode"); + // n.attachChild(cube2); + batch.attachChild(cube); + // batch.attachChild(cube2); + // batch.setMaterial(mat); + batch.batch(); + rootNode.attachChild(batch); + cube.setLocalTranslation(3, 0, 0); + cube2.setLocalTranslation(0, 20, 0); + + + updateBoindPoints(points); + frustum = new WireFrustum(points); + frustumMdl = new Geometry("f", frustum); + frustumMdl.setCullHint(Spatial.CullHint.Never); + frustumMdl.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")); + frustumMdl.getMaterial().getAdditionalRenderState().setWireframe(true); + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red); + rootNode.attachChild(frustumMdl); + dl = new DirectionalLight(); + dl.setColor(ColorRGBA.White.mult(2)); + dl.setDirection(new Vector3f(1, -1, -1)); + rootNode.addLight(dl); + flyCam.setMoveSpeed(10); + } + Node n; + Geometry cube; + Geometry cube2; + float time = 0; + DirectionalLight dl; + boolean done = false; + + @Override + public void simpleUpdate(float tpf) { + if (!done) { + done = true; + batch.attachChild(cube2); + batch.batch(); + } + updateBoindPoints(points); + frustum.update(points); + time += tpf; + dl.setDirection(cam.getDirection()); + cube2.setLocalTranslation(FastMath.sin(-time) * 3, FastMath.cos(time) * 3, 0); + cube2.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Z)); + cube2.setLocalScale(Math.max(FastMath.sin(time), 0.5f)); + +// batch.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Z)); + + } +// + + public void updateBoindPoints(Vector3f[] points) { + BoundingBox bb = (BoundingBox) batch.getWorldBound(); + float xe = bb.getXExtent(); + float ye = bb.getYExtent(); + float ze = bb.getZExtent(); + float x = bb.getCenter().x; + float y = bb.getCenter().y; + float z = bb.getCenter().z; + + points[0].set(new Vector3f(x - xe, y - ye, z - ze)); + points[1].set(new Vector3f(x - xe, y + ye, z - ze)); + points[2].set(new Vector3f(x + xe, y + ye, z - ze)); + points[3].set(new Vector3f(x + xe, y - ye, z - ze)); + + points[4].set(new Vector3f(x + xe, y - ye, z + ze)); + points[5].set(new Vector3f(x - xe, y - ye, z + ze)); + points[6].set(new Vector3f(x - xe, y + ye, z + ze)); + points[7].set(new Vector3f(x + xe, y + ye, z + ze)); + } +} diff --git a/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java b/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java new file mode 100644 index 000000000..4559acfdc --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.batching; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.scene.*; +import com.jme3.scene.debug.Arrow; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; +import com.jme3.system.NanoTimer; +import java.util.ArrayList; +import java.util.Random; + +public class TestBatchNodeCluster extends SimpleApplication { + + public static void main(String[] args) { + TestBatchNodeCluster app = new TestBatchNodeCluster(); + settingst = new AppSettings(true); + //settingst.setFrameRate(75); + settingst.setResolution(640, 480); + settingst.setVSync(false); + settingst.setFullscreen(false); + app.setSettings(settingst); + app.setShowSettings(false); + app.start(); + } + private ActionListener al = new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("Start Game")) { +// randomGenerator(); + } + } + }; + protected Random rand = new Random(); + protected int maxCubes = 2000; + protected int startAt = 0; + protected static int xPositions = 0, yPositions = 0, zPositions = 0; + protected int returner = 0; + protected ArrayList xPosition = new ArrayList(); + protected ArrayList yPosition = new ArrayList(); + protected ArrayList zPosition = new ArrayList(); + protected int xLimitf = 60, xLimits = -60, yLimitf = 60, yLimits = -20, zLimitf = 60, zLimits = -60; + protected int circ = 8;//increases by 8 every time. + protected int dynamic = 4; + protected static AppSettings settingst; + protected boolean isTrue = true; + private int lineLength = 50; + protected BatchNode batchNode; + Material mat1; + Material mat2; + Material mat3; + Material mat4; + Node terrain; + //protected +// protected Geometry player; + + @Override + public void simpleInitApp() { + timer = new NanoTimer(); + + batchNode = new SimpleBatchNode("BatchNode"); + + + xPosition.add(0); + yPosition.add(0); + zPosition.add(0); + + mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat1.setColor("Color", ColorRGBA.White); + mat1.setColor("GlowColor", ColorRGBA.Blue.mult(10)); + + mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat2.setColor("Color", ColorRGBA.White); + mat2.setColor("GlowColor", ColorRGBA.Red.mult(10)); + + mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat3.setColor("Color", ColorRGBA.White); + mat3.setColor("GlowColor", ColorRGBA.Yellow.mult(10)); + + mat4 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat4.setColor("Color", ColorRGBA.White); + mat4.setColor("GlowColor", ColorRGBA.Orange.mult(10)); + + randomGenerator(); + + //rootNode.attachChild(SkyFactory.createSky( + // assetManager, "Textures/SKY02.zip", false)); + inputManager.addMapping("Start Game", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addListener(al, new String[]{"Start Game"}); + + + cam.setLocation(new Vector3f(-34.403286f, 126.65158f, 434.791f)); + cam.setRotation(new Quaternion(0.022630932f, 0.9749435f, -0.18736298f, 0.11776358f)); + + + batchNode.batch(); + + + terrain = new Node("terrain"); + terrain.setLocalTranslation(50, 0, 50); + terrain.attachChild(batchNode); + + flyCam.setMoveSpeed(100); + rootNode.attachChild(terrain); + Vector3f pos = new Vector3f(-40, 0, -40); + batchNode.setLocalTranslation(pos); + + + Arrow a = new Arrow(new Vector3f(0, 50, 0)); + Geometry g = new Geometry("a", a); + g.setLocalTranslation(terrain.getLocalTranslation()); + Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + m.setColor("Color", ColorRGBA.Blue); + g.setMaterial(m); + + + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + fpp.addFilter(new BloomFilter(BloomFilter.GlowMode.Objects)); +// SSAOFilter ssao = new SSAOFilter(8.630104f,22.970434f,2.9299977f,0.2999997f); +// fpp.addFilter(ssao); + viewPort.addProcessor(fpp); + // viewPort.setBackgroundColor(ColorRGBA.DarkGray); + } + + public void randomGenerator() { + for (int i = startAt; i < maxCubes - 1; i++) { + randomize(); + Geometry box = new Geometry("Box" + i, new Box(Vector3f.ZERO, 1, 1, 1)); + box.setLocalTranslation(new Vector3f(xPosition.get(xPosition.size() - 1), + yPosition.get(yPosition.size() - 1), + zPosition.get(zPosition.size() - 1))); + batchNode.attachChild(box); + if (i < 500) { + box.setMaterial(mat1); + } else if (i < 1000) { + + box.setMaterial(mat2); + } else if (i < 1500) { + + box.setMaterial(mat3); + } else { + + box.setMaterial(mat4); + } + + } + } + +// public BatchNode randomBatch() { +// +// int randomn = rand.nextInt(4); +// if (randomn == 0) { +// return blue; +// } else if (randomn == 1) { +// return brown; +// } else if (randomn == 2) { +// return pink; +// } else if (randomn == 3) { +// return orange; +// } +// return null; +// } + public ColorRGBA randomColor() { + ColorRGBA color = ColorRGBA.Black; + int randomn = rand.nextInt(4); + if (randomn == 0) { + color = ColorRGBA.Orange; + } else if (randomn == 1) { + color = ColorRGBA.Blue; + } else if (randomn == 2) { + color = ColorRGBA.Brown; + } else if (randomn == 3) { + color = ColorRGBA.Magenta; + } + return color; + } + + public void randomize() { + int xpos = xPosition.get(xPosition.size() - 1); + int ypos = yPosition.get(yPosition.size() - 1); + int zpos = zPosition.get(zPosition.size() - 1); + int x = 0; + int y = 0; + int z = 0; + boolean unTrue = true; + while (unTrue) { + unTrue = false; + boolean xChanged = false; + x = 0; + y = 0; + z = 0; + if (xpos >= lineLength * 2) { + x = 2; + xChanged = true; + } else { + x = xPosition.get(xPosition.size() - 1) + 2; + } + if (xChanged) { + //y = yPosition.get(yPosition.size() - lineLength) + 2; + } else { + y = rand.nextInt(3); + if (yPosition.size() > lineLength) { + if (yPosition.size() > 51) { + if (y == 0 && ypos < yLimitf && getym(lineLength) > ypos - 2) { + y = ypos + 2; + } else if (y == 1 && ypos > yLimits && getym(lineLength) < ypos + 2) { + y = ypos - 2; + } else if (y == 2 && getym(lineLength) > ypos - 2 && getym(lineLength) < ypos + 2) { + y = ypos; + } else { + if (ypos >= yLimitf) { + y = ypos - 2; + } else if (ypos <= yLimits) { + y = ypos + 2; + } else if (y == 0 && getym(lineLength) >= ypos - 4) { + y = ypos - 2; + } else if (y == 0 && getym(lineLength) >= ypos - 2) { + y = ypos; + } else if (y == 1 && getym(lineLength) >= ypos + 4) { + y = ypos + 2; + } else if (y == 1 && getym(lineLength) >= ypos + 2) { + y = ypos; + } else if (y == 2 && getym(lineLength) <= ypos - 2) { + y = ypos - 2; + } else if (y == 2 && getym(lineLength) >= ypos + 2) { + y = ypos + 2; + } else { + System.out.println("wtf"); + } + } + } else if (yPosition.size() == lineLength) { + if (y == 0 && ypos < yLimitf) { + y = getym(lineLength) + 2; + } else if (y == 1 && ypos > yLimits) { + y = getym(lineLength) - 2; + } + } + } else { + if (y == 0 && ypos < yLimitf) { + y = ypos + 2; + } else if (y == 1 && ypos > yLimits) { + y = ypos - 2; + } else if (y == 2) { + y = ypos; + } else if (y == 0 && ypos >= yLimitf) { + y = ypos - 2; + } else if (y == 1 && ypos <= yLimits) { + y = ypos + 2; + } + } + } + if (xChanged) { + z = zpos + 2; + } else { + z = zpos; + } +// for (int i = 0; i < xPosition.size(); i++) +// { +// if (x - xPosition.get(i) <= 1 && x - xPosition.get(i) >= -1 && +// y - yPosition.get(i) <= 1 && y - yPosition.get(i) >= -1 +// &&z - zPosition.get(i) <= 1 && z - zPosition.get(i) >= +// -1) +// { +// unTrue = true; +// } +// } + } + xPosition.add(x); + yPosition.add(y); + zPosition.add(z); + } + + public int getxm(int i) { + return xPosition.get(xPosition.size() - i); + } + + public int getym(int i) { + return yPosition.get(yPosition.size() - i); + } + + public int getzm(int i) { + return zPosition.get(zPosition.size() - i); + } + + public int getx(int i) { + return xPosition.get(i); + } + + public int gety(int i) { + return yPosition.get(i); + } + + public int getz(int i) { + return zPosition.get(i); + } + long nbFrames = 0; + long cullTime = 0; + float time = 0; + Vector3f lookAtPos = new Vector3f(0, 0, 0); + float xpos = 0; + Spatial box; + + @Override + public void simpleUpdate(float tpf) { + time += tpf; + int random = rand.nextInt(2000); + float mult1 = 1.0f; + float mult2 = 1.0f; + if (random < 500) { + mult1 = 1.0f; + mult2 = 1.0f; + } else if (random < 1000) { + mult1 = -1.0f; + mult2 = 1.0f; + } else if (random < 1500) { + mult1 = 1.0f; + mult2 = -1.0f; + } else if (random <= 2000) { + mult1 = -1.0f; + mult2 = -1.0f; + } + box = batchNode.getChild("Box" + random); + if (box != null) { + Vector3f v = box.getLocalTranslation(); + box.setLocalTranslation(v.x + FastMath.sin(time * mult1) * 20, v.y + (FastMath.sin(time * mult1) * FastMath.cos(time * mult1) * 20), v.z + FastMath.cos(time * mult2) * 20); + } + terrain.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Y)); + + + } +} diff --git a/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeTower.java b/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeTower.java new file mode 100644 index 000000000..0147c4435 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeTower.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.batching; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.font.BitmapText; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.BatchNode; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; +import com.jme3.shadow.CompareMode; +import com.jme3.shadow.DirectionalLightShadowFilter; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import com.jme3.system.NanoTimer; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import jme3test.bullet.BombControl; + +/** + * + * @author double1984 (tower mod by atom) + */ +public class TestBatchNodeTower extends SimpleApplication { + + int bricksPerLayer = 8; + int brickLayers = 30; + + static float brickWidth = .75f, brickHeight = .25f, brickDepth = .25f; + float radius = 3f; + float angle = 0; + + + Material mat; + Material mat2; + Material mat3; + DirectionalLightShadowFilter shadowRenderer; + private Sphere bullet; + private Box brick; + private SphereCollisionShape bulletCollisionShape; + + private BulletAppState bulletAppState; + BatchNode batchNode = new BatchNode("batch Node"); + + public static void main(String args[]) { + TestBatchNodeTower f = new TestBatchNodeTower(); + AppSettings s = new AppSettings(true); + f.setSettings(s); + f.start(); + } + + @Override + public void simpleInitApp() { + timer = new NanoTimer(); + bulletAppState = new BulletAppState(); + bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); + // bulletAppState.setEnabled(false); + stateManager.attach(bulletAppState); + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(0.4f); + + brick = new Box(brickWidth, brickHeight, brickDepth); + brick.scaleTextureCoordinates(new Vector2f(1f, .5f)); + //bulletAppState.getPhysicsSpace().enableDebug(assetManager); + initMaterial(); + initTower(); + initFloor(); + initCrossHairs(); + this.cam.setLocation(new Vector3f(0, 25f, 8f)); + cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0)); + cam.setFrustumFar(80); + inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(actionListener, "shoot"); + rootNode.setShadowMode(ShadowMode.Off); + + batchNode.batch(); + batchNode.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(batchNode); + + + shadowRenderer = new DirectionalLightShadowFilter(assetManager, 1024, 2); + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + shadowRenderer.setLight(dl); + shadowRenderer.setLambda(0.55f); + shadowRenderer.setShadowIntensity(0.6f); + shadowRenderer.setShadowCompareMode(CompareMode.Hardware); + shadowRenderer.setEdgeFilteringMode(EdgeFilteringMode.PCF4); + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + fpp.addFilter(shadowRenderer); + viewPort.addProcessor(fpp); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + private ActionListener actionListener = new ActionListener() { + + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("shoot") && !keyPressed) { + Geometry bulletg = new Geometry("bullet", bullet); + bulletg.setMaterial(mat2); + bulletg.setShadowMode(ShadowMode.CastAndReceive); + bulletg.setLocalTranslation(cam.getLocation()); + RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1); +// RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1); + bulletNode.setLinearVelocity(cam.getDirection().mult(25)); + bulletg.addControl(bulletNode); + rootNode.attachChild(bulletg); + getPhysicsSpace().add(bulletNode); + } + } + }; + + public void initTower() { + double tempX = 0; + double tempY = 0; + double tempZ = 0; + angle = 0f; + for (int i = 0; i < brickLayers; i++){ + // Increment rows + if (i != 0) { + tempY += brickHeight * 2; + } else { + tempY = brickHeight; + } + // Alternate brick seams + angle = 360.0f / bricksPerLayer * i/2f; + for (int j = 0; j < bricksPerLayer; j++){ + tempZ = Math.cos(Math.toRadians(angle))*radius; + tempX = Math.sin(Math.toRadians(angle))*radius; + System.out.println("x="+((float)(tempX))+" y="+((float)(tempY))+" z="+(float)(tempZ)); + Vector3f vt = new Vector3f((float)(tempX), (float)(tempY), (float)(tempZ)); + // Add crenelation + if (i==brickLayers-1){ + if (j%2 == 0){ + addBrick(vt); + } + } + // Create main tower + else { + addBrick(vt); + } + angle += 360.0/bricksPerLayer; + } + } + + } + + public void initFloor() { + Box floorBox = new Box(Vector3f.ZERO, 10f, 0.1f, 5f); + floorBox.scaleTextureCoordinates(new Vector2f(3, 6)); + + Geometry floor = new Geometry("floor", floorBox); + floor.setMaterial(mat3); + floor.setShadowMode(ShadowMode.Receive); + floor.setLocalTranslation(0, 0, 0); + floor.addControl(new RigidBodyControl(0)); + this.rootNode.attachChild(floor); + this.getPhysicsSpace().add(floor); + } + + public void initMaterial() { + mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg"); + key.setGenerateMips(true); + Texture tex = assetManager.loadTexture(key); + mat.setTexture("ColorMap", tex); + + mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG"); + key2.setGenerateMips(true); + Texture tex2 = assetManager.loadTexture(key2); + mat2.setTexture("ColorMap", tex2); + + mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg"); + key3.setGenerateMips(true); + Texture tex3 = assetManager.loadTexture(key3); + tex3.setWrap(WrapMode.Repeat); + mat3.setTexture("ColorMap", tex3); + } +int nbBrick =0; + public void addBrick(Vector3f ori) { + Geometry reBoxg = new Geometry("brick", brick); + reBoxg.setMaterial(mat); + reBoxg.setLocalTranslation(ori); + reBoxg.rotate(0f, (float)Math.toRadians(angle) , 0f ); + reBoxg.addControl(new RigidBodyControl(1.5f)); + reBoxg.setShadowMode(ShadowMode.CastAndReceive); + reBoxg.getControl(RigidBodyControl.class).setFriction(1.6f); + this.batchNode.attachChild(reBoxg); + this.getPhysicsSpace().add(reBoxg); + nbBrick++; + } + + protected void initCrossHairs() { + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText ch = new BitmapText(guiFont, false); + ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); + ch.setText("+"); // crosshairs + ch.setLocalTranslation( // center + settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2, + settings.getHeight() / 2 + ch.getLineHeight() / 2, 0); + guiNode.attachChild(ch); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/blender/TestBlenderLoader.java b/jme3-examples/src/main/java/jme3test/blender/TestBlenderLoader.java new file mode 100644 index 000000000..0ff740bfb --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/blender/TestBlenderLoader.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.blender; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; + +public class TestBlenderLoader extends SimpleApplication { + + public static void main(String[] args){ + TestBlenderLoader app = new TestBlenderLoader(); + app.start(); + } + + @Override + public void simpleInitApp() { + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + + //load model with packed images + Spatial ogre = assetManager.loadModel("Blender/2.4x/Sinbad.blend"); + rootNode.attachChild(ogre); + + //load model with referenced images + Spatial track = assetManager.loadModel("Blender/2.4x/MountainValley_Track.blend"); + rootNode.attachChild(track); + + // sunset light + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f,-0.7f,1).normalizeLocal()); + dl.setColor(new ColorRGBA(0.44f, 0.30f, 0.20f, 1.0f)); + rootNode.addLight(dl); + + // skylight + dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.6f,-1,-0.6f).normalizeLocal()); + dl.setColor(new ColorRGBA(0.10f, 0.22f, 0.44f, 1.0f)); + rootNode.addLight(dl); + + // white ambient light + dl = new DirectionalLight(); + dl.setDirection(new Vector3f(1, -0.5f,-0.1f).normalizeLocal()); + dl.setColor(new ColorRGBA(0.80f, 0.70f, 0.80f, 1.0f)); + rootNode.addLight(dl); + } + + @Override + public void simpleUpdate(float tpf){ + } + +} diff --git a/jme3-examples/src/main/java/jme3test/bounding/TestRayCollision.java b/jme3-examples/src/main/java/jme3test/bounding/TestRayCollision.java new file mode 100644 index 000000000..839628407 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bounding/TestRayCollision.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/bullet/BombControl.java b/jme3-examples/src/main/java/jme3test/bullet/BombControl.java new file mode 100644 index 000000000..c02e5a441 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/BombControl.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.asset.AssetManager; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.PhysicsCollisionEvent; +import com.jme3.bullet.collision.PhysicsCollisionListener; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsGhostObject; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.effect.shapes.EmitterSphereShape; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.Iterator; + +/** + * + * @author normenhansen + */ +public class BombControl extends RigidBodyControl implements PhysicsCollisionListener, PhysicsTickListener { + + private float explosionRadius = 10; + private PhysicsGhostObject ghostObject; + private Vector3f vector = new Vector3f(); + private Vector3f vector2 = new Vector3f(); + private float forceFactor = 1; + private ParticleEmitter effect; + private float fxTime = 0.5f; + private float maxTime = 4f; + private float curTime = -1.0f; + private float timer; + + public BombControl(CollisionShape shape, float mass) { + super(shape, mass); + createGhostObject(); + } + + public BombControl(AssetManager manager, CollisionShape shape, float mass) { + super(shape, mass); + createGhostObject(); + prepareEffect(manager); + } + + public void setPhysicsSpace(PhysicsSpace space) { + super.setPhysicsSpace(space); + if (space != null) { + space.addCollisionListener(this); + } + } + + private void prepareEffect(AssetManager assetManager) { + int COUNT_FACTOR = 1; + float COUNT_FACTOR_F = 1f; + effect = new ParticleEmitter("Flame", Type.Triangle, 32 * COUNT_FACTOR); + effect.setSelectRandomImage(true); + effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (float) (1f / COUNT_FACTOR_F))); + effect.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f)); + effect.setStartSize(1.3f); + effect.setEndSize(2f); + effect.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); + effect.setParticlesPerSec(0); + effect.setGravity(0, -5f, 0); + effect.setLowLife(.4f); + effect.setHighLife(.5f); + effect.setInitialVelocity(new Vector3f(0, 7, 0)); + effect.setVelocityVariation(1f); + effect.setImagesX(2); + effect.setImagesY(2); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png")); + effect.setMaterial(mat); + } + + protected void createGhostObject() { + ghostObject = new PhysicsGhostObject(new SphereCollisionShape(explosionRadius)); + } + + public void collision(PhysicsCollisionEvent event) { + if (space == null) { + return; + } + if (event.getObjectA() == this || event.getObjectB() == this) { + space.add(ghostObject); + ghostObject.setPhysicsLocation(getPhysicsLocation(vector)); + space.addTickListener(this); + if (effect != null && spatial.getParent() != null) { + curTime = 0; + effect.setLocalTranslation(spatial.getLocalTranslation()); + spatial.getParent().attachChild(effect); + effect.emitAllParticles(); + } + space.remove(this); + spatial.removeFromParent(); + } + } + + public void prePhysicsTick(PhysicsSpace space, float f) { + space.removeCollisionListener(this); + } + + public void physicsTick(PhysicsSpace space, float f) { + //get all overlapping objects and apply impulse to them + for (Iterator it = ghostObject.getOverlappingObjects().iterator(); it.hasNext();) { + PhysicsCollisionObject physicsCollisionObject = it.next(); + if (physicsCollisionObject instanceof PhysicsRigidBody) { + PhysicsRigidBody rBody = (PhysicsRigidBody) physicsCollisionObject; + rBody.getPhysicsLocation(vector2); + vector2.subtractLocal(vector); + float force = explosionRadius - vector2.length(); + force *= forceFactor; + force = force > 0 ? force : 0; + vector2.normalizeLocal(); + vector2.multLocal(force); + ((PhysicsRigidBody) physicsCollisionObject).applyImpulse(vector2, Vector3f.ZERO); + } + } + space.removeTickListener(this); + space.remove(ghostObject); + } + + @Override + public void update(float tpf) { + super.update(tpf); + if(enabled){ + timer+=tpf; + if(timer>maxTime){ + if(spatial.getParent()!=null){ + space.removeCollisionListener(this); + space.remove(this); + spatial.removeFromParent(); + } + } + } + if (enabled && curTime >= 0) { + curTime += tpf; + if (curTime > fxTime) { + curTime = -1; + effect.removeFromParent(); + } + } + } + + /** + * @return the explosionRadius + */ + public float getExplosionRadius() { + return explosionRadius; + } + + /** + * @param explosionRadius the explosionRadius to set + */ + public void setExplosionRadius(float explosionRadius) { + this.explosionRadius = explosionRadius; + createGhostObject(); + } + + public float getForceFactor() { + return forceFactor; + } + + public void setForceFactor(float forceFactor) { + this.forceFactor = forceFactor; + } + + + @Override + public void read(JmeImporter im) throws IOException { + throw new UnsupportedOperationException("Reading not supported."); + } + + @Override + public void write(JmeExporter ex) throws IOException { + throw new UnsupportedOperationException("Saving not supported."); + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java b/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java new file mode 100644 index 000000000..469fb03ae --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.PhysicsControl; +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.Control; +import java.io.IOException; + +/** + * PhysicsHoverControl uses a RayCast Vehicle with "slippery wheels" to simulate a hovering tank + * @author normenhansen + */ +public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsControl, PhysicsTickListener { + + protected Spatial spatial; + protected boolean enabled = true; + protected PhysicsSpace space = null; + protected float steeringValue = 0; + protected float accelerationValue = 0; + protected int xw = 3; + protected int zw = 5; + protected int yw = 2; + protected Vector3f HOVER_HEIGHT_LF_START = new Vector3f(xw, 1, zw); + protected Vector3f HOVER_HEIGHT_RF_START = new Vector3f(-xw, 1, zw); + protected Vector3f HOVER_HEIGHT_LR_START = new Vector3f(xw, 1, -zw); + protected Vector3f HOVER_HEIGHT_RR_START = new Vector3f(-xw, 1, -zw); + protected Vector3f HOVER_HEIGHT_LF = new Vector3f(xw, -yw, zw); + protected Vector3f HOVER_HEIGHT_RF = new Vector3f(-xw, -yw, zw); + protected Vector3f HOVER_HEIGHT_LR = new Vector3f(xw, -yw, -zw); + protected Vector3f HOVER_HEIGHT_RR = new Vector3f(-xw, -yw, -zw); + protected Vector3f tempVect1 = new Vector3f(0, 0, 0); + protected Vector3f tempVect2 = new Vector3f(0, 0, 0); + protected Vector3f tempVect3 = new Vector3f(0, 0, 0); +// protected float rotationCounterForce = 10000f; +// protected float speedCounterMult = 2000f; +// protected float multiplier = 1000f; + + public PhysicsHoverControl() { + } + + /** + * Creates a new PhysicsNode with the supplied collision shape + * @param shape + */ + public PhysicsHoverControl(CollisionShape shape) { + super(shape); + createWheels(); + } + + public PhysicsHoverControl(CollisionShape shape, float mass) { + super(shape, mass); + createWheels(); + } + + public Control cloneForSpatial(Spatial spatial) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public void setSpatial(Spatial spatial) { + this.spatial = spatial; + setUserObject(spatial); + if (spatial == null) { + return; + } + setPhysicsLocation(spatial.getWorldTranslation()); + setPhysicsRotation(spatial.getWorldRotation().toRotationMatrix()); + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + private void createWheels() { + addWheel(HOVER_HEIGHT_LF_START, new Vector3f(0, -1, 0), new Vector3f(-1, 0, 0), yw, yw, false); + addWheel(HOVER_HEIGHT_RF_START, new Vector3f(0, -1, 0), new Vector3f(-1, 0, 0), yw, yw, false); + addWheel(HOVER_HEIGHT_LR_START, new Vector3f(0, -1, 0), new Vector3f(-1, 0, 0), yw, yw, false); + addWheel(HOVER_HEIGHT_RR_START, new Vector3f(0, -1, 0), new Vector3f(-1, 0, 0), yw, yw, false); + for (int i = 0; i < 4; i++) { + getWheel(i).setFrictionSlip(0.001f); + } + } + + public void prePhysicsTick(PhysicsSpace space, float f) { + Vector3f angVel = getAngularVelocity(); + float rotationVelocity = angVel.getY(); + Vector3f dir = getForwardVector(tempVect2).multLocal(1, 0, 1).normalizeLocal(); + getLinearVelocity(tempVect3); + Vector3f linearVelocity = tempVect3.multLocal(1, 0, 1); + + if (steeringValue != 0) { + if (rotationVelocity < 1 && rotationVelocity > -1) { + applyTorque(tempVect1.set(0, steeringValue, 0)); + } + } else { + // counter the steering value! + if (rotationVelocity > 0.2f) { + applyTorque(tempVect1.set(0, -mass * 20, 0)); + } else if (rotationVelocity < -0.2f) { + applyTorque(tempVect1.set(0, mass * 20, 0)); + } + } + if (accelerationValue > 0) { + // counter force that will adjust velocity + // if we are not going where we want to go. + // this will prevent "drifting" and thus improve control + // of the vehicle + float d = dir.dot(linearVelocity.normalize()); + Vector3f counter = dir.project(linearVelocity).normalizeLocal().negateLocal().multLocal(1 - d); + applyForce(counter.multLocal(mass * 10), Vector3f.ZERO); + + if (linearVelocity.length() < 30) { + applyForce(dir.multLocal(accelerationValue), Vector3f.ZERO); + } + } else { + // counter the acceleration value + if (linearVelocity.length() > FastMath.ZERO_TOLERANCE) { + linearVelocity.normalizeLocal().negateLocal(); + applyForce(linearVelocity.mult(mass * 10), Vector3f.ZERO); + } + } + } + + public void physicsTick(PhysicsSpace space, float f) { + } + + public void update(float tpf) { + if (enabled && spatial != null) { + getMotionState().applyTransform(spatial); + } + } + + public void render(RenderManager rm, ViewPort vp) { + } + + 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/jme3-examples/src/main/java/jme3test/bullet/PhysicsTestHelper.java b/jme3-examples/src/main/java/jme3test/bullet/PhysicsTestHelper.java new file mode 100644 index 000000000..bb057606e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/PhysicsTestHelper.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.Application; +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; +import com.jme3.texture.Texture; + +/** + * + * @author normenhansen + */ +public class PhysicsTestHelper { + + /** + * creates a simple physics test world with a floor, an obstacle and some test boxes + * @param rootNode + * @param assetManager + * @param space + */ + public static void createPhysicsTestWorld(Node rootNode, AssetManager assetManager, PhysicsSpace space) { + AmbientLight light = new AmbientLight(); + light.setColor(ColorRGBA.LightGray); + rootNode.addLight(light); + + Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + + Box floorBox = new Box(140, 0.25f, 140); + Geometry floorGeometry = new Geometry("Floor", floorBox); + floorGeometry.setMaterial(material); + floorGeometry.setLocalTranslation(0, -5, 0); +// Plane plane = new Plane(); +// plane.setOriginNormal(new Vector3f(0, 0.25f, 0), Vector3f.UNIT_Y); +// floorGeometry.addControl(new RigidBodyControl(new PlaneCollisionShape(plane), 0)); + floorGeometry.addControl(new RigidBodyControl(0)); + rootNode.attachChild(floorGeometry); + space.add(floorGeometry); + + //movable boxes + for (int i = 0; i < 12; i++) { + Box box = new Box(0.25f, 0.25f, 0.25f); + Geometry boxGeometry = new Geometry("Box", box); + boxGeometry.setMaterial(material); + boxGeometry.setLocalTranslation(i, 5, -3); + //RigidBodyControl automatically uses box collision shapes when attached to single geometry with box mesh + boxGeometry.addControl(new RigidBodyControl(2)); + rootNode.attachChild(boxGeometry); + space.add(boxGeometry); + } + + //immovable sphere with mesh collision shape + Sphere sphere = new Sphere(8, 8, 1); + Geometry sphereGeometry = new Geometry("Sphere", sphere); + sphereGeometry.setMaterial(material); + sphereGeometry.setLocalTranslation(4, -4, 2); + sphereGeometry.addControl(new RigidBodyControl(new MeshCollisionShape(sphere), 0)); + rootNode.attachChild(sphereGeometry); + space.add(sphereGeometry); + + } + + public static void createPhysicsTestWorldSoccer(Node rootNode, AssetManager assetManager, PhysicsSpace space) { + AmbientLight light = new AmbientLight(); + light.setColor(ColorRGBA.LightGray); + rootNode.addLight(light); + + Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + + Box floorBox = new Box(20, 0.25f, 20); + Geometry floorGeometry = new Geometry("Floor", floorBox); + floorGeometry.setMaterial(material); + floorGeometry.setLocalTranslation(0, -0.25f, 0); +// Plane plane = new Plane(); +// plane.setOriginNormal(new Vector3f(0, 0.25f, 0), Vector3f.UNIT_Y); +// floorGeometry.addControl(new RigidBodyControl(new PlaneCollisionShape(plane), 0)); + floorGeometry.addControl(new RigidBodyControl(0)); + rootNode.attachChild(floorGeometry); + space.add(floorGeometry); + + //movable spheres + for (int i = 0; i < 5; i++) { + Sphere sphere = new Sphere(16, 16, .5f); + Geometry ballGeometry = new Geometry("Soccer ball", sphere); + ballGeometry.setMaterial(material); + ballGeometry.setLocalTranslation(i, 2, -3); + //RigidBodyControl automatically uses Sphere collision shapes when attached to single geometry with sphere mesh + ballGeometry.addControl(new RigidBodyControl(.001f)); + ballGeometry.getControl(RigidBodyControl.class).setRestitution(1); + rootNode.attachChild(ballGeometry); + space.add(ballGeometry); + } + { + //immovable Box with mesh collision shape + Box box = new Box(1, 1, 1); + Geometry boxGeometry = new Geometry("Box", box); + boxGeometry.setMaterial(material); + boxGeometry.setLocalTranslation(4, 1, 2); + boxGeometry.addControl(new RigidBodyControl(new MeshCollisionShape(box), 0)); + rootNode.attachChild(boxGeometry); + space.add(boxGeometry); + } + { + //immovable Box with mesh collision shape + Box box = new Box(1, 1, 1); + Geometry boxGeometry = new Geometry("Box", box); + boxGeometry.setMaterial(material); + boxGeometry.setLocalTranslation(4, 3, 4); + boxGeometry.addControl(new RigidBodyControl(new MeshCollisionShape(box), 0)); + rootNode.attachChild(boxGeometry); + space.add(boxGeometry); + } + } + + /** + * creates a box geometry with a RigidBodyControl + * @param assetManager + * @return + */ + public static Geometry createPhysicsTestBox(AssetManager assetManager) { + Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + Box box = new Box(0.25f, 0.25f, 0.25f); + Geometry boxGeometry = new Geometry("Box", box); + boxGeometry.setMaterial(material); + //RigidBodyControl automatically uses box collision shapes when attached to single geometry with box mesh + boxGeometry.addControl(new RigidBodyControl(2)); + return boxGeometry; + } + + /** + * creates a sphere geometry with a RigidBodyControl + * @param assetManager + * @return + */ + public static Geometry createPhysicsTestSphere(AssetManager assetManager) { + Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + Sphere sphere = new Sphere(8, 8, 0.25f); + Geometry boxGeometry = new Geometry("Sphere", sphere); + boxGeometry.setMaterial(material); + //RigidBodyControl automatically uses sphere collision shapes when attached to single geometry with sphere mesh + boxGeometry.addControl(new RigidBodyControl(2)); + return boxGeometry; + } + + /** + * creates an empty node with a RigidBodyControl + * @param manager + * @param shape + * @param mass + * @return + */ + public static Node createPhysicsTestNode(AssetManager manager, CollisionShape shape, float mass) { + Node node = new Node("PhysicsNode"); + RigidBodyControl control = new RigidBodyControl(shape, mass); + node.addControl(control); + return node; + } + + /** + * creates the necessary inputlistener and action to shoot balls from teh camera + * @param app + * @param rootNode + * @param space + */ + public static void createBallShooter(final Application app, final Node rootNode, final PhysicsSpace space) { + ActionListener actionListener = new ActionListener() { + + public void onAction(String name, boolean keyPressed, float tpf) { + Sphere bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + Material mat2 = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG"); + key2.setGenerateMips(true); + Texture tex2 = app.getAssetManager().loadTexture(key2); + mat2.setTexture("ColorMap", tex2); + if (name.equals("shoot") && !keyPressed) { + Geometry bulletg = new Geometry("bullet", bullet); + bulletg.setMaterial(mat2); + bulletg.setShadowMode(ShadowMode.CastAndReceive); + bulletg.setLocalTranslation(app.getCamera().getLocation()); + RigidBodyControl bulletControl = new RigidBodyControl(10); + 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/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java b/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java new file mode 100644 index 000000000..a4b4b92a3 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.control.VehicleControl; +import com.jme3.bullet.joints.SliderJoint; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Cylinder; +import com.jme3.texture.Texture; + +/** + * Tests attaching/detaching nodes via joints + * @author normenhansen + */ +public class TestAttachDriver extends SimpleApplication implements ActionListener { + + private VehicleControl vehicle; + private RigidBodyControl driver; + private RigidBodyControl bridge; + private SliderJoint slider; + private final float accelerationForce = 1000.0f; + private final float brakeForce = 100.0f; + private float steeringValue = 0; + private float accelerationValue = 0; + private Vector3f jumpForce = new Vector3f(0, 3000, 0); + private BulletAppState bulletAppState; + + public static void main(String[] args) { + TestAttachDriver app = new TestAttachDriver(); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + setupKeys(); + setupFloor(); + buildPlayer(); + } + + private PhysicsSpace getPhysicsSpace(){ + return bulletAppState.getPhysicsSpace(); + } + + private void setupKeys() { + inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_H)); + inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K)); + inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addListener(this, "Lefts"); + inputManager.addListener(this, "Rights"); + inputManager.addListener(this, "Ups"); + inputManager.addListener(this, "Downs"); + inputManager.addListener(this, "Space"); + inputManager.addListener(this, "Reset"); + } + + public void setupFloor() { + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key = new TextureKey("Interface/Logo/Monkey.jpg", true); + key.setGenerateMips(true); + Texture tex = assetManager.loadTexture(key); + tex.setMinFilter(Texture.MinFilter.Trilinear); + mat.setTexture("ColorMap", tex); + + Box floor = new Box(Vector3f.ZERO, 100, 1f, 100); + Geometry floorGeom = new Geometry("Floor", floor); + floorGeom.setMaterial(mat); + floorGeom.setLocalTranslation(new Vector3f(0f, -3, 0f)); + + floorGeom.addControl(new RigidBodyControl(new MeshCollisionShape(floorGeom.getMesh()), 0)); + rootNode.attachChild(floorGeom); + getPhysicsSpace().add(floorGeom); + } + + private void buildPlayer() { + Material mat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + mat.setColor("Color", ColorRGBA.Red); + + //create a compound shape and attach the BoxCollisionShape for the car body at 0,1,0 + //this shifts the effective center of mass of the BoxCollisionShape to 0,-1,0 + CompoundCollisionShape compoundShape = new CompoundCollisionShape(); + BoxCollisionShape box = new BoxCollisionShape(new Vector3f(1.2f, 0.5f, 2.4f)); + compoundShape.addChildShape(box, new Vector3f(0, 1, 0)); + + //create vehicle node + Node vehicleNode=new Node("vehicleNode"); + vehicle = new VehicleControl(compoundShape, 800); + vehicleNode.addControl(vehicle); + + //setting suspension values for wheels, this can be a bit tricky + //see also https://docs.google.com/Doc?docid=0AXVUZ5xw6XpKZGNuZG56a3FfMzU0Z2NyZnF4Zmo&hl=en + float stiffness = 60.0f;//200=f1 car + float compValue = .3f; //(should be lower than damp) + float dampValue = .4f; + vehicle.setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness)); + vehicle.setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness)); + vehicle.setSuspensionStiffness(stiffness); + vehicle.setMaxSuspensionForce(10000.0f); + + //Create four wheels and add them at their locations + Vector3f wheelDirection = new Vector3f(0, -1, 0); // was 0, -1, 0 + Vector3f wheelAxle = new Vector3f(-1, 0, 0); // was -1, 0, 0 + float radius = 0.5f; + float restLength = 0.3f; + float yOff = 0.5f; + float xOff = 1f; + float zOff = 2f; + + Cylinder wheelMesh = new Cylinder(16, 16, radius, radius * 0.6f, true); + + Node node1 = new Node("wheel 1 node"); + Geometry wheels1 = new Geometry("wheel 1", wheelMesh); + node1.attachChild(wheels1); + wheels1.rotate(0, FastMath.HALF_PI, 0); + wheels1.setMaterial(mat); + vehicle.addWheel(node1, new Vector3f(-xOff, yOff, zOff), + wheelDirection, wheelAxle, restLength, radius, true); + + Node node2 = new Node("wheel 2 node"); + Geometry wheels2 = new Geometry("wheel 2", wheelMesh); + node2.attachChild(wheels2); + wheels2.rotate(0, FastMath.HALF_PI, 0); + wheels2.setMaterial(mat); + vehicle.addWheel(node2, new Vector3f(xOff, yOff, zOff), + wheelDirection, wheelAxle, restLength, radius, true); + + Node node3 = new Node("wheel 3 node"); + Geometry wheels3 = new Geometry("wheel 3", wheelMesh); + node3.attachChild(wheels3); + wheels3.rotate(0, FastMath.HALF_PI, 0); + wheels3.setMaterial(mat); + vehicle.addWheel(node3, new Vector3f(-xOff, yOff, -zOff), + wheelDirection, wheelAxle, restLength, radius, false); + + Node node4 = new Node("wheel 4 node"); + Geometry wheels4 = new Geometry("wheel 4", wheelMesh); + node4.attachChild(wheels4); + wheels4.rotate(0, FastMath.HALF_PI, 0); + wheels4.setMaterial(mat); + vehicle.addWheel(node4, new Vector3f(xOff, yOff, -zOff), + wheelDirection, wheelAxle, restLength, radius, false); + + vehicleNode.attachChild(node1); + vehicleNode.attachChild(node2); + vehicleNode.attachChild(node3); + vehicleNode.attachChild(node4); + + rootNode.attachChild(vehicleNode); + getPhysicsSpace().add(vehicle); + + //driver + Node driverNode=new Node("driverNode"); + driverNode.setLocalTranslation(0,2,0); + driver=new RigidBodyControl(new BoxCollisionShape(new Vector3f(0.2f,.5f,0.2f))); + driverNode.addControl(driver); + + rootNode.attachChild(driverNode); + getPhysicsSpace().add(driver); + + //joint + slider=new SliderJoint(driver, vehicle, Vector3f.UNIT_Y.negate(), Vector3f.UNIT_Y, true); + slider.setUpperLinLimit(.1f); + slider.setLowerLinLimit(-.1f); + + getPhysicsSpace().add(slider); + + Node pole1Node=new Node("pole1Node"); + Node pole2Node=new Node("pole1Node"); + Node bridgeNode=new Node("pole1Node"); + pole1Node.setLocalTranslation(new Vector3f(-2,-1,4)); + pole2Node.setLocalTranslation(new Vector3f(2,-1,4)); + bridgeNode.setLocalTranslation(new Vector3f(0,1.4f,4)); + + RigidBodyControl pole1=new RigidBodyControl(new BoxCollisionShape(new Vector3f(0.2f,1.25f,0.2f)),0); + pole1Node.addControl(pole1); + RigidBodyControl pole2=new RigidBodyControl(new BoxCollisionShape(new Vector3f(0.2f,1.25f,0.2f)),0); + pole2Node.addControl(pole2); + bridge=new RigidBodyControl(new BoxCollisionShape(new Vector3f(2.5f,0.2f,0.2f))); + bridgeNode.addControl(bridge); + + rootNode.attachChild(pole1Node); + rootNode.attachChild(pole2Node); + rootNode.attachChild(bridgeNode); + getPhysicsSpace().add(pole1); + getPhysicsSpace().add(pole2); + getPhysicsSpace().add(bridge); + + } + + @Override + public void simpleUpdate(float tpf) { + Quaternion quat=new Quaternion(); + cam.lookAt(vehicle.getPhysicsLocation(), Vector3f.UNIT_Y); + } + + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("Lefts")) { + if (value) { + steeringValue += .5f; + } else { + steeringValue += -.5f; + } + vehicle.steer(steeringValue); + } else if (binding.equals("Rights")) { + if (value) { + steeringValue += -.5f; + } else { + steeringValue += .5f; + } + vehicle.steer(steeringValue); + } else if (binding.equals("Ups")) { + if (value) { + accelerationValue += accelerationForce; + } else { + accelerationValue -= accelerationForce; + } + vehicle.accelerate(accelerationValue); + } else if (binding.equals("Downs")) { + if (value) { + vehicle.brake(brakeForce); + } else { + vehicle.brake(0f); + } + } else if (binding.equals("Space")) { + if (value) { + getPhysicsSpace().remove(slider); + slider.destroy(); + vehicle.applyImpulse(jumpForce, Vector3f.ZERO); + } + } else if (binding.equals("Reset")) { + if (value) { + System.out.println("Reset"); + vehicle.setPhysicsLocation(new Vector3f(0, 0, 0)); + vehicle.setPhysicsRotation(new Matrix3f()); + vehicle.setLinearVelocity(Vector3f.ZERO); + vehicle.setAngularVelocity(Vector3f.ZERO); + vehicle.resetSuspension(); + bridge.setPhysicsLocation(new Vector3f(0,1.4f,4)); + bridge.setPhysicsRotation(Quaternion.DIRECTION_Z.toRotationMatrix()); + } + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java b/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java new file mode 100644 index 000000000..5b6992f3a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.GhostControl; +import com.jme3.bullet.control.PhysicsControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.joints.HingeJoint; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; + +/** + * Tests attaching ghost nodes to physicsnodes via the scenegraph + * @author normenhansen + */ +public class TestAttachGhostObject extends SimpleApplication implements AnalogListener { + + private HingeJoint joint; + private GhostControl ghostControl; + private Node collisionNode; + private Node hammerNode; + private Vector3f tempVec = new Vector3f(); + private BulletAppState bulletAppState; + + public static void main(String[] args) { + TestAttachGhostObject app = new TestAttachGhostObject(); + app.start(); + } + + private void setupKeys() { + inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_H)); + inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K)); + inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(this, "Lefts", "Rights", "Space"); + } + + public void onAnalog(String binding, float value, float tpf) { + if (binding.equals("Lefts")) { + joint.enableMotor(true, 1, .1f); + } else if (binding.equals("Rights")) { + joint.enableMotor(true, -1, .1f); + } else if (binding.equals("Space")) { + joint.enableMotor(false, 0, 0); + } + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + setupKeys(); + setupJoint(); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + public void setupJoint() { + Node holderNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(.1f, .1f, .1f)), 0); + holderNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, 0, 0f)); + rootNode.attachChild(holderNode); + getPhysicsSpace().add(holderNode); + + Node hammerNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(.3f, .3f, .3f)), 1); + hammerNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -1, 0f)); + rootNode.attachChild(hammerNode); + getPhysicsSpace().add(hammerNode); + + //immovable + collisionNode = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(.3f, .3f, .3f)), 0); + collisionNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(1.8f, 0, 0f)); + rootNode.attachChild(collisionNode); + getPhysicsSpace().add(collisionNode); + + //ghost node + ghostControl = new GhostControl(new SphereCollisionShape(0.7f)); + + hammerNode.addControl(ghostControl); + getPhysicsSpace().add(ghostControl); + + joint = new HingeJoint(holderNode.getControl(RigidBodyControl.class), hammerNode.getControl(RigidBodyControl.class), Vector3f.ZERO, new Vector3f(0f, -1, 0f), Vector3f.UNIT_Z, Vector3f.UNIT_Z); + getPhysicsSpace().add(joint); + } + + @Override + public void simpleUpdate(float tpf) { + if (ghostControl.getOverlappingObjects().contains(collisionNode.getControl(PhysicsControl.class))) { + fpsText.setText("collide"); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestBetterCharacter.java b/jme3-examples/src/main/java/jme3test/bullet/TestBetterCharacter.java new file mode 100644 index 000000000..0b9dfbad3 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestBetterCharacter.java @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved.

      + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer.

      * Redistributions + * in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution.

      * Neither the name of + * 'jMonkeyEngine' nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written + * permission.

      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT + * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.control.BetterCharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.debug.DebugTools; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.CameraNode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.control.CameraControl.ControlDirection; +import com.jme3.scene.shape.Sphere; +import com.jme3.system.AppSettings; + +/** + * A walking physical character followed by a 3rd person camera. (No animation.) + * + * @author normenhansen, zathras + */ +public class TestBetterCharacter extends SimpleApplication implements ActionListener { + + private BulletAppState bulletAppState; + private BetterCharacterControl physicsCharacter; + private Node characterNode; + private CameraNode camNode; + boolean rotate = false; + private Vector3f walkDirection = new Vector3f(0, 0, 0); + private Vector3f viewDirection = new Vector3f(0, 0, 1); + boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, + leftRotate = false, rightRotate = false; + private Vector3f normalGravity = new Vector3f(0, -9.81f, 0); + private Geometry planet; + + public static void main(String[] args) { + TestBetterCharacter app = new TestBetterCharacter(); + AppSettings settings = new AppSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL2); + settings.setAudioRenderer(AppSettings.LWJGL_OPENAL); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() { + //setup keyboard mapping + setupKeys(); + + // activate physics + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); + + // init a physics test scene + PhysicsTestHelper.createPhysicsTestWorldSoccer(rootNode, assetManager, bulletAppState.getPhysicsSpace()); + PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace()); + setupPlanet(); + + // Create a node for the character model + characterNode = new Node("character node"); + characterNode.setLocalTranslation(new Vector3f(4, 5, 2)); + + // Add a character control to the node so we can add other things and + // control the model rotation + physicsCharacter = new BetterCharacterControl(0.3f, 2.5f, 8f); + characterNode.addControl(physicsCharacter); + getPhysicsSpace().add(physicsCharacter); + + // Load model, attach to character node + Node model = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o"); + model.setLocalScale(1.50f); + characterNode.attachChild(model); + + // Add character node to the rootNode + rootNode.attachChild(characterNode); + + // Set forward camera node that follows the character, only used when + // view is "locked" + camNode = new CameraNode("CamNode", cam); + camNode.setControlDir(ControlDirection.SpatialToCamera); + camNode.setLocalTranslation(new Vector3f(0, 2, -6)); + Quaternion quat = new Quaternion(); + // These coordinates are local, the camNode is attached to the character node! + quat.lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y); + camNode.setLocalRotation(quat); + characterNode.attachChild(camNode); + // Disable by default, can be enabled via keyboard shortcut + camNode.setEnabled(false); + } + + @Override + public void simpleUpdate(float tpf) { + // Apply planet gravity to character if close enough (see below) + checkPlanetGravity(); + + // Get current forward and left vectors of model by using its rotation + // to rotate the unit vectors + Vector3f modelForwardDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_Z); + Vector3f modelLeftDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_X); + + // WalkDirection is global! + // You *can* make your character fly with this. + walkDirection.set(0, 0, 0); + if (leftStrafe) { + walkDirection.addLocal(modelLeftDir.mult(3)); + } else if (rightStrafe) { + walkDirection.addLocal(modelLeftDir.negate().multLocal(3)); + } + if (forward) { + walkDirection.addLocal(modelForwardDir.mult(3)); + } else if (backward) { + walkDirection.addLocal(modelForwardDir.negate().multLocal(3)); + } + physicsCharacter.setWalkDirection(walkDirection); + + // ViewDirection is local to characters physics system! + // The final world rotation depends on the gravity and on the state of + // setApplyPhysicsLocal() + if (leftRotate) { + Quaternion rotateL = new Quaternion().fromAngleAxis(FastMath.PI * tpf, Vector3f.UNIT_Y); + rotateL.multLocal(viewDirection); + } else if (rightRotate) { + Quaternion rotateR = new Quaternion().fromAngleAxis(-FastMath.PI * tpf, Vector3f.UNIT_Y); + rotateR.multLocal(viewDirection); + } + physicsCharacter.setViewDirection(viewDirection); + fpsText.setText("Touch da ground = " + physicsCharacter.isOnGround()); + if (!lockView) { + cam.lookAt(characterNode.getWorldTranslation().add(new Vector3f(0, 2, 0)), Vector3f.UNIT_Y); + } + } + + private void setupPlanet() { + Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + //immovable sphere with mesh collision shape + Sphere sphere = new Sphere(64, 64, 20); + planet = new Geometry("Sphere", sphere); + planet.setMaterial(material); + planet.setLocalTranslation(30, -15, 30); + planet.addControl(new RigidBodyControl(new MeshCollisionShape(sphere), 0)); + rootNode.attachChild(planet); + getPhysicsSpace().add(planet); + } + + private void checkPlanetGravity() { + Vector3f planetDist = planet.getWorldTranslation().subtract(characterNode.getWorldTranslation()); + if (planetDist.length() < 24) { + physicsCharacter.setGravity(planetDist.normalizeLocal().multLocal(9.81f)); + } else { + physicsCharacter.setGravity(normalGravity); + } + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("Strafe Left")) { + if (value) { + leftStrafe = true; + } else { + leftStrafe = false; + } + } else if (binding.equals("Strafe Right")) { + if (value) { + rightStrafe = true; + } else { + rightStrafe = false; + } + } else if (binding.equals("Rotate Left")) { + if (value) { + leftRotate = true; + } else { + leftRotate = false; + } + } else if (binding.equals("Rotate Right")) { + if (value) { + rightRotate = true; + } else { + rightRotate = false; + } + } else if (binding.equals("Walk Forward")) { + if (value) { + forward = true; + } else { + forward = false; + } + } else if (binding.equals("Walk Backward")) { + if (value) { + backward = true; + } else { + backward = false; + } + } else if (binding.equals("Jump")) { + physicsCharacter.jump(); + } else if (binding.equals("Duck")) { + if (value) { + physicsCharacter.setDucked(true); + } else { + physicsCharacter.setDucked(false); + } + } else if (binding.equals("Lock View")) { + if (value && lockView) { + lockView = false; + } else if (value && !lockView) { + lockView = true; + } + flyCam.setEnabled(!lockView); + camNode.setEnabled(lockView); + } + } + private boolean lockView = false; + + private void setupKeys() { + inputManager.addMapping("Strafe Left", + new KeyTrigger(KeyInput.KEY_U), + new KeyTrigger(KeyInput.KEY_Z)); + inputManager.addMapping("Strafe Right", + new KeyTrigger(KeyInput.KEY_O), + new KeyTrigger(KeyInput.KEY_X)); + inputManager.addMapping("Rotate Left", + new KeyTrigger(KeyInput.KEY_J), + new KeyTrigger(KeyInput.KEY_LEFT)); + inputManager.addMapping("Rotate Right", + new KeyTrigger(KeyInput.KEY_L), + new KeyTrigger(KeyInput.KEY_RIGHT)); + inputManager.addMapping("Walk Forward", + new KeyTrigger(KeyInput.KEY_I), + new KeyTrigger(KeyInput.KEY_UP)); + inputManager.addMapping("Walk Backward", + new KeyTrigger(KeyInput.KEY_K), + new KeyTrigger(KeyInput.KEY_DOWN)); + inputManager.addMapping("Jump", + new KeyTrigger(KeyInput.KEY_F), + new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("Duck", + new KeyTrigger(KeyInput.KEY_G), + new KeyTrigger(KeyInput.KEY_LSHIFT), + new KeyTrigger(KeyInput.KEY_RSHIFT)); + inputManager.addMapping("Lock View", + new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addListener(this, "Strafe Left", "Strafe Right"); + inputManager.addListener(this, "Rotate Left", "Rotate Right"); + inputManager.addListener(this, "Walk Forward", "Walk Backward"); + inputManager.addListener(this, "Jump", "Duck", "Lock View"); + } + + @Override + public void simpleRender(RenderManager rm) { + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestBoneRagdoll.java b/jme3-examples/src/main/java/jme3test/bullet/TestBoneRagdoll.java new file mode 100644 index 000000000..bc806b018 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestBoneRagdoll.java @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.animation.*; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionEvent; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.RagdollCollisionListener; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.KinematicRagdollControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.debug.SkeletonDebugger; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; +import com.jme3.texture.Texture; + +/** + * PHYSICS RAGDOLLS ARE NOT WORKING PROPERLY YET! + * @author normenhansen + */ +public class TestBoneRagdoll extends SimpleApplication implements RagdollCollisionListener, AnimEventListener { + + private BulletAppState bulletAppState; + Material matBullet; + Node model; + KinematicRagdollControl ragdoll; + float bulletSize = 1f; + Material mat; + Material mat3; + private Sphere bullet; + private SphereCollisionShape bulletCollisionShape; + + public static void main(String[] args) { + TestBoneRagdoll app = new TestBoneRagdoll(); + app.start(); + } + + public void simpleInitApp() { + initCrossHairs(); + initMaterial(); + + cam.setLocation(new Vector3f(0.26924422f, 6.646658f, 22.265987f)); + cam.setRotation(new Quaternion(-2.302544E-4f, 0.99302495f, -0.117888905f, -0.0019395084f)); + + + bulletAppState = new BulletAppState(); + bulletAppState.setEnabled(true); + stateManager.attach(bulletAppState); + bullet = new Sphere(32, 32, 1.0f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(1.0f); + +// bulletAppState.getPhysicsSpace().enableDebug(assetManager); + PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); + setupLight(); + + model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); + + // model.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X)); + + //debug view + AnimControl control = model.getControl(AnimControl.class); + SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton", control.getSkeleton()); + Material mat2 = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + mat2.getAdditionalRenderState().setWireframe(true); + mat2.setColor("Color", ColorRGBA.Green); + mat2.getAdditionalRenderState().setDepthTest(false); + skeletonDebug.setMaterial(mat2); + skeletonDebug.setLocalTranslation(model.getLocalTranslation()); + + //Note: PhysicsRagdollControl is still TODO, constructor will change + ragdoll = new KinematicRagdollControl(0.5f); + setupSinbad(ragdoll); + ragdoll.addCollisionListener(this); + model.addControl(ragdoll); + + float eighth_pi = FastMath.PI * 0.125f; + ragdoll.setJointLimit("Waist", eighth_pi, eighth_pi, eighth_pi, eighth_pi, eighth_pi, eighth_pi); + ragdoll.setJointLimit("Chest", eighth_pi, eighth_pi, 0, 0, eighth_pi, eighth_pi); + + + //Oto's head is almost rigid + // ragdoll.setJointLimit("head", 0, 0, eighth_pi, -eighth_pi, 0, 0); + + getPhysicsSpace().add(ragdoll); + speed = 1.3f; + + rootNode.attachChild(model); + // rootNode.attachChild(skeletonDebug); + flyCam.setMoveSpeed(50); + + + animChannel = control.createChannel(); + animChannel.setAnim("Dance"); + control.addListener(this); + + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("toggle") && isPressed) { + + Vector3f v = new Vector3f(); + v.set(model.getLocalTranslation()); + v.y = 0; + model.setLocalTranslation(v); + Quaternion q = new Quaternion(); + float[] angles = new float[3]; + model.getLocalRotation().toAngles(angles); + q.fromAngleAxis(angles[1], Vector3f.UNIT_Y); + model.setLocalRotation(q); + if (angles[0] < 0) { + animChannel.setAnim("StandUpBack"); + ragdoll.blendToKinematicMode(0.5f); + } else { + animChannel.setAnim("StandUpFront"); + ragdoll.blendToKinematicMode(0.5f); + } + + } + if (name.equals("bullet+") && isPressed) { + bulletSize += 0.1f; + + } + if (name.equals("bullet-") && isPressed) { + bulletSize -= 0.1f; + + } + + if (name.equals("stop") && isPressed) { + ragdoll.setEnabled(!ragdoll.isEnabled()); + ragdoll.setRagdollMode(); + } + + if (name.equals("shoot") && !isPressed) { + Geometry bulletg = new Geometry("bullet", bullet); + bulletg.setMaterial(matBullet); + bulletg.setLocalTranslation(cam.getLocation()); + bulletg.setLocalScale(bulletSize); + bulletCollisionShape = new SphereCollisionShape(bulletSize); + RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, bulletSize * 10); + bulletNode.setCcdMotionThreshold(0.001f); + bulletNode.setLinearVelocity(cam.getDirection().mult(80)); + bulletg.addControl(bulletNode); + rootNode.attachChild(bulletg); + getPhysicsSpace().add(bulletNode); + } + if (name.equals("boom") && !isPressed) { + Geometry bulletg = new Geometry("bullet", bullet); + bulletg.setMaterial(matBullet); + bulletg.setLocalTranslation(cam.getLocation()); + bulletg.setLocalScale(bulletSize); + bulletCollisionShape = new SphereCollisionShape(bulletSize); + BombControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1); + bulletNode.setForceFactor(8); + bulletNode.setExplosionRadius(20); + bulletNode.setCcdMotionThreshold(0.001f); + bulletNode.setLinearVelocity(cam.getDirection().mult(180)); + bulletg.addControl(bulletNode); + rootNode.attachChild(bulletg); + getPhysicsSpace().add(bulletNode); + } + } + }, "toggle", "shoot", "stop", "bullet+", "bullet-", "boom"); + inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addMapping("boom", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); + inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_H)); + inputManager.addMapping("bullet-", new KeyTrigger(KeyInput.KEY_COMMA)); + inputManager.addMapping("bullet+", new KeyTrigger(KeyInput.KEY_PERIOD)); + + + } + + private void setupLight() { + // AmbientLight al = new AmbientLight(); + // al.setColor(ColorRGBA.White.mult(1)); + // rootNode.addLight(al); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal()); + dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); + rootNode.addLight(dl); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + public void initMaterial() { + + matBullet = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG"); + key2.setGenerateMips(true); + Texture tex2 = assetManager.loadTexture(key2); + matBullet.setTexture("ColorMap", tex2); + } + + protected void initCrossHairs() { + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText ch = new BitmapText(guiFont, false); + ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); + ch.setText("+"); // crosshairs + ch.setLocalTranslation( // center + settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2, + settings.getHeight() / 2 + ch.getLineHeight() / 2, 0); + guiNode.attachChild(ch); + } + + public void collide(Bone bone, PhysicsCollisionObject object, PhysicsCollisionEvent event) { + + if (object.getUserObject() != null && object.getUserObject() instanceof Geometry) { + Geometry geom = (Geometry) object.getUserObject(); + if ("Floor".equals(geom.getName())) { + return; + } + } + + ragdoll.setRagdollMode(); + + } + + private void setupSinbad(KinematicRagdollControl ragdoll) { + ragdoll.addBoneName("Ulna.L"); + ragdoll.addBoneName("Ulna.R"); + ragdoll.addBoneName("Chest"); + ragdoll.addBoneName("Foot.L"); + ragdoll.addBoneName("Foot.R"); + ragdoll.addBoneName("Hand.R"); + ragdoll.addBoneName("Hand.L"); + ragdoll.addBoneName("Neck"); + ragdoll.addBoneName("Root"); + ragdoll.addBoneName("Stomach"); + ragdoll.addBoneName("Waist"); + ragdoll.addBoneName("Humerus.L"); + ragdoll.addBoneName("Humerus.R"); + ragdoll.addBoneName("Thigh.L"); + ragdoll.addBoneName("Thigh.R"); + ragdoll.addBoneName("Calf.L"); + ragdoll.addBoneName("Calf.R"); + ragdoll.addBoneName("Clavicle.L"); + ragdoll.addBoneName("Clavicle.R"); + + } + float elTime = 0; + boolean forward = true; + AnimControl animControl; + AnimChannel animChannel; + Vector3f direction = new Vector3f(0, 0, 1); + Quaternion rotate = new Quaternion().fromAngleAxis(FastMath.PI / 8, Vector3f.UNIT_Y); + boolean dance = true; + + @Override + public void simpleUpdate(float tpf) { + // System.out.println(((BoundingBox) model.getWorldBound()).getYExtent()); +// elTime += tpf; +// if (elTime > 3) { +// elTime = 0; +// if (dance) { +// rotate.multLocal(direction); +// } +// if (Math.random() > 0.80) { +// dance = true; +// animChannel.setAnim("Dance"); +// } else { +// dance = false; +// animChannel.setAnim("RunBase"); +// rotate.fromAngleAxis(FastMath.QUARTER_PI * ((float) Math.random() - 0.5f), Vector3f.UNIT_Y); +// rotate.multLocal(direction); +// } +// } +// if (!ragdoll.hasControl() && !dance) { +// if (model.getLocalTranslation().getZ() < -10) { +// direction.z = 1; +// direction.normalizeLocal(); +// } else if (model.getLocalTranslation().getZ() > 10) { +// direction.z = -1; +// direction.normalizeLocal(); +// } +// if (model.getLocalTranslation().getX() < -10) { +// direction.x = 1; +// direction.normalizeLocal(); +// } else if (model.getLocalTranslation().getX() > 10) { +// direction.x = -1; +// direction.normalizeLocal(); +// } +// model.move(direction.multLocal(tpf * 8)); +// direction.normalizeLocal(); +// model.lookAt(model.getLocalTranslation().add(direction), Vector3f.UNIT_Y); +// } + } + + public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { +// if(channel.getAnimationName().equals("StandUpFront")){ +// channel.setAnim("Dance"); +// } + + if (channel.getAnimationName().equals("StandUpBack") || channel.getAnimationName().equals("StandUpFront")) { + channel.setLoopMode(LoopMode.DontLoop); + channel.setAnim("IdleTop", 5); + channel.setLoopMode(LoopMode.Loop); + } +// if(channel.getAnimationName().equals("IdleTop")){ +// channel.setAnim("StandUpFront"); +// } + + } + + public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java b/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java new file mode 100644 index 000000000..bcadf623d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.font.BitmapText; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; +import com.jme3.shadow.PssmShadowRenderer; +import com.jme3.shadow.PssmShadowRenderer.CompareMode; +import com.jme3.shadow.PssmShadowRenderer.FilterMode; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; + +/** + * + * @author double1984 (tower mod by atom) + */ +public class TestBrickTower extends SimpleApplication { + + int bricksPerLayer = 8; + int brickLayers = 30; + + static float brickWidth = .75f, brickHeight = .25f, brickDepth = .25f; + float radius = 3f; + float angle = 0; + + + Material mat; + Material mat2; + Material mat3; + PssmShadowRenderer bsr; + private Sphere bullet; + private Box brick; + private SphereCollisionShape bulletCollisionShape; + + private BulletAppState bulletAppState; + + public static void main(String args[]) { + TestBrickTower f = new TestBrickTower(); + f.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); + // bulletAppState.setEnabled(false); + stateManager.attach(bulletAppState); + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(0.4f); + + brick = new Box(Vector3f.ZERO, brickWidth, brickHeight, brickDepth); + brick.scaleTextureCoordinates(new Vector2f(1f, .5f)); + //bulletAppState.getPhysicsSpace().enableDebug(assetManager); + initMaterial(); + initTower(); + initFloor(); + initCrossHairs(); + this.cam.setLocation(new Vector3f(0, 25f, 8f)); + cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0)); + cam.setFrustumFar(80); + inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(actionListener, "shoot"); + rootNode.setShadowMode(ShadowMode.Off); + bsr = new PssmShadowRenderer(assetManager, 1024, 2); + bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + bsr.setLambda(0.55f); + bsr.setShadowIntensity(0.6f); + bsr.setCompareMode(CompareMode.Hardware); + bsr.setFilterMode(FilterMode.PCF4); + viewPort.addProcessor(bsr); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + private ActionListener actionListener = new ActionListener() { + + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("shoot") && !keyPressed) { + Geometry bulletg = new Geometry("bullet", bullet); + bulletg.setMaterial(mat2); + bulletg.setShadowMode(ShadowMode.CastAndReceive); + bulletg.setLocalTranslation(cam.getLocation()); + RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1); +// RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1); + bulletNode.setLinearVelocity(cam.getDirection().mult(25)); + bulletg.addControl(bulletNode); + rootNode.attachChild(bulletg); + getPhysicsSpace().add(bulletNode); + } + } + }; + + public void initTower() { + double tempX = 0; + double tempY = 0; + double tempZ = 0; + angle = 0f; + for (int i = 0; i < brickLayers; i++){ + // Increment rows + if(i!=0) + tempY+=brickHeight*2; + else + tempY=brickHeight; + // Alternate brick seams + angle = 360.0f / bricksPerLayer * i/2f; + for (int j = 0; j < bricksPerLayer; j++){ + tempZ = Math.cos(Math.toRadians(angle))*radius; + tempX = Math.sin(Math.toRadians(angle))*radius; + System.out.println("x="+((float)(tempX))+" y="+((float)(tempY))+" z="+(float)(tempZ)); + Vector3f vt = new Vector3f((float)(tempX), (float)(tempY), (float)(tempZ)); + // Add crenelation + if (i==brickLayers-1){ + if (j%2 == 0){ + addBrick(vt); + } + } + // Create main tower + else { + addBrick(vt); + } + angle += 360.0/bricksPerLayer; + } + } + + } + + public void initFloor() { + Box floorBox = new Box(Vector3f.ZERO, 10f, 0.1f, 5f); + floorBox.scaleTextureCoordinates(new Vector2f(3, 6)); + + Geometry floor = new Geometry("floor", floorBox); + floor.setMaterial(mat3); + floor.setShadowMode(ShadowMode.Receive); + floor.setLocalTranslation(0, 0, 0); + floor.addControl(new RigidBodyControl(0)); + this.rootNode.attachChild(floor); + this.getPhysicsSpace().add(floor); + } + + public void initMaterial() { + mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg"); + key.setGenerateMips(true); + Texture tex = assetManager.loadTexture(key); + mat.setTexture("ColorMap", tex); + + mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG"); + key2.setGenerateMips(true); + Texture tex2 = assetManager.loadTexture(key2); + mat2.setTexture("ColorMap", tex2); + + mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg"); + key3.setGenerateMips(true); + Texture tex3 = assetManager.loadTexture(key3); + tex3.setWrap(WrapMode.Repeat); + mat3.setTexture("ColorMap", tex3); + } + + public void addBrick(Vector3f ori) { + Geometry reBoxg = new Geometry("brick", brick); + reBoxg.setMaterial(mat); + reBoxg.setLocalTranslation(ori); + reBoxg.rotate(0f, (float)Math.toRadians(angle) , 0f ); + reBoxg.addControl(new RigidBodyControl(1.5f)); + reBoxg.setShadowMode(ShadowMode.CastAndReceive); + reBoxg.getControl(RigidBodyControl.class).setFriction(1.6f); + this.rootNode.attachChild(reBoxg); + this.getPhysicsSpace().add(reBoxg); + } + + protected void initCrossHairs() { + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText ch = new BitmapText(guiFont, false); + ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); + ch.setText("+"); // crosshairs + ch.setLocalTranslation( // center + settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2, + settings.getHeight() / 2 + ch.getLineHeight() / 2, 0); + guiNode.attachChild(ch); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestBrickWall.java b/jme3-examples/src/main/java/jme3test/bullet/TestBrickWall.java new file mode 100644 index 000000000..b190e8c6c --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestBrickWall.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; +import com.jme3.shadow.BasicShadowRenderer; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; + +/** + * + * @author double1984 + */ +public class TestBrickWall extends SimpleApplication { + + static float bLength = 0.48f; + static float bWidth = 0.24f; + static float bHeight = 0.12f; + Material mat; + Material mat2; + Material mat3; + BasicShadowRenderer bsr; + private static Sphere bullet; + private static Box brick; + private static SphereCollisionShape bulletCollisionShape; + + private BulletAppState bulletAppState; + + public static void main(String args[]) { + TestBrickWall f = new TestBrickWall(); + f.start(); + } + + @Override + public void simpleInitApp() { + + bulletAppState = new BulletAppState(); + bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); + stateManager.attach(bulletAppState); + + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(0.4f); + brick = new Box(Vector3f.ZERO, bLength, bHeight, bWidth); + brick.scaleTextureCoordinates(new Vector2f(1f, .5f)); + + initMaterial(); + initWall(); + initFloor(); + initCrossHairs(); + this.cam.setLocation(new Vector3f(0, 6f, 6f)); + cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0)); + cam.setFrustumFar(15); + inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(actionListener, "shoot"); + inputManager.addMapping("gc", new KeyTrigger(KeyInput.KEY_X)); + inputManager.addListener(actionListener, "gc"); + + rootNode.setShadowMode(ShadowMode.Off); + bsr = new BasicShadowRenderer(assetManager, 256); + bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + viewPort.addProcessor(bsr); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + private ActionListener actionListener = new ActionListener() { + + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("shoot") && !keyPressed) { + Geometry bulletg = new Geometry("bullet", bullet); + bulletg.setMaterial(mat2); + bulletg.setShadowMode(ShadowMode.CastAndReceive); + bulletg.setLocalTranslation(cam.getLocation()); + + SphereCollisionShape bulletCollisionShape = new SphereCollisionShape(0.4f); + RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1); +// RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1); + bulletNode.setLinearVelocity(cam.getDirection().mult(25)); + bulletg.addControl(bulletNode); + rootNode.attachChild(bulletg); + getPhysicsSpace().add(bulletNode); + } + if (name.equals("gc") && !keyPressed) { + System.gc(); + } + } + }; + + public void initWall() { + float startpt = bLength / 4; + float height = 0; + for (int j = 0; j < 15; j++) { + for (int i = 0; i < 4; i++) { + Vector3f vt = new Vector3f(i * bLength * 2 + startpt, bHeight + height, 0); + addBrick(vt); + } + startpt = -startpt; + height += 2 * bHeight; + } + } + + public void initFloor() { + Box floorBox = new Box(Vector3f.ZERO, 10f, 0.1f, 5f); + floorBox.scaleTextureCoordinates(new Vector2f(3, 6)); + + Geometry floor = new Geometry("floor", floorBox); + floor.setMaterial(mat3); + floor.setShadowMode(ShadowMode.Receive); + floor.setLocalTranslation(0, -0.1f, 0); + floor.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(10f, 0.1f, 5f)), 0)); + this.rootNode.attachChild(floor); + this.getPhysicsSpace().add(floor); + } + + public void initMaterial() { + mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg"); + key.setGenerateMips(true); + Texture tex = assetManager.loadTexture(key); + mat.setTexture("ColorMap", tex); + + mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG"); + key2.setGenerateMips(true); + Texture tex2 = assetManager.loadTexture(key2); + mat2.setTexture("ColorMap", tex2); + + mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg"); + key3.setGenerateMips(true); + Texture tex3 = assetManager.loadTexture(key3); + tex3.setWrap(WrapMode.Repeat); + mat3.setTexture("ColorMap", tex3); + } + + public void addBrick(Vector3f ori) { + + Geometry reBoxg = new Geometry("brick", brick); + reBoxg.setMaterial(mat); + reBoxg.setLocalTranslation(ori); + //for geometry with sphere mesh the physics system automatically uses a sphere collision shape + reBoxg.addControl(new RigidBodyControl(1.5f)); + reBoxg.setShadowMode(ShadowMode.CastAndReceive); + reBoxg.getControl(RigidBodyControl.class).setFriction(0.6f); + this.rootNode.attachChild(reBoxg); + this.getPhysicsSpace().add(reBoxg); + } + + protected void initCrossHairs() { + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText ch = new BitmapText(guiFont, false); + ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); + ch.setText("+"); // crosshairs + ch.setLocalTranslation( // center + settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2, + settings.getHeight() / 2 + ch.getLineHeight() / 2, 0); + guiNode.attachChild(ch); + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java b/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java new file mode 100644 index 000000000..beb57bdec --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; + +/** + * + * @author normenhansen + */ +public class TestCcd extends SimpleApplication implements ActionListener { + + private Material mat; + private Material mat2; + private Sphere bullet; + private SphereCollisionShape bulletCollisionShape; + private BulletAppState bulletAppState; + + public static void main(String[] args) { + TestCcd app = new TestCcd(); + app.start(); + } + + private void setupKeys() { + inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addMapping("shoot2", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); + inputManager.addListener(this, "shoot"); + inputManager.addListener(this, "shoot2"); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(0.1f); + setupKeys(); + + mat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + mat.setColor("Color", ColorRGBA.Green); + + mat2 = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + mat2.getAdditionalRenderState().setWireframe(true); + mat2.setColor("Color", ColorRGBA.Red); + + // An obstacle mesh, does not move (mass=0) + Node node2 = new Node(); + node2.setName("mesh"); + node2.setLocalTranslation(new Vector3f(2.5f, 0, 0f)); + node2.addControl(new RigidBodyControl(new MeshCollisionShape(new Box(Vector3f.ZERO, 4, 4, 0.1f)), 0)); + rootNode.attachChild(node2); + getPhysicsSpace().add(node2); + + // The floor, does not move (mass=0) + Node node3 = new Node(); + node3.setLocalTranslation(new Vector3f(0f, -6, 0f)); + node3.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(100, 1, 100)), 0)); + rootNode.attachChild(node3); + getPhysicsSpace().add(node3); + + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + @Override + public void simpleUpdate(float tpf) { + //TODO: add update code + } + + @Override + public void simpleRender(RenderManager rm) { + //TODO: add render code + } + + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("shoot") && !value) { + Geometry bulletg = new Geometry("bullet", bullet); + bulletg.setMaterial(mat); + bulletg.setName("bullet"); + bulletg.setLocalTranslation(cam.getLocation()); + bulletg.setShadowMode(ShadowMode.CastAndReceive); + bulletg.addControl(new RigidBodyControl(bulletCollisionShape, 1)); + bulletg.getControl(RigidBodyControl.class).setCcdMotionThreshold(0.1f); + bulletg.getControl(RigidBodyControl.class).setLinearVelocity(cam.getDirection().mult(40)); + rootNode.attachChild(bulletg); + getPhysicsSpace().add(bulletg); + } else if (binding.equals("shoot2") && !value) { + Geometry bulletg = new Geometry("bullet", bullet); + bulletg.setMaterial(mat2); + bulletg.setName("bullet"); + bulletg.setLocalTranslation(cam.getLocation()); + bulletg.setShadowMode(ShadowMode.CastAndReceive); + bulletg.addControl(new RigidBodyControl(bulletCollisionShape, 1)); + bulletg.getControl(RigidBodyControl.class).setLinearVelocity(cam.getDirection().mult(40)); + rootNode.attachChild(bulletg); + getPhysicsSpace().add(bulletg); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionGroups.java b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionGroups.java new file mode 100644 index 000000000..0bfa9f784 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionGroups.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; + +/** + * + * @author normenhansen + */ +public class TestCollisionGroups extends SimpleApplication { + + private BulletAppState bulletAppState; + + public static void main(String[] args) { + TestCollisionGroups app = new TestCollisionGroups(); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + + // Add a physics sphere to the world + Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); + physicsSphere.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3, 6, 0)); + rootNode.attachChild(physicsSphere); + getPhysicsSpace().add(physicsSphere); + + // Add a physics sphere to the world + Node physicsSphere2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); + physicsSphere2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(4, 8, 0)); + physicsSphere2.getControl(RigidBodyControl.class).addCollideWithGroup(PhysicsCollisionObject.COLLISION_GROUP_02); + rootNode.attachChild(physicsSphere2); + getPhysicsSpace().add(physicsSphere2); + + // an obstacle mesh, does not move (mass=0) + Node node2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Sphere(16, 16, 1.2f)), 0); + node2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2.5f, -4, 0f)); + node2.getControl(RigidBodyControl.class).setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02); + node2.getControl(RigidBodyControl.class).setCollideWithGroups(PhysicsCollisionObject.COLLISION_GROUP_02); + rootNode.attachChild(node2); + getPhysicsSpace().add(node2); + + // the floor, does not move (mass=0) + Node node3 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Box(Vector3f.ZERO, 100f, 0.2f, 100f)), 0); + node3.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -6, 0f)); + rootNode.attachChild(node3); + getPhysicsSpace().add(node3); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + @Override + public void simpleUpdate(float tpf) { + //TODO: add update code + } + + @Override + public void simpleRender(RenderManager rm) { + //TODO: add render code + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java new file mode 100644 index 000000000..1a13b09fb --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionEvent; +import com.jme3.bullet.collision.PhysicsCollisionListener; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; + +/** + * + * @author normenhansen + */ +public class TestCollisionListener extends SimpleApplication implements PhysicsCollisionListener { + + private BulletAppState bulletAppState; + private Sphere bullet; + private SphereCollisionShape bulletCollisionShape; + + public static void main(String[] args) { + TestCollisionListener app = new TestCollisionListener(); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(0.4f); + + PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); + PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace()); + + // add ourselves as collision listener + getPhysicsSpace().addCollisionListener(this); + } + + private PhysicsSpace getPhysicsSpace(){ + return bulletAppState.getPhysicsSpace(); + } + + @Override + public void simpleUpdate(float tpf) { + //TODO: add update code + } + + @Override + public void simpleRender(RenderManager rm) { + //TODO: add render code + } + + public void collision(PhysicsCollisionEvent event) { + if ("Box".equals(event.getNodeA().getName()) || "Box".equals(event.getNodeB().getName())) { + if ("bullet".equals(event.getNodeA().getName()) || "bullet".equals(event.getNodeB().getName())) { + fpsText.setText("You hit the box!"); + } + } + } + +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionShapeFactory.java b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionShapeFactory.java new file mode 100644 index 000000000..b2c44ce4e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionShapeFactory.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Cylinder; +import com.jme3.scene.shape.Torus; + +/** + * This is a basic Test of jbullet-jme functions + * + * @author normenhansen + */ +public class TestCollisionShapeFactory extends SimpleApplication { + + private BulletAppState bulletAppState; + private Material mat1; + private Material mat2; + private Material mat3; + + public static void main(String[] args) { + TestCollisionShapeFactory app = new TestCollisionShapeFactory(); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + createMaterial(); + + Node node = new Node("node1"); + attachRandomGeometry(node, mat1); + randomizeTransform(node); + + Node node2 = new Node("node2"); + attachRandomGeometry(node2, mat2); + randomizeTransform(node2); + + node.attachChild(node2); + rootNode.attachChild(node); + + RigidBodyControl control = new RigidBodyControl(0); + node.addControl(control); + getPhysicsSpace().add(control); + + //test single geometry too + Geometry myGeom = new Geometry("cylinder", new Cylinder(16, 16, 0.5f, 1)); + myGeom.setMaterial(mat3); + randomizeTransform(myGeom); + rootNode.attachChild(myGeom); + RigidBodyControl control3 = new RigidBodyControl(0); + myGeom.addControl(control3); + getPhysicsSpace().add(control3); + } + + private void attachRandomGeometry(Node node, Material mat) { + Box box = new Box(0.25f, 0.25f, 0.25f); + Torus torus = new Torus(16, 16, 0.2f, 0.8f); + Geometry[] boxes = new Geometry[]{ + new Geometry("box1", box), + new Geometry("box2", box), + new Geometry("box3", box), + new Geometry("torus1", torus), + new Geometry("torus2", torus), + new Geometry("torus3", torus) + }; + for (int i = 0; i < boxes.length; i++) { + Geometry geometry = boxes[i]; + geometry.setLocalTranslation((float) Math.random() * 10 -10, (float) Math.random() * 10 -10, (float) Math.random() * 10 -10); + geometry.setLocalRotation(new Quaternion().fromAngles((float) Math.random() * FastMath.PI, (float) Math.random() * FastMath.PI, (float) Math.random() * FastMath.PI)); + geometry.setLocalScale((float) Math.random() * 10 -10, (float) Math.random() * 10 -10, (float) Math.random() * 10 -10); + geometry.setMaterial(mat); + node.attachChild(geometry); + } + } + + private void randomizeTransform(Spatial spat){ + spat.setLocalTranslation((float) Math.random() * 10, (float) Math.random() * 10, (float) Math.random() * 10); + spat.setLocalTranslation((float) Math.random() * 10, (float) Math.random() * 10, (float) Math.random() * 10); + spat.setLocalScale((float) Math.random() * 2, (float) Math.random() * 2, (float) Math.random() * 2); + } + + private void createMaterial() { + mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat1.setColor("Color", ColorRGBA.Green); + mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat2.setColor("Color", ColorRGBA.Red); + mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat3.setColor("Color", ColorRGBA.Yellow); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestFancyCar.java b/jme3-examples/src/main/java/jme3test/bullet/TestFancyCar.java new file mode 100644 index 000000000..6249f601b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestFancyCar.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.VehicleControl; +import com.jme3.bullet.objects.VehicleWheel; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.BasicShadowRenderer; + +public class TestFancyCar extends SimpleApplication implements ActionListener { + + private BulletAppState bulletAppState; + private VehicleControl player; + private VehicleWheel fr, fl, br, bl; + private Node node_fr, node_fl, node_br, node_bl; + private float wheelRadius; + private float steeringValue = 0; + private float accelerationValue = 0; + private Node carNode; + + public static void main(String[] args) { + TestFancyCar app = new TestFancyCar(); + app.start(); + } + + private void setupKeys() { + inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_H)); + inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K)); + inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addListener(this, "Lefts"); + inputManager.addListener(this, "Rights"); + inputManager.addListener(this, "Ups"); + inputManager.addListener(this, "Downs"); + inputManager.addListener(this, "Space"); + inputManager.addListener(this, "Reset"); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); +// bulletAppState.getPhysicsSpace().enableDebug(assetManager); + if (settings.getRenderer().startsWith("LWJGL")) { + BasicShadowRenderer bsr = new BasicShadowRenderer(assetManager, 512); + bsr.setDirection(new Vector3f(-0.5f, -0.3f, -0.3f).normalizeLocal()); + viewPort.addProcessor(bsr); + } + cam.setFrustumFar(150f); + flyCam.setMoveSpeed(10); + + setupKeys(); + PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); +// setupFloor(); + buildPlayer(); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.5f, -1f, -0.3f).normalizeLocal()); + rootNode.addLight(dl); + + dl = new DirectionalLight(); + dl.setDirection(new Vector3f(0.5f, -0.1f, 0.3f).normalizeLocal()); + rootNode.addLight(dl); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + +// public void setupFloor() { +// Material mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"); +// mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat); +//// mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat); +//// mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat); +// +// Box floor = new Box(Vector3f.ZERO, 140, 1f, 140); +// floor.scaleTextureCoordinates(new Vector2f(112.0f, 112.0f)); +// Geometry floorGeom = new Geometry("Floor", floor); +// floorGeom.setShadowMode(ShadowMode.Receive); +// floorGeom.setMaterial(mat); +// +// PhysicsNode tb = new PhysicsNode(floorGeom, new MeshCollisionShape(floorGeom.getMesh()), 0); +// tb.setLocalTranslation(new Vector3f(0f, -6, 0f)); +//// tb.attachDebugShape(assetManager); +// rootNode.attachChild(tb); +// getPhysicsSpace().add(tb); +// } + + private Geometry findGeom(Spatial spatial, String name) { + if (spatial instanceof Node) { + Node node = (Node) spatial; + for (int i = 0; i < node.getQuantity(); i++) { + Spatial child = node.getChild(i); + Geometry result = findGeom(child, name); + if (result != null) { + return result; + } + } + } else if (spatial instanceof Geometry) { + if (spatial.getName().startsWith(name)) { + return (Geometry) spatial; + } + } + return null; + } + + private void buildPlayer() { + float stiffness = 120.0f;//200=f1 car + float compValue = 0.2f; //(lower than damp!) + float dampValue = 0.3f; + final float mass = 400; + + //Load model and get chassis Geometry + carNode = (Node)assetManager.loadModel("Models/Ferrari/Car.scene"); + carNode.setShadowMode(ShadowMode.Cast); + Geometry chasis = findGeom(carNode, "Car"); + BoundingBox box = (BoundingBox) chasis.getModelBound(); + + //Create a hull collision shape for the chassis + CollisionShape carHull = CollisionShapeFactory.createDynamicMeshShape(chasis); + + //Create a vehicle control + player = new VehicleControl(carHull, mass); + carNode.addControl(player); + + //Setting default values for wheels + player.setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness)); + player.setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness)); + player.setSuspensionStiffness(stiffness); + player.setMaxSuspensionForce(10000); + + //Create four wheels and add them at their locations + //note that our fancy car actually goes backwards.. + Vector3f wheelDirection = new Vector3f(0, -1, 0); + Vector3f wheelAxle = new Vector3f(-1, 0, 0); + + Geometry wheel_fr = findGeom(carNode, "WheelFrontRight"); + wheel_fr.center(); + box = (BoundingBox) wheel_fr.getModelBound(); + wheelRadius = box.getYExtent(); + float back_wheel_h = (wheelRadius * 1.7f) - 1f; + float front_wheel_h = (wheelRadius * 1.9f) - 1f; + player.addWheel(wheel_fr.getParent(), box.getCenter().add(0, -front_wheel_h, 0), + wheelDirection, wheelAxle, 0.2f, wheelRadius, true); + + Geometry wheel_fl = findGeom(carNode, "WheelFrontLeft"); + wheel_fl.center(); + box = (BoundingBox) wheel_fl.getModelBound(); + player.addWheel(wheel_fl.getParent(), box.getCenter().add(0, -front_wheel_h, 0), + wheelDirection, wheelAxle, 0.2f, wheelRadius, true); + + Geometry wheel_br = findGeom(carNode, "WheelBackRight"); + wheel_br.center(); + box = (BoundingBox) wheel_br.getModelBound(); + player.addWheel(wheel_br.getParent(), box.getCenter().add(0, -back_wheel_h, 0), + wheelDirection, wheelAxle, 0.2f, wheelRadius, false); + + Geometry wheel_bl = findGeom(carNode, "WheelBackLeft"); + wheel_bl.center(); + box = (BoundingBox) wheel_bl.getModelBound(); + player.addWheel(wheel_bl.getParent(), box.getCenter().add(0, -back_wheel_h, 0), + wheelDirection, wheelAxle, 0.2f, wheelRadius, false); + + player.getWheel(2).setFrictionSlip(4); + player.getWheel(3).setFrictionSlip(4); + + rootNode.attachChild(carNode); + getPhysicsSpace().add(player); + } + + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("Lefts")) { + if (value) { + steeringValue += .5f; + } else { + steeringValue += -.5f; + } + player.steer(steeringValue); + } else if (binding.equals("Rights")) { + if (value) { + steeringValue += -.5f; + } else { + steeringValue += .5f; + } + player.steer(steeringValue); + } //note that our fancy car actually goes backwards.. + else if (binding.equals("Ups")) { + if (value) { + accelerationValue -= 800; + } else { + accelerationValue += 800; + } + player.accelerate(accelerationValue); + player.setCollisionShape(CollisionShapeFactory.createDynamicMeshShape(findGeom(carNode, "Car"))); + } else if (binding.equals("Downs")) { + if (value) { + player.brake(40f); + } else { + player.brake(0f); + } + } else if (binding.equals("Reset")) { + if (value) { + System.out.println("Reset"); + player.setPhysicsLocation(Vector3f.ZERO); + player.setPhysicsRotation(new Matrix3f()); + player.setLinearVelocity(Vector3f.ZERO); + player.setAngularVelocity(Vector3f.ZERO); + player.resetSuspension(); + } else { + } + } + } + + @Override + public void simpleUpdate(float tpf) { + cam.lookAt(carNode.getWorldTranslation(), Vector3f.UNIT_Y); + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestGhostObject.java b/jme3-examples/src/main/java/jme3test/bullet/TestGhostObject.java new file mode 100644 index 000000000..dde9e530c --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestGhostObject.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.bullet; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.GhostControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; + +/** + * + * @author tim8dev [at] gmail [dot com] + */ +public class TestGhostObject extends SimpleApplication { + + private BulletAppState bulletAppState; + private GhostControl ghostControl; + + public static void main(String[] args) { + Application app = new TestGhostObject(); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + + // Mesh to be shared across several boxes. + Box boxGeom = new Box(Vector3f.ZERO, 1f, 1f, 1f); + // CollisionShape to be shared across several boxes. + CollisionShape shape = new BoxCollisionShape(new Vector3f(1, 1, 1)); + + Node physicsBox = PhysicsTestHelper.createPhysicsTestNode(assetManager, shape, 1); + physicsBox.setName("box0"); + physicsBox.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(.6f, 4, .5f)); + rootNode.attachChild(physicsBox); + getPhysicsSpace().add(physicsBox); + + Node physicsBox1 = PhysicsTestHelper.createPhysicsTestNode(assetManager, shape, 1); + physicsBox1.setName("box1"); + physicsBox1.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0, 40, 0)); + rootNode.attachChild(physicsBox1); + getPhysicsSpace().add(physicsBox1); + + Node physicsBox2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(1, 1, 1)), 1); + physicsBox2.setName("box0"); + physicsBox2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(.5f, 80, -.8f)); + rootNode.attachChild(physicsBox2); + getPhysicsSpace().add(physicsBox2); + + // the floor, does not move (mass=0) + Node node = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(100, 1, 100)), 0); + node.setName("floor"); + node.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -6, 0f)); + rootNode.attachChild(node); + getPhysicsSpace().add(node); + + initGhostObject(); + } + + private PhysicsSpace getPhysicsSpace(){ + return bulletAppState.getPhysicsSpace(); + } + + private void initGhostObject() { + Vector3f halfExtents = new Vector3f(3, 4.2f, 1); + ghostControl = new GhostControl(new BoxCollisionShape(halfExtents)); + Node node=new Node("Ghost Object"); + node.addControl(ghostControl); + rootNode.attachChild(node); + getPhysicsSpace().add(ghostControl); + } + + @Override + public void simpleUpdate(float tpf) { + fpsText.setText("Overlapping objects: " + ghostControl.getOverlappingObjects().toString()); + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java b/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java new file mode 100644 index 000000000..2dcf95b4b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.font.BitmapText; +import com.jme3.input.ChaseCamera; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.shadow.PssmShadowRenderer; +import com.jme3.shadow.PssmShadowRenderer.CompareMode; +import com.jme3.shadow.PssmShadowRenderer.FilterMode; +import com.jme3.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; + +public class TestHoveringTank extends SimpleApplication implements AnalogListener, + ActionListener { + + private BulletAppState bulletAppState; + private PhysicsHoverControl hoverControl; + private Spatial spaceCraft; + TerrainQuad terrain; + Material matRock; + boolean wireframe = false; + protected BitmapText hintText; + PointLight pl; + Geometry lightMdl; + Geometry collisionMarker; + + public static void main(String[] args) { + TestHoveringTank app = new TestHoveringTank(); + AppSettings settings = new AppSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL3); + app.start(); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + private void setupKeys() { + inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A)); + inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D)); + inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S)); + inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addListener(this, "Lefts"); + inputManager.addListener(this, "Rights"); + inputManager.addListener(this, "Ups"); + inputManager.addListener(this, "Downs"); + inputManager.addListener(this, "Space"); + inputManager.addListener(this, "Reset"); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); + stateManager.attach(bulletAppState); +// bulletAppState.getPhysicsSpace().enableDebug(assetManager); + bulletAppState.getPhysicsSpace().setAccuracy(1f/30f); + rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false)); + + PssmShadowRenderer pssmr = new PssmShadowRenderer(assetManager, 2048, 3); + pssmr.setDirection(new Vector3f(-0.5f, -0.3f, -0.3f).normalizeLocal()); + pssmr.setLambda(0.55f); + pssmr.setShadowIntensity(0.6f); + pssmr.setCompareMode(CompareMode.Hardware); + pssmr.setFilterMode(FilterMode.Bilinear); + viewPort.addProcessor(pssmr); + + setupKeys(); + createTerrain(); + buildPlayer(); + + DirectionalLight dl = new DirectionalLight(); + dl.setColor(new ColorRGBA(1.0f, 0.94f, 0.8f, 1f).multLocal(1.3f)); + dl.setDirection(new Vector3f(-0.5f, -0.3f, -0.3f).normalizeLocal()); + rootNode.addLight(dl); + + Vector3f lightDir2 = new Vector3f(0.70518064f, 0.5902297f, -0.39287305f); + DirectionalLight dl2 = new DirectionalLight(); + dl2.setColor(new ColorRGBA(0.7f, 0.85f, 1.0f, 1f)); + dl2.setDirection(lightDir2); + rootNode.addLight(dl2); + } + + private void buildPlayer() { + spaceCraft = assetManager.loadModel("Models/HoverTank/Tank2.mesh.xml"); + CollisionShape colShape = CollisionShapeFactory.createDynamicMeshShape(spaceCraft); + spaceCraft.setShadowMode(ShadowMode.CastAndReceive); + spaceCraft.setLocalTranslation(new Vector3f(-140, 14, -23)); + spaceCraft.setLocalRotation(new Quaternion(new float[]{0, 0.01f, 0})); + + hoverControl = new PhysicsHoverControl(colShape, 500); + hoverControl.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02); + + spaceCraft.addControl(hoverControl); + + + rootNode.attachChild(spaceCraft); + getPhysicsSpace().add(hoverControl); + + ChaseCamera chaseCam = new ChaseCamera(cam, inputManager); + spaceCraft.addControl(chaseCam); + + flyCam.setEnabled(false); + } + + public void makeMissile() { + Vector3f pos = spaceCraft.getWorldTranslation().clone(); + Quaternion rot = spaceCraft.getWorldRotation(); + Vector3f dir = rot.getRotationColumn(2); + + Spatial missile = assetManager.loadModel("Models/SpaceCraft/Rocket.mesh.xml"); + missile.scale(0.5f); + missile.rotate(0, FastMath.PI, 0); + missile.updateGeometricState(); + + BoundingBox box = (BoundingBox) missile.getWorldBound(); + final Vector3f extent = box.getExtent(null); + + BoxCollisionShape boxShape = new BoxCollisionShape(extent); + + missile.setName("Missile"); + missile.rotate(rot); + missile.setLocalTranslation(pos.addLocal(0, extent.y * 4.5f, 0)); + missile.setLocalRotation(hoverControl.getPhysicsRotation()); + missile.setShadowMode(ShadowMode.Cast); + RigidBodyControl control = new BombControl(assetManager, boxShape, 20); + control.setLinearVelocity(dir.mult(100)); + control.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_03); + missile.addControl(control); + + + rootNode.attachChild(missile); + getPhysicsSpace().add(missile); + } + + public void onAnalog(String binding, float value, float tpf) { + } + + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("Lefts")) { + hoverControl.steer(value ? 50f : 0); + } else if (binding.equals("Rights")) { + hoverControl.steer(value ? -50f : 0); + } else if (binding.equals("Ups")) { + hoverControl.accelerate(value ? 100f : 0); + } else if (binding.equals("Downs")) { + hoverControl.accelerate(value ? -100f : 0); + } else if (binding.equals("Reset")) { + if (value) { + System.out.println("Reset"); + hoverControl.setPhysicsLocation(new Vector3f(-140, 14, -23)); + hoverControl.setPhysicsRotation(new Matrix3f()); + hoverControl.clearForces(); + } else { + } + } else if (binding.equals("Space") && value) { + makeMissile(); + } + } + + public void updateCamera() { + rootNode.updateGeometricState(); + + Vector3f pos = spaceCraft.getWorldTranslation().clone(); + Quaternion rot = spaceCraft.getWorldRotation(); + Vector3f dir = rot.getRotationColumn(2); + + // make it XZ only + Vector3f camPos = new Vector3f(dir); + camPos.setY(0); + camPos.normalizeLocal(); + + // negate and multiply by distance from object + camPos.negateLocal(); + camPos.multLocal(15); + + // add Y distance + camPos.setY(2); + camPos.addLocal(pos); + cam.setLocation(camPos); + + Vector3f lookAt = new Vector3f(dir); + lookAt.multLocal(7); // look at dist + lookAt.addLocal(pos); + cam.lookAt(lookAt, Vector3f.UNIT_Y); + } + + @Override + public void simpleUpdate(float tpf) { + } + + private void createTerrain() { + matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matRock.setBoolean("useTriPlanarMapping", false); + matRock.setBoolean("WardIso", true); + matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap", grass); + matRock.setFloat("DiffuseMap_0_scale", 64); + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_1", dirt); + matRock.setFloat("DiffuseMap_1_scale", 16); + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_2", rock); + matRock.setFloat("DiffuseMap_2_scale", 128); + Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); + normalMap0.setWrap(WrapMode.Repeat); + Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); + normalMap1.setWrap(WrapMode.Repeat); + Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); + normalMap2.setWrap(WrapMode.Repeat); + matRock.setTexture("NormalMap", normalMap0); + matRock.setTexture("NormalMap_1", normalMap2); + matRock.setTexture("NormalMap_2", normalMap2); + + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f); + heightmap.load(); + } catch (Exception e) { + e.printStackTrace(); + } + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List 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.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/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java b/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java new file mode 100644 index 000000000..636122b4b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.collision.shapes.PlaneCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.math.Plane; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Sphere; + +/** + * + * @author Nehon + */ +public class TestKinematicAddToPhysicsSpaceIssue extends SimpleApplication { + + public static void main(String[] args) { + TestKinematicAddToPhysicsSpaceIssue app = new TestKinematicAddToPhysicsSpaceIssue(); + app.start(); + } + BulletAppState bulletAppState; + + @Override + public void simpleInitApp() { + + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + // Add a physics sphere to the world + Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); + physicsSphere.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3, 6, 0)); + rootNode.attachChild(physicsSphere); + + //Setting the rigidBody to kinematic before adding it to the physic space + physicsSphere.getControl(RigidBodyControl.class).setKinematic(true); + //adding it to the physic space + getPhysicsSpace().add(physicsSphere); + //Making it not kinematic again, it should fall under gravity, it doesn't + physicsSphere.getControl(RigidBodyControl.class).setKinematic(false); + + // Add a physics sphere to the world using the collision shape from sphere one + Node physicsSphere2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); + physicsSphere2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(5, 6, 0)); + rootNode.attachChild(physicsSphere2); + + //Adding the rigid body to physic space + getPhysicsSpace().add(physicsSphere2); + //making it kinematic + physicsSphere2.getControl(RigidBodyControl.class).setKinematic(false); + //Making it not kinematic again, it works properly, the rigidbody is affected by grvity. + physicsSphere2.getControl(RigidBodyControl.class).setKinematic(false); + + + + // an obstacle mesh, does not move (mass=0) + Node node2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Sphere(16, 16, 1.2f)), 0); + node2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2.5f, -4, 0f)); + rootNode.attachChild(node2); + getPhysicsSpace().add(node2); + + // the floor mesh, does not move (mass=0) + Node node3 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new PlaneCollisionShape(new Plane(new Vector3f(0, 1, 0), 0)), 0); + node3.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -6, 0f)); + rootNode.attachChild(node3); + getPhysicsSpace().add(node3); + + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestLocalPhysics.java b/jme3-examples/src/main/java/jme3test/bullet/TestLocalPhysics.java new file mode 100644 index 000000000..ed432da47 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestLocalPhysics.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.*; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.math.Plane; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Sphere; + +/** + * This is a basic Test of jbullet-jme functions + * + * @author normenhansen + */ +public class TestLocalPhysics extends SimpleApplication { + + private BulletAppState bulletAppState; + + public static void main(String[] args) { + TestLocalPhysics app = new TestLocalPhysics(); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + + // Add a physics sphere to the world + Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); + physicsSphere.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3, 6, 0)); + physicsSphere.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true); + rootNode.attachChild(physicsSphere); + getPhysicsSpace().add(physicsSphere); + + // Add a physics sphere to the world using the collision shape from sphere one + Node physicsSphere2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, physicsSphere.getControl(RigidBodyControl.class).getCollisionShape(), 1); + physicsSphere2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(4, 8, 0)); + physicsSphere2.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true); + rootNode.attachChild(physicsSphere2); + getPhysicsSpace().add(physicsSphere2); + + // Add a physics box to the world + Node physicsBox = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(1, 1, 1)), 1); + physicsBox.getControl(RigidBodyControl.class).setFriction(0.1f); + physicsBox.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(.6f, 4, .5f)); + physicsBox.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true); + rootNode.attachChild(physicsBox); + getPhysicsSpace().add(physicsBox); + + // Add a physics cylinder to the world + Node physicsCylinder = PhysicsTestHelper.createPhysicsTestNode(assetManager, new CylinderCollisionShape(new Vector3f(1f, 1f, 1.5f)), 1); + physicsCylinder.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2, 2, 0)); + physicsCylinder.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true); + rootNode.attachChild(physicsCylinder); + getPhysicsSpace().add(physicsCylinder); + + // an obstacle mesh, does not move (mass=0) + Node node2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Sphere(16, 16, 1.2f)), 0); + node2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2.5f, -4, 0f)); + node2.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true); + rootNode.attachChild(node2); + getPhysicsSpace().add(node2); + + // the floor mesh, does not move (mass=0) + Node node3 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new PlaneCollisionShape(new Plane(new Vector3f(0, 1, 0), 0)), 0); + node3.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -6, 0f)); + node3.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true); + rootNode.attachChild(node3); + getPhysicsSpace().add(node3); + + // Join the physics objects with a Point2Point joint +// PhysicsPoint2PointJoint joint=new PhysicsPoint2PointJoint(physicsSphere, physicsBox, new Vector3f(-2,0,0), new Vector3f(2,0,0)); +// PhysicsHingeJoint joint=new PhysicsHingeJoint(physicsSphere, physicsBox, new Vector3f(-2,0,0), new Vector3f(2,0,0), Vector3f.UNIT_Z,Vector3f.UNIT_Z); +// getPhysicsSpace().add(joint); + + } + + @Override + public void simpleUpdate(float tpf) { + rootNode.rotate(tpf, 0, 0); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java new file mode 100644 index 000000000..26d7c1a8e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.control.VehicleControl; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Cylinder; + +public class TestPhysicsCar extends SimpleApplication implements ActionListener { + + private BulletAppState bulletAppState; + private VehicleControl vehicle; + private final float accelerationForce = 1000.0f; + private final float brakeForce = 100.0f; + private float steeringValue = 0; + private float accelerationValue = 0; + private Vector3f jumpForce = new Vector3f(0, 3000, 0); + + public static void main(String[] args) { + TestPhysicsCar app = new TestPhysicsCar(); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); + setupKeys(); + buildPlayer(); + } + + private PhysicsSpace getPhysicsSpace(){ + return bulletAppState.getPhysicsSpace(); + } + + private void setupKeys() { + inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_H)); + inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K)); + inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addListener(this, "Lefts"); + inputManager.addListener(this, "Rights"); + inputManager.addListener(this, "Ups"); + inputManager.addListener(this, "Downs"); + inputManager.addListener(this, "Space"); + inputManager.addListener(this, "Reset"); + } + + private void buildPlayer() { + Material mat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + mat.setColor("Color", ColorRGBA.Red); + + //create a compound shape and attach the BoxCollisionShape for the car body at 0,1,0 + //this shifts the effective center of mass of the BoxCollisionShape to 0,-1,0 + CompoundCollisionShape compoundShape = new CompoundCollisionShape(); + BoxCollisionShape box = new BoxCollisionShape(new Vector3f(1.2f, 0.5f, 2.4f)); + compoundShape.addChildShape(box, new Vector3f(0, 1, 0)); + + //create vehicle node + Node vehicleNode=new Node("vehicleNode"); + vehicle = new VehicleControl(compoundShape, 400); + vehicleNode.addControl(vehicle); + + //setting suspension values for wheels, this can be a bit tricky + //see also https://docs.google.com/Doc?docid=0AXVUZ5xw6XpKZGNuZG56a3FfMzU0Z2NyZnF4Zmo&hl=en + float stiffness = 60.0f;//200=f1 car + float compValue = .3f; //(should be lower than damp) + float dampValue = .4f; + vehicle.setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness)); + vehicle.setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness)); + vehicle.setSuspensionStiffness(stiffness); + vehicle.setMaxSuspensionForce(10000.0f); + + //Create four wheels and add them at their locations + Vector3f wheelDirection = new Vector3f(0, -1, 0); // was 0, -1, 0 + Vector3f wheelAxle = new Vector3f(-1, 0, 0); // was -1, 0, 0 + float radius = 0.5f; + float restLength = 0.3f; + float yOff = 0.5f; + float xOff = 1f; + float zOff = 2f; + + Cylinder wheelMesh = new Cylinder(16, 16, radius, radius * 0.6f, true); + + Node node1 = new Node("wheel 1 node"); + Geometry wheels1 = new Geometry("wheel 1", wheelMesh); + node1.attachChild(wheels1); + wheels1.rotate(0, FastMath.HALF_PI, 0); + wheels1.setMaterial(mat); + vehicle.addWheel(node1, new Vector3f(-xOff, yOff, zOff), + wheelDirection, wheelAxle, restLength, radius, true); + + Node node2 = new Node("wheel 2 node"); + Geometry wheels2 = new Geometry("wheel 2", wheelMesh); + node2.attachChild(wheels2); + wheels2.rotate(0, FastMath.HALF_PI, 0); + wheels2.setMaterial(mat); + vehicle.addWheel(node2, new Vector3f(xOff, yOff, zOff), + wheelDirection, wheelAxle, restLength, radius, true); + + Node node3 = new Node("wheel 3 node"); + Geometry wheels3 = new Geometry("wheel 3", wheelMesh); + node3.attachChild(wheels3); + wheels3.rotate(0, FastMath.HALF_PI, 0); + wheels3.setMaterial(mat); + vehicle.addWheel(node3, new Vector3f(-xOff, yOff, -zOff), + wheelDirection, wheelAxle, restLength, radius, false); + + Node node4 = new Node("wheel 4 node"); + Geometry wheels4 = new Geometry("wheel 4", wheelMesh); + node4.attachChild(wheels4); + wheels4.rotate(0, FastMath.HALF_PI, 0); + wheels4.setMaterial(mat); + vehicle.addWheel(node4, new Vector3f(xOff, yOff, -zOff), + wheelDirection, wheelAxle, restLength, radius, false); + + vehicleNode.attachChild(node1); + vehicleNode.attachChild(node2); + vehicleNode.attachChild(node3); + vehicleNode.attachChild(node4); + rootNode.attachChild(vehicleNode); + + getPhysicsSpace().add(vehicle); + } + + @Override + public void simpleUpdate(float tpf) { + cam.lookAt(vehicle.getPhysicsLocation(), Vector3f.UNIT_Y); + } + + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("Lefts")) { + if (value) { + steeringValue += .5f; + } else { + steeringValue += -.5f; + } + vehicle.steer(steeringValue); + } else if (binding.equals("Rights")) { + if (value) { + steeringValue += -.5f; + } else { + steeringValue += .5f; + } + vehicle.steer(steeringValue); + } else if (binding.equals("Ups")) { + if (value) { + accelerationValue += accelerationForce; + } else { + accelerationValue -= accelerationForce; + } + vehicle.accelerate(accelerationValue); + } else if (binding.equals("Downs")) { + if (value) { + vehicle.brake(brakeForce); + } else { + vehicle.brake(0f); + } + } else if (binding.equals("Space")) { + if (value) { + vehicle.applyImpulse(jumpForce, Vector3f.ZERO); + } + } else if (binding.equals("Reset")) { + if (value) { + System.out.println("Reset"); + vehicle.setPhysicsLocation(Vector3f.ZERO); + vehicle.setPhysicsRotation(new Matrix3f()); + vehicle.setLinearVelocity(Vector3f.ZERO); + vehicle.setAngularVelocity(Vector3f.ZERO); + vehicle.resetSuspension(); + } else { + } + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCharacter.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCharacter.java new file mode 100644 index 000000000..235f5d291 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCharacter.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved.

      + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer.

      * Redistributions + * in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution.

      * Neither the name of + * 'jMonkeyEngine' nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written + * permission.

      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT + * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.CameraNode; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.CameraControl.ControlDirection; + +/** + * A walking physical character followed by a 3rd person camera. (No animation.) + * @author normenhansen, zathras + */ +public class TestPhysicsCharacter extends SimpleApplication implements ActionListener { + + private BulletAppState bulletAppState; + private CharacterControl physicsCharacter; + private Node characterNode; + private CameraNode camNode; + boolean rotate = false; + private Vector3f walkDirection = new Vector3f(0,0,0); + private Vector3f viewDirection = new Vector3f(0,0,0); + boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, + leftRotate = false, rightRotate = false; + + public static void main(String[] args) { + TestPhysicsCharacter app = new TestPhysicsCharacter(); + app.start(); + } + + private void setupKeys() { + inputManager.addMapping("Strafe Left", + new KeyTrigger(KeyInput.KEY_Q), + new KeyTrigger(KeyInput.KEY_Z)); + inputManager.addMapping("Strafe Right", + new KeyTrigger(KeyInput.KEY_E), + new KeyTrigger(KeyInput.KEY_X)); + inputManager.addMapping("Rotate Left", + new KeyTrigger(KeyInput.KEY_A), + new KeyTrigger(KeyInput.KEY_LEFT)); + inputManager.addMapping("Rotate Right", + new KeyTrigger(KeyInput.KEY_D), + new KeyTrigger(KeyInput.KEY_RIGHT)); + inputManager.addMapping("Walk Forward", + new KeyTrigger(KeyInput.KEY_W), + new KeyTrigger(KeyInput.KEY_UP)); + inputManager.addMapping("Walk Backward", + new KeyTrigger(KeyInput.KEY_S), + new KeyTrigger(KeyInput.KEY_DOWN)); + inputManager.addMapping("Jump", + new KeyTrigger(KeyInput.KEY_SPACE), + new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addMapping("Shoot", + new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(this, "Strafe Left", "Strafe Right"); + inputManager.addListener(this, "Rotate Left", "Rotate Right"); + inputManager.addListener(this, "Walk Forward", "Walk Backward"); + inputManager.addListener(this, "Jump", "Shoot"); + } + @Override + public void simpleInitApp() { + // activate physics + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // init a physical test scene + PhysicsTestHelper.createPhysicsTestWorldSoccer(rootNode, assetManager, bulletAppState.getPhysicsSpace()); + setupKeys(); + + // Add a physics character to the world + physicsCharacter = new CharacterControl(new CapsuleCollisionShape(0.5f, 1.8f), .1f); + physicsCharacter.setPhysicsLocation(new Vector3f(0, 1, 0)); + characterNode = new Node("character node"); + Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); + model.scale(0.25f); + characterNode.addControl(physicsCharacter); + getPhysicsSpace().add(physicsCharacter); + rootNode.attachChild(characterNode); + characterNode.attachChild(model); + + // set forward camera node that follows the character + camNode = new CameraNode("CamNode", cam); + camNode.setControlDir(ControlDirection.SpatialToCamera); + camNode.setLocalTranslation(new Vector3f(0, 1, -5)); + camNode.lookAt(model.getLocalTranslation(), Vector3f.UNIT_Y); + characterNode.attachChild(camNode); + + //disable the default 1st-person flyCam (don't forget this!!) + flyCam.setEnabled(false); + + } + + @Override + public void simpleUpdate(float tpf) { + Vector3f camDir = cam.getDirection().mult(0.2f); + Vector3f camLeft = cam.getLeft().mult(0.2f); + camDir.y = 0; + camLeft.y = 0; + viewDirection.set(camDir); + walkDirection.set(0, 0, 0); + if (leftStrafe) { + walkDirection.addLocal(camLeft); + } else + if (rightStrafe) { + walkDirection.addLocal(camLeft.negate()); + } + if (leftRotate) { + viewDirection.addLocal(camLeft.mult(0.02f)); + } else + if (rightRotate) { + viewDirection.addLocal(camLeft.mult(0.02f).negate()); + } + if (forward) { + walkDirection.addLocal(camDir); + } else + if (backward) { + walkDirection.addLocal(camDir.negate()); + } + physicsCharacter.setWalkDirection(walkDirection); + physicsCharacter.setViewDirection(viewDirection); + } + + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("Strafe Left")) { + if (value) { + leftStrafe = true; + } else { + leftStrafe = false; + } + } else if (binding.equals("Strafe Right")) { + if (value) { + rightStrafe = true; + } else { + rightStrafe = false; + } + } else if (binding.equals("Rotate Left")) { + if (value) { + leftRotate = true; + } else { + leftRotate = false; + } + } else if (binding.equals("Rotate Right")) { + if (value) { + rightRotate = true; + } else { + rightRotate = false; + } + } else if (binding.equals("Walk Forward")) { + if (value) { + forward = true; + } else { + forward = false; + } + } else if (binding.equals("Walk Backward")) { + if (value) { + backward = true; + } else { + backward = false; + } + } else if (binding.equals("Jump")) { + physicsCharacter.jump(); + } + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + @Override + public void simpleRender(RenderManager rm) { + //TODO: add render code + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java new file mode 100644 index 000000000..f01ad23f9 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.joints.HingeJoint; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; + +public class TestPhysicsHingeJoint extends SimpleApplication implements AnalogListener { + private BulletAppState bulletAppState; + private HingeJoint joint; + + public static void main(String[] args) { + TestPhysicsHingeJoint app = new TestPhysicsHingeJoint(); + app.start(); + } + + private void setupKeys() { + inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_H)); + inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_K)); + inputManager.addMapping("Swing", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(this, "Left", "Right", "Swing"); + } + + public void onAnalog(String binding, float value, float tpf) { + if(binding.equals("Left")){ + joint.enableMotor(true, 1, .1f); + } + else if(binding.equals("Right")){ + joint.enableMotor(true, -1, .1f); + } + else if(binding.equals("Swing")){ + joint.enableMotor(false, 0, 0); + } + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + setupKeys(); + setupJoint(); + } + + private PhysicsSpace getPhysicsSpace(){ + return bulletAppState.getPhysicsSpace(); + } + + public void setupJoint() { + Node holderNode=PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .1f, .1f, .1f)),0); + holderNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f,0,0f)); + rootNode.attachChild(holderNode); + getPhysicsSpace().add(holderNode); + + Node hammerNode=PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f( .3f, .3f, .3f)),1); + hammerNode.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f,-1,0f)); + rootNode.attachChild(hammerNode); + getPhysicsSpace().add(hammerNode); + + joint=new HingeJoint(holderNode.getControl(RigidBodyControl.class), hammerNode.getControl(RigidBodyControl.class), Vector3f.ZERO, new Vector3f(0f,-1,0f), Vector3f.UNIT_Z, Vector3f.UNIT_Z); + getPhysicsSpace().add(joint); + } + + @Override + public void simpleUpdate(float tpf) { + + } + + +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java new file mode 100644 index 000000000..c51afd3cc --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java @@ -0,0 +1,60 @@ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.PhysicsRayTestResult; +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.scene.Node; +import com.jme3.scene.Spatial; +import java.util.List; +/** + * + * @author @wezrule + */ +public class TestPhysicsRayCast extends SimpleApplication { + + private BulletAppState bulletAppState = new BulletAppState(); + + public static void main(String[] args) { + new TestPhysicsRayCast().start(); + } + + @Override + public void simpleInitApp() { + stateManager.attach(bulletAppState); + initCrossHair(); + + Spatial s = assetManager.loadModel("Models/Elephant/Elephant.mesh.xml"); + s.setLocalScale(0.1f); + + CollisionShape collisionShape = CollisionShapeFactory.createMeshShape(s); + Node n = new Node("elephant"); + n.addControl(new RigidBodyControl(collisionShape, 1)); + n.getControl(RigidBodyControl.class).setKinematic(true); + bulletAppState.getPhysicsSpace().add(n); + rootNode.attachChild(n); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + } + + @Override + public void simpleUpdate(float tpf) { + List rayTest = bulletAppState.getPhysicsSpace().rayTest(cam.getLocation(), cam.getLocation().add(cam.getDirection())); + if (rayTest.size() > 0) { + PhysicsRayTestResult get = rayTest.get(0); + PhysicsCollisionObject collisionObject = get.getCollisionObject(); + //do stuff + fpsText.setText(collisionObject.getUserObject().toString()); + } + } + + private void initCrossHair() { + BitmapText bitmapText = new BitmapText(guiFont); + bitmapText.setText("+"); + bitmapText.setLocalTranslation((settings.getWidth() - bitmapText.getLineWidth())*0.5f, (settings.getHeight() + bitmapText.getLineHeight())*0.5f, 0); + guiNode.attachChild(bitmapText); + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java new file mode 100644 index 000000000..5724f7a16 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.bullet; + + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.*; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.joints.HingeJoint; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.math.Plane; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Sphere; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This is a basic Test of jbullet-jme functions + * + * @author normenhansen + */ +public class TestPhysicsReadWrite extends SimpleApplication{ + private BulletAppState bulletAppState; + private Node physicsRootNode; + public static void main(String[] args){ + TestPhysicsReadWrite app = new TestPhysicsReadWrite(); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + physicsRootNode=new Node("PhysicsRootNode"); + rootNode.attachChild(physicsRootNode); + + // Add a physics sphere to the world + Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); + physicsSphere.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3, 6, 0)); + rootNode.attachChild(physicsSphere); + getPhysicsSpace().add(physicsSphere); + + // Add a physics sphere to the world using the collision shape from sphere one + Node physicsSphere2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, physicsSphere.getControl(RigidBodyControl.class).getCollisionShape(), 1); + physicsSphere2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(4, 8, 0)); + rootNode.attachChild(physicsSphere2); + getPhysicsSpace().add(physicsSphere2); + + // Add a physics box to the world + Node physicsBox = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(1, 1, 1)), 1); + physicsBox.getControl(RigidBodyControl.class).setFriction(0.1f); + physicsBox.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(.6f, 4, .5f)); + rootNode.attachChild(physicsBox); + getPhysicsSpace().add(physicsBox); + + // Add a physics cylinder to the world + Node physicsCylinder = PhysicsTestHelper.createPhysicsTestNode(assetManager, new CylinderCollisionShape(new Vector3f(1f, 1f, 1.5f)), 1); + physicsCylinder.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2, 2, 0)); + rootNode.attachChild(physicsCylinder); + getPhysicsSpace().add(physicsCylinder); + + // an obstacle mesh, does not move (mass=0) + Node node2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Sphere(16, 16, 1.2f)), 0); + node2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2.5f, -4, 0f)); + rootNode.attachChild(node2); + getPhysicsSpace().add(node2); + + // the floor mesh, does not move (mass=0) + Node node3 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new PlaneCollisionShape(new Plane(new Vector3f(0, 1, 0), 0)), 0); + node3.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -6, 0f)); + rootNode.attachChild(node3); + getPhysicsSpace().add(node3); + + // Join the physics objects with a Point2Point joint + HingeJoint joint=new HingeJoint(physicsSphere.getControl(RigidBodyControl.class), physicsBox.getControl(RigidBodyControl.class), new Vector3f(-2,0,0), new Vector3f(2,0,0), Vector3f.UNIT_Z,Vector3f.UNIT_Z); + getPhysicsSpace().add(joint); + + //save and load the physicsRootNode + try { + //remove all physics objects from physics space + getPhysicsSpace().removeAll(physicsRootNode); + physicsRootNode.removeFromParent(); + //export to byte array + ByteArrayOutputStream bout=new ByteArrayOutputStream(); + BinaryExporter.getInstance().save(physicsRootNode, bout); + //import from byte array + ByteArrayInputStream bin=new ByteArrayInputStream(bout.toByteArray()); + BinaryImporter imp=BinaryImporter.getInstance(); + imp.setAssetManager(assetManager); + Node newPhysicsRootNode=(Node)imp.load(bin); + //add all physics objects to physics space + getPhysicsSpace().addAll(newPhysicsRootNode); + rootNode.attachChild(newPhysicsRootNode); + } catch (IOException ex) { + Logger.getLogger(TestPhysicsReadWrite.class.getName()).log(Level.SEVERE, null, ex); + } + + } + + private PhysicsSpace getPhysicsSpace(){ + return bulletAppState.getPhysicsSpace(); + } + + @Override + public void simpleUpdate(float tpf) { + //TODO: add update code + } + + @Override + public void simpleRender(RenderManager rm) { + //TODO: add render code + } + +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestQ3.java b/jme3-examples/src/main/java/jme3test/bullet/TestQ3.java new file mode 100644 index 000000000..81d9cb7bf --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestQ3.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.HttpZipLocator; +import com.jme3.asset.plugins.ZipLocator; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsCharacter; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.MaterialList; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.plugins.ogre.OgreMeshKey; +import java.io.File; + +public class TestQ3 extends SimpleApplication implements ActionListener { + + private BulletAppState bulletAppState; + private Node gameLevel; + private PhysicsCharacter player; + private Vector3f walkDirection = new Vector3f(); + private static boolean useHttp = false; + private boolean left=false,right=false,up=false,down=false; + + public static void main(String[] args) { + File file = new File("quake3level.zip"); + if (!file.exists()) { + useHttp = true; + } + TestQ3 app = new TestQ3(); + app.start(); + } + + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + flyCam.setMoveSpeed(100); + setupKeys(); + + this.cam.setFrustumFar(2000); + + DirectionalLight dl = new DirectionalLight(); + dl.setColor(ColorRGBA.White.clone().multLocal(2)); + dl.setDirection(new Vector3f(-1, -1, -1).normalize()); + rootNode.addLight(dl); + + AmbientLight am = new AmbientLight(); + am.setColor(ColorRGBA.White.mult(2)); + rootNode.addLight(am); + + // load the level from zip or http zip + if (useHttp) { + assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/quake3level.zip", HttpZipLocator.class); + } else { + assetManager.registerLocator("quake3level.zip", ZipLocator.class); + } + + // 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/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java b/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java new file mode 100644 index 000000000..5768bc021 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.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/jme3-examples/src/main/java/jme3test/bullet/TestRagdollCharacter.java b/jme3-examples/src/main/java/jme3test/bullet/TestRagdollCharacter.java new file mode 100644 index 000000000..9785ed250 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestRagdollCharacter.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.animation.AnimChannel; +import com.jme3.animation.AnimControl; +import com.jme3.animation.AnimEventListener; +import com.jme3.animation.LoopMode; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.control.KinematicRagdollControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.texture.Texture; + +/** + * @author normenhansen + */ +public class TestRagdollCharacter extends SimpleApplication implements AnimEventListener, ActionListener { + + BulletAppState bulletAppState; + Node model; + KinematicRagdollControl ragdoll; + boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, + leftRotate = false, rightRotate = false; + AnimControl animControl; + AnimChannel animChannel; + + public static void main(String[] args) { + TestRagdollCharacter app = new TestRagdollCharacter(); + app.start(); + } + + public void simpleInitApp() { + setupKeys(); + + bulletAppState = new BulletAppState(); + bulletAppState.setEnabled(true); + stateManager.attach(bulletAppState); + + +// bulletAppState.getPhysicsSpace().enableDebug(assetManager); + PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); + initWall(2,1,1); + setupLight(); + + cam.setLocation(new Vector3f(-8,0,-4)); + cam.lookAt(new Vector3f(4,0,-7), Vector3f.UNIT_Y); + + model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); + model.lookAt(new Vector3f(0,0,-1), Vector3f.UNIT_Y); + model.setLocalTranslation(4, 0, -7f); + + ragdoll = new KinematicRagdollControl(0.5f); + model.addControl(ragdoll); + + getPhysicsSpace().add(ragdoll); + speed = 1.3f; + + rootNode.attachChild(model); + + + AnimControl control = model.getControl(AnimControl.class); + animChannel = control.createChannel(); + animChannel.setAnim("IdleTop"); + control.addListener(this); + + } + + private void setupLight() { + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal()); + dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); + rootNode.addLight(dl); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + private void setupKeys() { + inputManager.addMapping("Rotate Left", + new KeyTrigger(KeyInput.KEY_H)); + inputManager.addMapping("Rotate Right", + new KeyTrigger(KeyInput.KEY_K)); + inputManager.addMapping("Walk Forward", + new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("Walk Backward", + new KeyTrigger(KeyInput.KEY_J)); + inputManager.addMapping("Slice", + new KeyTrigger(KeyInput.KEY_SPACE), + new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addListener(this, "Strafe Left", "Strafe Right"); + inputManager.addListener(this, "Rotate Left", "Rotate Right"); + inputManager.addListener(this, "Walk Forward", "Walk Backward"); + inputManager.addListener(this, "Slice"); + } + + public void initWall(float bLength, float bWidth, float bHeight) { + Box brick = new Box(Vector3f.ZERO, bLength, bHeight, bWidth); + brick.scaleTextureCoordinates(new Vector2f(1f, .5f)); + Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg"); + key.setGenerateMips(true); + Texture tex = assetManager.loadTexture(key); + mat2.setTexture("ColorMap", tex); + + float startpt = bLength / 4; + float height = -5; + for (int j = 0; j < 15; j++) { + for (int i = 0; i < 4; i++) { + Vector3f ori = new Vector3f(i * bLength * 2 + startpt, bHeight + height, -10); + Geometry reBoxg = new Geometry("brick", brick); + reBoxg.setMaterial(mat2); + reBoxg.setLocalTranslation(ori); + //for geometry with sphere mesh the physics system automatically uses a sphere collision shape + reBoxg.addControl(new RigidBodyControl(1.5f)); + reBoxg.setShadowMode(ShadowMode.CastAndReceive); + reBoxg.getControl(RigidBodyControl.class).setFriction(0.6f); + this.rootNode.attachChild(reBoxg); + this.getPhysicsSpace().add(reBoxg); + } + startpt = -startpt; + height += 2 * bHeight; + } + } + + public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { + + if (channel.getAnimationName().equals("SliceHorizontal")) { + channel.setLoopMode(LoopMode.DontLoop); + channel.setAnim("IdleTop", 5); + channel.setLoopMode(LoopMode.Loop); + } + + } + + public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { + } + + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("Rotate Left")) { + if (value) { + leftRotate = true; + } else { + leftRotate = false; + } + } else if (binding.equals("Rotate Right")) { + if (value) { + rightRotate = true; + } else { + rightRotate = false; + } + } else if (binding.equals("Walk Forward")) { + if (value) { + forward = true; + } else { + forward = false; + } + } else if (binding.equals("Walk Backward")) { + if (value) { + backward = true; + } else { + backward = false; + } + } else if (binding.equals("Slice")) { + if (value) { + animChannel.setAnim("SliceHorizontal"); + animChannel.setSpeed(0.3f); + } + } + } + + @Override + public void simpleUpdate(float tpf) { + if(forward){ + model.move(model.getLocalRotation().multLocal(new Vector3f(0,0,1)).multLocal(tpf)); + }else if(backward){ + model.move(model.getLocalRotation().multLocal(new Vector3f(0,0,1)).multLocal(-tpf)); + }else if(leftRotate){ + model.rotate(0, tpf, 0); + }else if(rightRotate){ + model.rotate(0, -tpf, 0); + } + fpsText.setText(cam.getLocation() + "/" + cam.getRotation()); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestSimplePhysics.java b/jme3-examples/src/main/java/jme3test/bullet/TestSimplePhysics.java new file mode 100644 index 000000000..2045e5c77 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestSimplePhysics.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.*; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.math.Plane; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Sphere; + +/** + * This is a basic Test of jbullet-jme functions + * + * @author normenhansen + */ +public class TestSimplePhysics extends SimpleApplication { + + private BulletAppState bulletAppState; + + public static void main(String[] args) { + TestSimplePhysics app = new TestSimplePhysics(); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + + // Add a physics sphere to the world + Node physicsSphere = PhysicsTestHelper.createPhysicsTestNode(assetManager, new SphereCollisionShape(1), 1); + physicsSphere.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(3, 6, 0)); + rootNode.attachChild(physicsSphere); + getPhysicsSpace().add(physicsSphere); + + // Add a physics sphere to the world using the collision shape from sphere one + Node physicsSphere2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, physicsSphere.getControl(RigidBodyControl.class).getCollisionShape(), 1); + physicsSphere2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(4, 8, 0)); + rootNode.attachChild(physicsSphere2); + getPhysicsSpace().add(physicsSphere2); + + // Add a physics box to the world + Node physicsBox = PhysicsTestHelper.createPhysicsTestNode(assetManager, new BoxCollisionShape(new Vector3f(1, 1, 1)), 1); + physicsBox.getControl(RigidBodyControl.class).setFriction(0.1f); + physicsBox.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(.6f, 4, .5f)); + rootNode.attachChild(physicsBox); + getPhysicsSpace().add(physicsBox); + + // Add a physics cylinder to the world + Node physicsCylinder = PhysicsTestHelper.createPhysicsTestNode(assetManager, new CylinderCollisionShape(new Vector3f(1f, 1f, 1.5f)), 1); + physicsCylinder.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2, 2, 0)); + rootNode.attachChild(physicsCylinder); + getPhysicsSpace().add(physicsCylinder); + + // an obstacle mesh, does not move (mass=0) + Node node2 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new MeshCollisionShape(new Sphere(16, 16, 1.2f)), 0); + node2.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(2.5f, -4, 0f)); + rootNode.attachChild(node2); + getPhysicsSpace().add(node2); + + // the floor mesh, does not move (mass=0) + Node node3 = PhysicsTestHelper.createPhysicsTestNode(assetManager, new PlaneCollisionShape(new Plane(new Vector3f(0, 1, 0), 0)), 0); + node3.getControl(RigidBodyControl.class).setPhysicsLocation(new Vector3f(0f, -6, 0f)); + rootNode.attachChild(node3); + getPhysicsSpace().add(node3); + + // Join the physics objects with a Point2Point joint +// PhysicsPoint2PointJoint joint=new PhysicsPoint2PointJoint(physicsSphere, physicsBox, new Vector3f(-2,0,0), new Vector3f(2,0,0)); +// PhysicsHingeJoint joint=new PhysicsHingeJoint(physicsSphere, physicsBox, new Vector3f(-2,0,0), new Vector3f(2,0,0), Vector3f.UNIT_Z,Vector3f.UNIT_Z); +// getPhysicsSpace().add(joint); + + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java b/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java new file mode 100644 index 000000000..6f9e3fdd6 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java @@ -0,0 +1,71 @@ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.PhysicsSweepTestResult; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.math.Transform; +import com.jme3.scene.Node; +import java.util.List; + +/** + * + * A spatial moves and sweeps its next movement for obstacles before moving + * there Run this example with Vsync enabled + * + * @author + * @wezrule + */ +public class TestSweepTest extends SimpleApplication { + + private BulletAppState bulletAppState = new BulletAppState(); + private CapsuleCollisionShape obstacleCollisionShape = new CapsuleCollisionShape(0.3f, 0.5f); + private CapsuleCollisionShape capsuleCollisionShape = new CapsuleCollisionShape(1f, 1f); + private Node capsule; + private Node obstacle; + private float dist = .5f; + + public static void main(String[] args) { + new TestSweepTest().start(); + } + + @Override + public void simpleInitApp() { + stateManager.attach(bulletAppState); + + capsule = new Node("capsule"); + capsule.move(-2, 0, 0); + capsule.addControl(new RigidBodyControl(capsuleCollisionShape, 1)); + capsule.getControl(RigidBodyControl.class).setKinematic(true); + bulletAppState.getPhysicsSpace().add(capsule); + rootNode.attachChild(capsule); + + obstacle = new Node("obstacle"); + obstacle.move(2, 0, 0); + RigidBodyControl bodyControl = new RigidBodyControl(obstacleCollisionShape, 0); + obstacle.addControl(bodyControl); + bulletAppState.getPhysicsSpace().add(obstacle); + rootNode.attachChild(obstacle); + + bulletAppState.getPhysicsSpace().enableDebug(assetManager); + } + + @Override + public void simpleUpdate(float tpf) { + + float move = tpf * 1; + + List sweepTest = bulletAppState.getPhysicsSpace().sweepTest(capsuleCollisionShape, new Transform(capsule.getWorldTranslation()), new Transform(capsule.getWorldTranslation().add(dist, 0, 0))); + + if (sweepTest.size() > 0) { + PhysicsSweepTestResult get = sweepTest.get(0); + PhysicsCollisionObject collisionObject = get.getCollisionObject(); + fpsText.setText("Almost colliding with " + collisionObject.getUserObject().toString()); + } else { + // if the sweep is clear then move the spatial + capsule.move(move, 0, 0); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java b/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java new file mode 100644 index 000000000..6e793da61 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.animation.AnimChannel; +import com.jme3.animation.AnimControl; +import com.jme3.animation.AnimEventListener; +import com.jme3.animation.LoopMode; +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionEvent; +import com.jme3.bullet.collision.PhysicsCollisionListener; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.effect.shapes.EmitterSphereShape; +import com.jme3.input.ChaseCamera; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; +import java.util.ArrayList; +import java.util.List; + +/** + * A walking animated character followed by a 3rd person camera on a terrain with LOD. + * @author normenhansen + */ +public class TestWalkingChar extends SimpleApplication implements ActionListener, PhysicsCollisionListener, AnimEventListener { + + private BulletAppState bulletAppState; + //character + CharacterControl character; + Node model; + //temp vectors + Vector3f walkDirection = new Vector3f(); + //terrain + TerrainQuad terrain; + RigidBodyControl terrainPhysicsNode; + //Materials + Material matRock; + Material matBullet; + //animation + AnimChannel animationChannel; + AnimChannel shootingChannel; + AnimControl animationControl; + float airTime = 0; + //camera + boolean left = false, right = false, up = false, down = false; + ChaseCamera chaseCam; + //bullet + Sphere bullet; + SphereCollisionShape bulletCollisionShape; + //explosion + ParticleEmitter effect; + //brick wall + Box brick; + float bLength = 0.8f; + float bWidth = 0.4f; + float bHeight = 0.4f; + FilterPostProcessor fpp; + + public static void main(String[] args) { + TestWalkingChar app = new TestWalkingChar(); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); + stateManager.attach(bulletAppState); + setupKeys(); + prepareBullet(); + prepareEffect(); + createLight(); + createSky(); + createTerrain(); + createWall(); + createCharacter(); + setupChaseCamera(); + setupAnimationController(); + setupFilter(); + } + + private void setupFilter() { + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects); + fpp.addFilter(bloom); + viewPort.addProcessor(fpp); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + private void setupKeys() { + inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addListener(this, "wireframe"); + inputManager.addMapping("CharLeft", new KeyTrigger(KeyInput.KEY_A)); + inputManager.addMapping("CharRight", new KeyTrigger(KeyInput.KEY_D)); + inputManager.addMapping("CharUp", new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping("CharDown", new KeyTrigger(KeyInput.KEY_S)); + inputManager.addMapping("CharSpace", new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addMapping("CharShoot", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(this, "CharLeft"); + inputManager.addListener(this, "CharRight"); + inputManager.addListener(this, "CharUp"); + inputManager.addListener(this, "CharDown"); + inputManager.addListener(this, "CharSpace"); + inputManager.addListener(this, "CharShoot"); + } + + private void createWall() { + float xOff = -144; + float zOff = -40; + float startpt = bLength / 4 - xOff; + float height = 6.1f; + brick = new Box(Vector3f.ZERO, bLength, bHeight, bWidth); + brick.scaleTextureCoordinates(new Vector2f(1f, .5f)); + for (int j = 0; j < 15; j++) { + for (int i = 0; i < 4; i++) { + Vector3f vt = new Vector3f(i * bLength * 2 + startpt, bHeight + height, zOff); + addBrick(vt); + } + startpt = -startpt; + height += 1.01f * bHeight; + } + } + + private void addBrick(Vector3f ori) { + Geometry reBoxg = new Geometry("brick", brick); + reBoxg.setMaterial(matBullet); + reBoxg.setLocalTranslation(ori); + reBoxg.addControl(new RigidBodyControl(1.5f)); + reBoxg.setShadowMode(ShadowMode.CastAndReceive); + this.rootNode.attachChild(reBoxg); + this.getPhysicsSpace().add(reBoxg); + } + + private void prepareBullet() { + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(0.4f); + matBullet = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + matBullet.setColor("Color", ColorRGBA.Green); + matBullet.setColor("GlowColor", ColorRGBA.Green); + getPhysicsSpace().addCollisionListener(this); + } + + private void prepareEffect() { + int COUNT_FACTOR = 1; + float COUNT_FACTOR_F = 1f; + effect = new ParticleEmitter("Flame", Type.Triangle, 32 * COUNT_FACTOR); + effect.setSelectRandomImage(true); + effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (float) (1f / COUNT_FACTOR_F))); + effect.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f)); + effect.setStartSize(1.3f); + effect.setEndSize(2f); + effect.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); + effect.setParticlesPerSec(0); + effect.setGravity(0, -5, 0); + effect.setLowLife(.4f); + effect.setHighLife(.5f); + effect.setInitialVelocity(new Vector3f(0, 7, 0)); + effect.setVelocityVariation(1f); + effect.setImagesX(2); + effect.setImagesY(2); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png")); + effect.setMaterial(mat); +// effect.setLocalScale(100); + rootNode.attachChild(effect); + } + + private void createLight() { + Vector3f direction = new Vector3f(-0.1f, -0.7f, -1).normalizeLocal(); + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(direction); + dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); + rootNode.addLight(dl); + } + + private void createSky() { + rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false)); + } + + private void createTerrain() { + matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matRock.setBoolean("useTriPlanarMapping", false); + matRock.setBoolean("WardIso", true); + matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap", grass); + matRock.setFloat("DiffuseMap_0_scale", 64); + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_1", dirt); + matRock.setFloat("DiffuseMap_1_scale", 16); + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_2", rock); + matRock.setFloat("DiffuseMap_2_scale", 128); + Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); + normalMap0.setWrap(WrapMode.Repeat); + Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); + normalMap1.setWrap(WrapMode.Repeat); + Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); + normalMap2.setWrap(WrapMode.Repeat); + matRock.setTexture("NormalMap", normalMap0); + matRock.setTexture("NormalMap_1", normalMap2); + matRock.setTexture("NormalMap_2", normalMap2); + + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f); + heightmap.load(); + + } catch (Exception e) { + e.printStackTrace(); + } + + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List 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)); + + terrainPhysicsNode = new RigidBodyControl(CollisionShapeFactory.createMeshShape(terrain), 0); + terrain.addControl(terrainPhysicsNode); + rootNode.attachChild(terrain); + getPhysicsSpace().add(terrainPhysicsNode); + } + + private void createCharacter() { + CapsuleCollisionShape capsule = new CapsuleCollisionShape(3f, 4f); + character = new CharacterControl(capsule, 0.01f); + model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + //model.setLocalScale(0.5f); + model.addControl(character); + character.setPhysicsLocation(new Vector3f(-140, 15, -10)); + rootNode.attachChild(model); + getPhysicsSpace().add(character); + } + + private void setupChaseCamera() { + flyCam.setEnabled(false); + chaseCam = new ChaseCamera(cam, model, inputManager); + } + + private void setupAnimationController() { + animationControl = model.getControl(AnimControl.class); + animationControl.addListener(this); + animationChannel = animationControl.createChannel(); + shootingChannel = animationControl.createChannel(); + shootingChannel.addBone(animationControl.getSkeleton().getBone("uparm.right")); + shootingChannel.addBone(animationControl.getSkeleton().getBone("arm.right")); + shootingChannel.addBone(animationControl.getSkeleton().getBone("hand.right")); + } + + @Override + public void simpleUpdate(float tpf) { + Vector3f camDir = cam.getDirection().clone().multLocal(0.1f); + Vector3f camLeft = cam.getLeft().clone().multLocal(0.1f); + camDir.y = 0; + camLeft.y = 0; + walkDirection.set(0, 0, 0); + if (left) { + walkDirection.addLocal(camLeft); + } + if (right) { + walkDirection.addLocal(camLeft.negate()); + } + if (up) { + walkDirection.addLocal(camDir); + } + if (down) { + walkDirection.addLocal(camDir.negate()); + } + if (!character.onGround()) { + airTime = airTime + tpf; + } else { + airTime = 0; + } + if (walkDirection.length() == 0) { + if (!"stand".equals(animationChannel.getAnimationName())) { + animationChannel.setAnim("stand", 1f); + } + } else { + character.setViewDirection(walkDirection); + if (airTime > .3f) { + if (!"stand".equals(animationChannel.getAnimationName())) { + animationChannel.setAnim("stand"); + } + } else if (!"Walk".equals(animationChannel.getAnimationName())) { + animationChannel.setAnim("Walk", 0.7f); + } + } + character.setWalkDirection(walkDirection); + } + + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("CharLeft")) { + if (value) { + left = true; + } else { + left = false; + } + } else if (binding.equals("CharRight")) { + if (value) { + right = true; + } else { + right = false; + } + } else if (binding.equals("CharUp")) { + if (value) { + up = true; + } else { + up = false; + } + } else if (binding.equals("CharDown")) { + if (value) { + down = true; + } else { + down = false; + } + } else if (binding.equals("CharSpace")) { + character.jump(); + } else if (binding.equals("CharShoot") && !value) { + bulletControl(); + } + } + + private void bulletControl() { + shootingChannel.setAnim("Dodge", 0.1f); + shootingChannel.setLoopMode(LoopMode.DontLoop); + Geometry bulletg = new Geometry("bullet", bullet); + bulletg.setMaterial(matBullet); + bulletg.setShadowMode(ShadowMode.CastAndReceive); + bulletg.setLocalTranslation(character.getPhysicsLocation().add(cam.getDirection().mult(5))); + RigidBodyControl bulletControl = new BombControl(bulletCollisionShape, 1); + bulletControl.setCcdMotionThreshold(0.1f); + bulletControl.setLinearVelocity(cam.getDirection().mult(80)); + bulletg.addControl(bulletControl); + rootNode.attachChild(bulletg); + getPhysicsSpace().add(bulletControl); + } + + public void collision(PhysicsCollisionEvent event) { + if (event.getObjectA() instanceof BombControl) { + final Spatial node = event.getNodeA(); + effect.killAllParticles(); + effect.setLocalTranslation(node.getLocalTranslation()); + effect.emitAllParticles(); + } else if (event.getObjectB() instanceof BombControl) { + final Spatial node = event.getNodeB(); + effect.killAllParticles(); + effect.setLocalTranslation(node.getLocalTranslation()); + effect.emitAllParticles(); + } + } + + public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { + if (channel == shootingChannel) { + channel.setAnim("stand"); + } + } + + public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { + } +} diff --git a/jme3-examples/src/main/java/jme3test/collision/RayTrace.java b/jme3-examples/src/main/java/jme3test/collision/RayTrace.java new file mode 100644 index 000000000..d7cc5a246 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/collision/RayTrace.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java new file mode 100644 index 000000000..84d186e3a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.collision; + +import com.jme3.app.SimpleApplication; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Ray; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.Arrow; +import com.jme3.scene.shape.Box; + +public class TestMousePick extends SimpleApplication { + + public static void main(String[] args) { + TestMousePick app = new TestMousePick(); + app.start(); + } + + Node shootables; + Geometry mark; + + @Override + public void simpleInitApp() { + flyCam.setEnabled(false); + initMark(); // a red sphere to mark the hit + + /** create four colored boxes and a floor to shoot at: */ + shootables = new Node("Shootables"); + rootNode.attachChild(shootables); + shootables.attachChild(makeCube("a Dragon", -2f, 0f, 1f)); + shootables.attachChild(makeCube("a tin can", 1f, -2f, 0f)); + shootables.attachChild(makeCube("the Sheriff", 0f, 1f, -2f)); + shootables.attachChild(makeCube("the Deputy", 1f, 0f, -4f)); + shootables.attachChild(makeFloor()); + shootables.attachChild(makeCharacter()); + } + + @Override + public void simpleUpdate(float tpf){ + Vector3f origin = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.0f); + Vector3f direction = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.3f); + direction.subtractLocal(origin).normalizeLocal(); + + Ray ray = new Ray(origin, direction); + CollisionResults results = new CollisionResults(); + shootables.collideWith(ray, results); +// System.out.println("----- Collisions? " + results.size() + "-----"); +// for (int i = 0; i < results.size(); i++) { +// // For each hit, we know distance, impact point, name of geometry. +// float dist = results.getCollision(i).getDistance(); +// Vector3f pt = results.getCollision(i).getWorldContactPoint(); +// String hit = results.getCollision(i).getGeometry().getName(); +// System.out.println("* Collision #" + i); +// System.out.println(" You shot " + hit + " at " + pt + ", " + dist + " wu away."); +// } + if (results.size() > 0) { + CollisionResult closest = results.getClosestCollision(); + mark.setLocalTranslation(closest.getContactPoint()); + + Quaternion q = new Quaternion(); + q.lookAt(closest.getContactNormal(), Vector3f.UNIT_Y); + mark.setLocalRotation(q); + + rootNode.attachChild(mark); + } else { + rootNode.detachChild(mark); + } + } + + /** A cube object for target practice */ + protected Geometry makeCube(String name, float x, float y, float z) { + Box box = new Box(new Vector3f(x, y, z), 1, 1, 1); + Geometry cube = new Geometry(name, box); + Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat1.setColor("Color", ColorRGBA.randomColor()); + cube.setMaterial(mat1); + return cube; + } + + /** A floor to show that the "shot" can go through several objects. */ + protected Geometry makeFloor() { + Box box = new Box(new Vector3f(0, -4, -5), 15, .2f, 15); + Geometry floor = new Geometry("the Floor", box); + Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat1.setColor("Color", ColorRGBA.Gray); + floor.setMaterial(mat1); + return floor; + } + + /** A red ball that marks the last spot that was "hit" by the "shot". */ + protected void initMark() { + Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f)); + arrow.setLineWidth(3); + + //Sphere sphere = new Sphere(30, 30, 0.2f); + mark = new Geometry("BOOM!", arrow); + //mark = new Geometry("BOOM!", sphere); + Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mark_mat.setColor("Color", ColorRGBA.Red); + mark.setMaterial(mark_mat); + } + + protected Spatial makeCharacter() { + // load a character from jme3test-test-data + Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + golem.scale(0.5f); + golem.setLocalTranslation(-1.0f, -1.5f, -0.6f); + + // We must add a light to make the model visible + DirectionalLight sun = new DirectionalLight(); + sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal()); + golem.addLight(sun); + return golem; + } +} diff --git a/jme3-examples/src/main/java/jme3test/collision/TestRayCasting.java b/jme3-examples/src/main/java/jme3test/collision/TestRayCasting.java new file mode 100644 index 000000000..06b07e87b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/collision/TestRayCasting.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.collision; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingSphere; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer.Type; + +public class TestRayCasting extends SimpleApplication { + + private RayTrace tracer; + private Spatial teapot; + + public static void main(String[] args){ + TestRayCasting app = new TestRayCasting(); + app.setPauseOnLostFocus(false); + app.start(); + } + + @Override + public void simpleInitApp() { +// flyCam.setEnabled(false); + + // load material + Material mat = (Material) assetManager.loadMaterial("Interface/Logo/Logo.j3m"); + + Mesh q = new Mesh(); + q.setBuffer(Type.Position, 3, new float[] + { + 1, 0, 0, + 0, 1.5f, 0, + -1, 0, 0 + } + ); + q.setBuffer(Type.Index, 3, new int[]{ 0, 1, 2 }); + q.setBound(new BoundingSphere()); + q.updateBound(); +// Geometry teapot = new Geometry("MyGeom", q); + + teapot = assetManager.loadModel("Models/Teapot/Teapot.mesh.xml"); +// teapot.scale(2f, 2f, 2f); +// teapot.move(2f, 2f, -.5f); + teapot.rotate(FastMath.HALF_PI, FastMath.HALF_PI, FastMath.HALF_PI); + teapot.setMaterial(mat); + rootNode.attachChild(teapot); + +// cam.setLocation(cam.getLocation().add(0,1,0)); +// cam.lookAt(teapot.getWorldBound().getCenter(), Vector3f.UNIT_Y); + + tracer = new RayTrace(rootNode, cam, 160, 128); + tracer.show(); + tracer.update(); + } + + @Override + public void simpleUpdate(float tpf){ + teapot.rotate(0,tpf,0); + tracer.update(); + } + +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/collision/TestTriangleCollision.java b/jme3-examples/src/main/java/jme3test/collision/TestTriangleCollision.java new file mode 100644 index 000000000..e28d7c768 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/collision/TestTriangleCollision.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.collision; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.CollisionResults; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; + +public class TestTriangleCollision extends SimpleApplication { + + Geometry geom1; + + Spatial golem; + + public static void main(String[] args) { + TestTriangleCollision app = new TestTriangleCollision(); + app.start(); + } + + @Override + public void simpleInitApp() { + // Create two boxes + Mesh mesh1 = new Box(0.5f, 0.5f, 0.5f); + geom1 = new Geometry("Box", mesh1); + geom1.move(2, 2, -.5f); + Material m1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + m1.setColor("Color", ColorRGBA.Blue); + geom1.setMaterial(m1); + rootNode.attachChild(geom1); + + // load a character from jme3test-test-data + golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + golem.scale(0.5f); + golem.setLocalTranslation(-1.0f, -1.5f, -0.6f); + + // We must add a light to make the model visible + DirectionalLight sun = new DirectionalLight(); + sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal()); + golem.addLight(sun); + rootNode.attachChild(golem); + + // Create input + inputManager.addMapping("MoveRight", new KeyTrigger(KeyInput.KEY_L)); + inputManager.addMapping("MoveLeft", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addMapping("MoveUp", new KeyTrigger(KeyInput.KEY_I)); + inputManager.addMapping("MoveDown", new KeyTrigger(KeyInput.KEY_K)); + + inputManager.addListener(analogListener, new String[]{ + "MoveRight", "MoveLeft", "MoveUp", "MoveDown" + }); + } + private AnalogListener analogListener = new AnalogListener() { + + public void onAnalog(String name, float value, float tpf) { + if (name.equals("MoveRight")) { + geom1.move(2 * tpf, 0, 0); + } + + if (name.equals("MoveLeft")) { + geom1.move(-2 * tpf, 0, 0); + } + + if (name.equals("MoveUp")) { + geom1.move(0, 2 * tpf, 0); + } + + if (name.equals("MoveDown")) { + geom1.move(0, -2 * tpf, 0); + } + } + }; + + @Override + public void simpleUpdate(float tpf) { + CollisionResults results = new CollisionResults(); + BoundingVolume bv = geom1.getWorldBound(); + golem.collideWith(bv, results); + + if (results.size() > 0) { + geom1.getMaterial().setColor("Color", ColorRGBA.Red); + }else{ + geom1.getMaterial().setColor("Color", ColorRGBA.Blue); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/conversion/TestMipMapGen.java b/jme3-examples/src/main/java/jme3test/conversion/TestMipMapGen.java new file mode 100644 index 000000000..55fc152f7 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/conversion/TestMipMapGen.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.conversion; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.material.Material; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import jme3tools.converters.MipMapGenerator; + +public class TestMipMapGen extends SimpleApplication { + + public static void main(String[] args){ + TestMipMapGen app = new TestMipMapGen(); + app.start(); + } + + @Override + public void simpleInitApp() { + BitmapText txt = guiFont.createLabel("Left: HW Mips"); + txt.setLocalTranslation(0, settings.getHeight() - txt.getLineHeight() * 4, 0); + guiNode.attachChild(txt); + + txt = guiFont.createLabel("Right: AWT Mips"); + txt.setLocalTranslation(0, settings.getHeight() - txt.getLineHeight() * 3, 0); + guiNode.attachChild(txt); + + // create a simple plane/quad + Quad quadMesh = new Quad(1, 1); + quadMesh.updateGeometry(1, 1, false); + quadMesh.updateBound(); + + Geometry quad1 = new Geometry("Textured Quad", quadMesh); + Geometry quad2 = new Geometry("Textured Quad 2", quadMesh); + + Texture tex = assetManager.loadTexture("Interface/Logo/Monkey.png"); + tex.setMinFilter(Texture.MinFilter.Trilinear); + + Texture texCustomMip = tex.clone(); + Image imageCustomMip = texCustomMip.getImage().clone(); + MipMapGenerator.generateMipMaps(imageCustomMip); + texCustomMip.setImage(imageCustomMip); + + Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat1.setTexture("ColorMap", tex); + + Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat2.setTexture("ColorMap", texCustomMip); + + quad1.setMaterial(mat1); +// quad1.setLocalTranslation(1, 0, 0); + + quad2.setMaterial(mat2); + quad2.setLocalTranslation(1, 0, 0); + + rootNode.attachChild(quad1); + rootNode.attachChild(quad2); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/conversion/TestTriangleStrip.java b/jme3-examples/src/main/java/jme3test/conversion/TestTriangleStrip.java new file mode 100644 index 000000000..07d147ea3 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/conversion/TestTriangleStrip.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/effect/TestEverything.java b/jme3-examples/src/main/java/jme3test/effect/TestEverything.java new file mode 100644 index 000000000..2aca26f5e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/effect/TestEverything.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.effect; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.post.HDRRenderer; +import com.jme3.renderer.Caps; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.scene.shape.Box; +import com.jme3.shadow.BasicShadowRenderer; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; +import com.jme3.util.TangentBinormalGenerator; + +public class TestEverything extends SimpleApplication { + + private BasicShadowRenderer bsr; + private HDRRenderer hdrRender; + private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); + + public static void main(String[] args){ + TestEverything app = new TestEverything(); + app.start(); + } + + public void setupHdr(){ + if (renderer.getCaps().contains(Caps.GLSL100)){ + hdrRender = new HDRRenderer(assetManager, renderer); + hdrRender.setMaxIterations(40); + hdrRender.setSamples(settings.getSamples()); + + hdrRender.setWhiteLevel(3); + hdrRender.setExposure(0.72f); + hdrRender.setThrottle(1); + + // setPauseOnLostFocus(false); + // new HDRConfig(hdrRender).setVisible(true); + + viewPort.addProcessor(hdrRender); + } + } + + public void setupBasicShadow(){ + if (renderer.getCaps().contains(Caps.GLSL100)){ + bsr = new BasicShadowRenderer(assetManager, 1024); + bsr.setDirection(lightDir); + viewPort.addProcessor(bsr); + } + } + + public void setupSkyBox(){ + Texture envMap; + if (renderer.getCaps().contains(Caps.FloatTexture)){ + envMap = assetManager.loadTexture("Textures/Sky/St Peters/StPeters.hdr"); + }else{ + envMap = assetManager.loadTexture("Textures/Sky/St Peters/StPeters.jpg"); + } + rootNode.attachChild(SkyFactory.createSky(assetManager, envMap, new Vector3f(-1,-1,-1), true)); + } + + public void setupLighting(){ + boolean hdr = false; + if (hdrRender != null){ + hdr = hdrRender.isEnabled(); + } + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(lightDir); + if (hdr){ + dl.setColor(new ColorRGBA(3, 3, 3, 1)); + }else{ + dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1)); + } + rootNode.addLight(dl); + + dl = new DirectionalLight(); + dl.setDirection(new Vector3f(1, 0, -1).normalizeLocal()); + if (hdr){ + dl.setColor(new ColorRGBA(1, 1, 1, 1)); + }else{ + dl.setColor(new ColorRGBA(.4f, .4f, .4f, 1)); + } + rootNode.addLight(dl); + } + + public void setupFloor(){ + Material mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"); + mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat); + mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat); + mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat); + + Box floor = new Box(Vector3f.ZERO, 50, 1f, 50); + TangentBinormalGenerator.generate(floor); + floor.scaleTextureCoordinates(new Vector2f(5, 5)); + Geometry floorGeom = new Geometry("Floor", floor); + floorGeom.setMaterial(mat); + floorGeom.setShadowMode(ShadowMode.Receive); + rootNode.attachChild(floorGeom); + } + +// public void setupTerrain(){ +// Material mat = manager.loadMaterial("Textures/Terrain/Rock/Rock.j3m"); +// mat.getTextureParam("DiffuseMap").getValue().setWrap(WrapMode.Repeat); +// mat.getTextureParam("NormalMap").getValue().setWrap(WrapMode.Repeat); +// try{ +// Geomap map = GeomapLoader.fromImage(TestEverything.class.getResource("/textures/heightmap.png")); +// Mesh m = map.createMesh(new Vector3f(0.35f, 0.0005f, 0.35f), new Vector2f(10, 10), true); +// Logger.getLogger(TangentBinormalGenerator.class.getName()).setLevel(Level.SEVERE); +// TangentBinormalGenerator.generate(m); +// Geometry t = new Geometry("Terrain", m); +// t.setLocalTranslation(85, -15, 0); +// t.setMaterial(mat); +// t.updateModelBound(); +// t.setShadowMode(ShadowMode.Receive); +// rootNode.attachChild(t); +// }catch (IOException ex){ +// ex.printStackTrace(); +// } +// +// } + + public void setupRobotGuy(){ + Node model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + Material mat = assetManager.loadMaterial("Models/Oto/Oto.j3m"); + model.getChild(0).setMaterial(mat); +// model.setAnimation("Walk"); + model.setLocalTranslation(30, 10.5f, 30); + model.setLocalScale(2); + model.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(model); + } + + public void setupSignpost(){ + Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml"); + Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); + signpost.setMaterial(mat); + signpost.rotate(0, FastMath.HALF_PI, 0); + signpost.setLocalTranslation(12, 3.5f, 30); + signpost.setLocalScale(4); + signpost.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(signpost); + } + + @Override + public void simpleInitApp() { + cam.setLocation(new Vector3f(-32.295086f, 54.80136f, 79.59805f)); + cam.setRotation(new Quaternion(0.074364014f, 0.92519957f, -0.24794696f, 0.27748522f)); + cam.update(); + + cam.setFrustumFar(300); + flyCam.setMoveSpeed(30); + + rootNode.setCullHint(CullHint.Never); + + setupBasicShadow(); + setupHdr(); + + setupLighting(); + setupSkyBox(); + +// setupTerrain(); + setupFloor(); +// setupRobotGuy(); + setupSignpost(); + + + } + +} diff --git a/jme3-examples/src/main/java/jme3test/effect/TestExplosionEffect.java b/jme3-examples/src/main/java/jme3test/effect/TestExplosionEffect.java new file mode 100644 index 000000000..5d2d781a6 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/effect/TestExplosionEffect.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.effect; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.effect.shapes.EmitterSphereShape; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; + +public class TestExplosionEffect extends SimpleApplication { + + private float time = 0; + private int state = 0; + private Node explosionEffect = new Node("explosionFX"); + private ParticleEmitter flame, flash, spark, roundspark, smoketrail, debris, + shockwave; + + + private static final int COUNT_FACTOR = 1; + private static final float COUNT_FACTOR_F = 1f; + + private static final boolean POINT_SPRITE = true; + private static final Type EMITTER_TYPE = POINT_SPRITE ? Type.Point : Type.Triangle; + + public static void main(String[] args){ + TestExplosionEffect app = new TestExplosionEffect(); + app.start(); + } + + private void createFlame(){ + flame = new ParticleEmitter("Flame", EMITTER_TYPE, 32 * COUNT_FACTOR); + flame.setSelectRandomImage(true); + flame.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (float) (1f / COUNT_FACTOR_F))); + flame.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f)); + flame.setStartSize(1.3f); + flame.setEndSize(2f); + flame.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); + flame.setParticlesPerSec(0); + flame.setGravity(0, -5, 0); + flame.setLowLife(.4f); + flame.setHighLife(.5f); + flame.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 7, 0)); + flame.getParticleInfluencer().setVelocityVariation(1f); + flame.setImagesX(2); + flame.setImagesY(2); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png")); + mat.setBoolean("PointSprite", POINT_SPRITE); + flame.setMaterial(mat); + explosionEffect.attachChild(flame); + } + + private void createFlash(){ + flash = new ParticleEmitter("Flash", EMITTER_TYPE, 24 * COUNT_FACTOR); + flash.setSelectRandomImage(true); + flash.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, (float) (1f / COUNT_FACTOR_F))); + flash.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); + flash.setStartSize(.1f); + flash.setEndSize(3.0f); + flash.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f)); + flash.setParticlesPerSec(0); + flash.setGravity(0, 0, 0); + flash.setLowLife(.2f); + flash.setHighLife(.2f); + flash.setInitialVelocity(new Vector3f(0, 5f, 0)); + flash.setVelocityVariation(1); + flash.setImagesX(2); + flash.setImagesY(2); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flash.png")); + mat.setBoolean("PointSprite", POINT_SPRITE); + flash.setMaterial(mat); + explosionEffect.attachChild(flash); + } + + private void createRoundSpark(){ + roundspark = new ParticleEmitter("RoundSpark", EMITTER_TYPE, 20 * COUNT_FACTOR); + roundspark.setStartColor(new ColorRGBA(1f, 0.29f, 0.34f, (float) (1.0 / COUNT_FACTOR_F))); + roundspark.setEndColor(new ColorRGBA(0, 0, 0, (float) (0.5f / COUNT_FACTOR_F))); + roundspark.setStartSize(1.2f); + roundspark.setEndSize(1.8f); + roundspark.setShape(new EmitterSphereShape(Vector3f.ZERO, 2f)); + roundspark.setParticlesPerSec(0); + roundspark.setGravity(0, -.5f, 0); + roundspark.setLowLife(1.8f); + roundspark.setHighLife(2f); + roundspark.setInitialVelocity(new Vector3f(0, 3, 0)); + roundspark.setVelocityVariation(.5f); + roundspark.setImagesX(1); + roundspark.setImagesY(1); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/roundspark.png")); + mat.setBoolean("PointSprite", POINT_SPRITE); + roundspark.setMaterial(mat); + explosionEffect.attachChild(roundspark); + } + + private void createSpark(){ + spark = new ParticleEmitter("Spark", Type.Triangle, 30 * COUNT_FACTOR); + spark.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, (float) (1.0f / COUNT_FACTOR_F))); + spark.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); + spark.setStartSize(.5f); + spark.setEndSize(.5f); + spark.setFacingVelocity(true); + spark.setParticlesPerSec(0); + spark.setGravity(0, 5, 0); + spark.setLowLife(1.1f); + spark.setHighLife(1.5f); + spark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 20, 0)); + spark.getParticleInfluencer().setVelocityVariation(1); + spark.setImagesX(1); + spark.setImagesY(1); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/spark.png")); + spark.setMaterial(mat); + explosionEffect.attachChild(spark); + } + + private void createSmokeTrail(){ + smoketrail = new ParticleEmitter("SmokeTrail", Type.Triangle, 22 * COUNT_FACTOR); + smoketrail.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, (float) (1.0f / COUNT_FACTOR_F))); + smoketrail.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); + smoketrail.setStartSize(.2f); + smoketrail.setEndSize(1f); + +// smoketrail.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); + smoketrail.setFacingVelocity(true); + smoketrail.setParticlesPerSec(0); + smoketrail.setGravity(0, 1, 0); + smoketrail.setLowLife(.4f); + smoketrail.setHighLife(.5f); + smoketrail.setInitialVelocity(new Vector3f(0, 12, 0)); + smoketrail.setVelocityVariation(1); + smoketrail.setImagesX(1); + smoketrail.setImagesY(3); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/smoketrail.png")); + smoketrail.setMaterial(mat); + explosionEffect.attachChild(smoketrail); + } + + private void createDebris(){ + debris = new ParticleEmitter("Debris", Type.Triangle, 15 * COUNT_FACTOR); + debris.setSelectRandomImage(true); + debris.setRandomAngle(true); + debris.setRotateSpeed(FastMath.TWO_PI * 4); + debris.setStartColor(new ColorRGBA(1f, 0.59f, 0.28f, (float) (1.0f / COUNT_FACTOR_F))); + debris.setEndColor(new ColorRGBA(.5f, 0.5f, 0.5f, 0f)); + debris.setStartSize(.2f); + debris.setEndSize(.2f); + +// debris.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f)); + debris.setParticlesPerSec(0); + debris.setGravity(0, 12f, 0); + debris.setLowLife(1.4f); + debris.setHighLife(1.5f); + debris.setInitialVelocity(new Vector3f(0, 15, 0)); + debris.setVelocityVariation(.60f); + debris.setImagesX(3); + debris.setImagesY(3); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/Debris.png")); + debris.setMaterial(mat); + explosionEffect.attachChild(debris); + } + + private void createShockwave(){ + shockwave = new ParticleEmitter("Shockwave", Type.Triangle, 1 * COUNT_FACTOR); +// shockwave.setRandomAngle(true); + shockwave.setFaceNormal(Vector3f.UNIT_Y); + shockwave.setStartColor(new ColorRGBA(.48f, 0.17f, 0.01f, (float) (.8f / COUNT_FACTOR_F))); + shockwave.setEndColor(new ColorRGBA(.48f, 0.17f, 0.01f, 0f)); + + shockwave.setStartSize(0f); + shockwave.setEndSize(7f); + + shockwave.setParticlesPerSec(0); + shockwave.setGravity(0, 0, 0); + shockwave.setLowLife(0.5f); + shockwave.setHighLife(0.5f); + shockwave.setInitialVelocity(new Vector3f(0, 0, 0)); + shockwave.setVelocityVariation(0f); + shockwave.setImagesX(1); + shockwave.setImagesY(1); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/shockwave.png")); + shockwave.setMaterial(mat); + explosionEffect.attachChild(shockwave); + } + + @Override + public void simpleInitApp() { + createFlame(); + createFlash(); + createSpark(); + createRoundSpark(); + createSmokeTrail(); + createDebris(); + createShockwave(); + explosionEffect.setLocalScale(0.5f); + renderManager.preloadScene(explosionEffect); + + cam.setLocation(new Vector3f(0, 3.5135868f, 10)); + cam.setRotation(new Quaternion(1.5714673E-4f, 0.98696727f, -0.16091813f, 9.6381607E-4f)); + + rootNode.attachChild(explosionEffect); + } + + @Override + public void simpleUpdate(float tpf){ + time += tpf / speed; + if (time > 1f && state == 0){ + flash.emitAllParticles(); + spark.emitAllParticles(); + smoketrail.emitAllParticles(); + debris.emitAllParticles(); + shockwave.emitAllParticles(); + state++; + } + if (time > 1f + .05f / speed && state == 1){ + flame.emitAllParticles(); + roundspark.emitAllParticles(); + state++; + } + + // rewind the effect + if (time > 5 / speed && state == 2){ + state = 0; + time = 0; + + flash.killAllParticles(); + spark.killAllParticles(); + smoketrail.killAllParticles(); + debris.killAllParticles(); + flame.killAllParticles(); + roundspark.killAllParticles(); + shockwave.killAllParticles(); + } + } + +} diff --git a/jme3-examples/src/main/java/jme3test/effect/TestMovingParticle.java b/jme3-examples/src/main/java/jme3test/effect/TestMovingParticle.java new file mode 100644 index 000000000..d603e229a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/effect/TestMovingParticle.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.effect; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; + +/** + * Particle that moves in a circle. + * + * @author Kirill Vainer + */ +public class TestMovingParticle extends SimpleApplication { + + private ParticleEmitter emit; + private float angle = 0; + + public static void main(String[] args) { + TestMovingParticle app = new TestMovingParticle(); + app.start(); + } + + @Override + public void simpleInitApp() { + emit = new ParticleEmitter("Emitter", Type.Triangle, 300); + emit.setGravity(0, 0, 0); + emit.setVelocityVariation(1); + emit.setLowLife(1); + emit.setHighLife(1); + emit.setInitialVelocity(new Vector3f(0, .5f, 0)); + emit.setImagesX(15); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png")); + emit.setMaterial(mat); + + rootNode.attachChild(emit); + + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if ("setNum".equals(name) && isPressed) { + emit.setNumParticles(1000); + } + } + }, "setNum"); + + inputManager.addMapping("setNum", new KeyTrigger(KeyInput.KEY_SPACE)); + } + + @Override + public void simpleUpdate(float tpf) { + angle += tpf; + angle %= FastMath.TWO_PI; + float x = FastMath.cos(angle) * 2; + float y = FastMath.sin(angle) * 2; + emit.setLocalTranslation(x, 0, y); + } +} diff --git a/jme3-examples/src/main/java/jme3test/effect/TestParticleExportingCloning.java b/jme3-examples/src/main/java/jme3test/effect/TestParticleExportingCloning.java new file mode 100644 index 000000000..552287f75 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/effect/TestParticleExportingCloning.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.effect; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.effect.shapes.EmitterSphereShape; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class TestParticleExportingCloning extends SimpleApplication { + + public static void main(String[] args){ + TestParticleExportingCloning app = new TestParticleExportingCloning(); + app.start(); + } + + @Override + public void simpleInitApp() { + ParticleEmitter emit = new ParticleEmitter("Emitter", Type.Triangle, 200); + emit.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); + emit.setGravity(0, 0, 0); + emit.setLowLife(5); + emit.setHighLife(10); + emit.setInitialVelocity(new Vector3f(0, 0, 0)); + emit.setImagesX(15); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png")); + emit.setMaterial(mat); + + ParticleEmitter emit2 = emit.clone(); + emit2.move(3, 0, 0); + + rootNode.attachChild(emit); + rootNode.attachChild(emit2); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + BinaryExporter.getInstance().save(emit, out); + + BinaryImporter imp = new BinaryImporter(); + imp.setAssetManager(assetManager); + ParticleEmitter emit3 = (ParticleEmitter) imp.load(out.toByteArray()); + + emit3.move(-3, 0, 0); + rootNode.attachChild(emit3); + } catch (IOException ex) { + ex.printStackTrace(); + } + + // Camera cam2 = cam.clone(); + // cam.setViewPortTop(0.5f); + // cam2.setViewPortBottom(0.5f); + // ViewPort vp = renderManager.createMainView("SecondView", cam2); + // viewPort.setClearEnabled(false); + // vp.attachScene(rootNode); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/effect/TestPointSprite.java b/jme3-examples/src/main/java/jme3test/effect/TestPointSprite.java new file mode 100644 index 000000000..1a3e89092 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/effect/TestPointSprite.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.effect; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.effect.shapes.EmitterBoxShape; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; + +public class TestPointSprite extends SimpleApplication { + + public static void main(String[] args){ + TestPointSprite app = new TestPointSprite(); + app.start(); + } + + @Override + public void simpleInitApp() { + final ParticleEmitter emit = new ParticleEmitter("Emitter", Type.Point, 10000); + emit.setShape(new EmitterBoxShape(new Vector3f(-1.8f, -1.8f, -1.8f), + new Vector3f(1.8f, 1.8f, 1.8f))); + emit.setGravity(0, 0, 0); + emit.setLowLife(60); + emit.setHighLife(60); + emit.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0)); + emit.setImagesX(15); + emit.setStartSize(0.05f); + emit.setEndSize(0.05f); + emit.setStartColor(ColorRGBA.White); + emit.setEndColor(ColorRGBA.White); + emit.setSelectRandomImage(true); + emit.emitAllParticles(); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setBoolean("PointSprite", true); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png")); + emit.setMaterial(mat); + + rootNode.attachChild(emit); + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if ("setNum".equals(name) && isPressed) { + emit.setNumParticles(5000); + emit.emitAllParticles(); + } + } + }, "setNum"); + + inputManager.addMapping("setNum", new KeyTrigger(KeyInput.KEY_SPACE)); + + } + +} diff --git a/jme3-examples/src/main/java/jme3test/effect/TestSoftParticles.java b/jme3-examples/src/main/java/jme3test/effect/TestSoftParticles.java new file mode 100644 index 000000000..3d8f96d65 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/effect/TestSoftParticles.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.effect; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh; +import com.jme3.effect.shapes.EmitterSphereShape; +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.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.TranslucentBucketFilter; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; + +/** + * + * @author Nehon + */ +public class TestSoftParticles extends SimpleApplication { + + private boolean softParticles = true; + private FilterPostProcessor fpp; + private TranslucentBucketFilter tbf; + private Node particleNode; + + public static void main(String[] args) { + TestSoftParticles app = new TestSoftParticles(); + app.start(); + } + + @Override + public void simpleInitApp() { + + cam.setLocation(new Vector3f(-7.2221026f, 4.1183004f, 7.759811f)); + cam.setRotation(new Quaternion(0.06152846f, 0.91236454f, -0.1492115f, 0.37621948f)); + + flyCam.setMoveSpeed(10); + + + // -------- floor + Box b = new Box(Vector3f.ZERO, 10, 0.1f, 10); + Geometry geom = new Geometry("Box", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Gray); + mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + geom.setMaterial(mat); + rootNode.attachChild(geom); + + Box b2 = new Box(Vector3f.ZERO, 1, 1, 1); + Geometry geom2 = new Geometry("Box", b2); + Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat2.setColor("Color", ColorRGBA.DarkGray); + geom2.setMaterial(mat2); + rootNode.attachChild(geom2); + geom2.setLocalScale(0.1f, 0.2f, 1); + + fpp = new FilterPostProcessor(assetManager); + tbf = new TranslucentBucketFilter(true); + fpp.addFilter(tbf); + viewPort.addProcessor(fpp); + + particleNode = new Node("particleNode"); + rootNode.attachChild(particleNode); + + createParticles(); + + + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if(isPressed && name.equals("toggle")){ + // tbf.setEnabled(!tbf.isEnabled()); + softParticles = !softParticles; + if(softParticles){ + viewPort.addProcessor(fpp); + }else{ + viewPort.removeProcessor(fpp); + } + } + } + }, "toggle"); + inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); + + // emit again + inputManager.addListener(new ActionListener() { + public void onAction(String name, boolean isPressed, float tpf) { + if(isPressed && name.equals("refire")) { + //fpp.removeFilter(tbf); // <-- add back in to fix + particleNode.detachAllChildren(); + createParticles(); + //fpp.addFilter(tbf); + } + } + }, "refire"); + inputManager.addMapping("refire", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + } + + private void createParticles() { + + Material material = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + material.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png")); + material.setFloat("Softness", 3f); // + + //Fire + ParticleEmitter fire = new ParticleEmitter("Fire", ParticleMesh.Type.Triangle, 30); + fire.setMaterial(material); + fire.setShape(new EmitterSphereShape(Vector3f.ZERO, 0.1f)); + 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.setStartSize(0.6f); + fire.setEndSize(0.01f); + fire.setGravity(0, -0.3f, 0); + fire.setLowLife(0.5f); + fire.setHighLife(3f); + fire.setLocalTranslation(0, 0.2f, 0); + + particleNode.attachChild(fire); + + + ParticleEmitter smoke = new ParticleEmitter("Smoke", ParticleMesh.Type.Triangle, 30); + smoke.setMaterial(material); + smoke.setShape(new EmitterSphereShape(Vector3f.ZERO, 5)); + smoke.setImagesX(1); + smoke.setImagesY(1); // 2x2 texture animation + smoke.setStartColor(new ColorRGBA(0.1f, 0.1f, 0.1f,1f)); // dark gray + smoke.setEndColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 0.3f)); // gray + smoke.setStartSize(3f); + smoke.setEndSize(5f); + smoke.setGravity(0, -0.001f, 0); + smoke.setLowLife(100f); + smoke.setHighLife(100f); + smoke.setLocalTranslation(0, 0.1f, 0); + smoke.emitAllParticles(); + + particleNode.attachChild(smoke); + } + + +} diff --git a/jme3-examples/src/main/java/jme3test/export/TestAssetLinkNode.java b/jme3-examples/src/main/java/jme3test/export/TestAssetLinkNode.java new file mode 100644 index 000000000..d2c721328 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/export/TestAssetLinkNode.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java b/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java new file mode 100644 index 000000000..bfc03c0c5 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.export; + +import com.jme3.animation.AnimChannel; +import com.jme3.animation.AnimControl; +import com.jme3.app.SimpleApplication; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class TestOgreConvert extends SimpleApplication { + + public static void main(String[] args){ + TestOgreConvert app = new TestOgreConvert(); + app.start(); + } + + @Override + public void simpleInitApp() { + Spatial ogreModel = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + + DirectionalLight dl = new DirectionalLight(); + dl.setColor(ColorRGBA.White); + dl.setDirection(new Vector3f(0,-1,-1).normalizeLocal()); + rootNode.addLight(dl); + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryExporter exp = new BinaryExporter(); + exp.save(ogreModel, baos); + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + BinaryImporter imp = new BinaryImporter(); + imp.setAssetManager(assetManager); + Node ogreModelReloaded = (Node) imp.load(bais, null, null); + + AnimControl control = ogreModelReloaded.getControl(AnimControl.class); + AnimChannel chan = control.createChannel(); + chan.setAnim("Walk"); + + rootNode.attachChild(ogreModelReloaded); + } catch (IOException ex){ + ex.printStackTrace(); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/games/CubeField.java b/jme3-examples/src/main/java/jme3test/games/CubeField.java new file mode 100644 index 000000000..fdad49de6 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/games/CubeField.java @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.games; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingVolume; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Dome; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Kyle "bonechilla" Williams + */ +public class CubeField extends SimpleApplication implements AnalogListener { + + public static void main(String[] args) { + CubeField app = new CubeField(); + app.start(); + } + + private BitmapFont defaultFont; + + private boolean START; + private int difficulty, Score, colorInt, highCap, lowCap,diffHelp; + private Node player; + private Geometry fcube; + private ArrayList cubeField; + private ArrayList obstacleColors; + private float speed, coreTime,coreTime2; + private float camAngle = 0; + private BitmapText fpsScoreText, pressStart; + + private boolean solidBox = true; + private Material playerMaterial; + private Material floorMaterial; + + private float fpsRate = 1000f / 1f; + + /** + * Initializes game + */ + @Override + public void simpleInitApp() { + Logger.getLogger("com.jme3").setLevel(Level.WARNING); + + flyCam.setEnabled(false); + setDisplayStatView(false); + + Keys(); + + defaultFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + pressStart = new BitmapText(defaultFont, false); + fpsScoreText = new BitmapText(defaultFont, false); + + loadText(fpsScoreText, "Current Score: 0", defaultFont, 0, 2, 0); + loadText(pressStart, "PRESS ENTER", defaultFont, 0, 5, 0); + + player = createPlayer(); + rootNode.attachChild(player); + cubeField = new ArrayList(); + 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); + speed = lowCap / 400f; + coreTime = 20.0f; + coreTime2 = 10.0f; + diffHelp=lowCap; + player.setLocalTranslation(0,0,0); + } + + @Override + public void simpleUpdate(float tpf) { + camTakeOver(tpf); + if (START){ + gameLogic(tpf); + } + colorLogic(); + } + /** + * Forcefully takes over Camera adding functionality and placing it behind the character + * @param tpf Tickes Per Frame + */ + private void camTakeOver(float tpf) { + cam.setLocation(player.getLocalTranslation().add(-8, 2, 0)); + cam.lookAt(player.getLocalTranslation(), Vector3f.UNIT_Y); + + Quaternion rot = new Quaternion(); + rot.fromAngleNormalAxis(camAngle, Vector3f.UNIT_Z); + cam.setRotation(cam.getRotation().mult(rot)); + camAngle *= FastMath.pow(.99f, fpsRate * tpf); + } + + @Override + public void requestClose(boolean esc) { + if (!esc){ + System.out.println("The game was quit."); + }else{ + System.out.println("Player has Collided. Final Score is " + Score); + } + context.destroy(false); + } + /** + * Randomly Places a cube on the map between 30 and 90 paces away from player + */ + private void randomizeCube() { + Geometry cube = fcube.clone(); + int playerX = (int) player.getLocalTranslation().getX(); + int playerZ = (int) player.getLocalTranslation().getZ(); +// float x = FastMath.nextRandomInt(playerX + difficulty + 10, playerX + difficulty + 150); + float x = FastMath.nextRandomInt(playerX + difficulty + 30, playerX + difficulty + 90); + float z = FastMath.nextRandomInt(playerZ - difficulty - 50, playerZ + difficulty + 50); + cube.getLocalTranslation().set(x, 0, z); + +// playerX+difficulty+30,playerX+difficulty+90 + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + if (!solidBox){ + mat.getAdditionalRenderState().setWireframe(true); + } + mat.setColor("Color", obstacleColors.get(FastMath.nextRandomInt(0, obstacleColors.size() - 1))); + cube.setMaterial(mat); + + rootNode.attachChild(cube); + cubeField.add(cube); + } + + private Geometry createFirstCube() { + Vector3f loc = player.getLocalTranslation(); + loc.addLocal(4, 0, 0); + Box b = new Box(loc, 1, 1, 1); + + Geometry geom = new Geometry("Box", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Blue); + geom.setMaterial(mat); + + return geom; + } + + private Node createPlayer() { + Dome b = new Dome(Vector3f.ZERO, 10, 100, 1); + Geometry playerMesh = new Geometry("Box", b); + + playerMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + playerMaterial.setColor("Color", ColorRGBA.Red); + playerMesh.setMaterial(playerMaterial); + playerMesh.setName("player"); + + Box floor = new Box(Vector3f.ZERO.add(playerMesh.getLocalTranslation().getX(), + playerMesh.getLocalTranslation().getY() - 1, 0), 100, 0, 100); + Geometry floorMesh = new Geometry("Box", floor); + + floorMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + floorMaterial.setColor("Color", ColorRGBA.LightGray); + floorMesh.setMaterial(floorMaterial); + floorMesh.setName("floor"); + + Node playerNode = new Node(); + playerNode.attachChild(playerMesh); + playerNode.attachChild(floorMesh); + + return playerNode; + } + + /** + * If Game is Lost display Score and Reset the Game + */ + private void gameLost(){ + START = false; + loadText(pressStart, "You lost! Press enter to try again.", defaultFont, 0, 5, 0); + gameReset(); + } + + /** + * Core Game Logic + */ + private void gameLogic(float tpf){ + //Subtract difficulty level in accordance to speed every 10 seconds + if(timer.getTimeInSeconds()>=coreTime2){ + coreTime2=timer.getTimeInSeconds()+10; + if(difficulty<=lowCap){ + difficulty=lowCap; + } + else if(difficulty>lowCap){ + difficulty-=5; + diffHelp+=1; + } + } + + if(speed<.1f){ + speed+=.000001f*tpf*fpsRate; + } + + player.move(speed * tpf * fpsRate, 0, 0); + if (cubeField.size() > difficulty){ + cubeField.remove(0); + }else if (cubeField.size() != difficulty){ + randomizeCube(); + } + + if (cubeField.isEmpty()){ + requestClose(false); + }else{ + for (int i = 0; i < cubeField.size(); i++){ + + //better way to check collision + Geometry playerModel = (Geometry) player.getChild(0); + Geometry cubeModel = cubeField.get(i); + cubeModel.updateGeometricState(); + + BoundingVolume pVol = playerModel.getWorldBound(); + BoundingVolume vVol = cubeModel.getWorldBound(); + + if (pVol.intersects(vVol)){ + gameLost(); + return; + } + //Remove cube if 10 world units behind player + if (cubeField.get(i).getLocalTranslation().getX() + 10 < player.getLocalTranslation().getX()){ + cubeField.get(i).removeFromParent(); + cubeField.remove(cubeField.get(i)); + } + + } + } + + Score += fpsRate * tpf; + fpsScoreText.setText("Current Score: "+Score); + } + /** + * Sets up the keyboard bindings + */ + private void Keys() { + inputManager.addMapping("START", new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_LEFT)); + inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_RIGHT)); + inputManager.addListener(this, "START", "Left", "Right"); + } + + public void onAnalog(String binding, float value, float tpf) { + if (binding.equals("START") && !START){ + START = true; + guiNode.detachChild(pressStart); + System.out.println("START"); + }else if (START == true && binding.equals("Left")){ + player.move(0, 0, -(speed / 2f) * value * fpsRate); + camAngle -= value*tpf; + }else if (START == true && binding.equals("Right")){ + player.move(0, 0, (speed / 2f) * value * fpsRate); + camAngle += value*tpf; + } + } + + /** + * Determines the colors of the player, floor, obstacle and background + */ + private void colorLogic() { + if (timer.getTimeInSeconds() >= coreTime){ + + colorInt++; + coreTime = timer.getTimeInSeconds() + 20; + + + switch (colorInt){ + case 1: + obstacleColors.clear(); + solidBox = false; + obstacleColors.add(ColorRGBA.Green); + renderer.setBackgroundColor(ColorRGBA.Black); + playerMaterial.setColor("Color", ColorRGBA.White); + floorMaterial.setColor("Color", ColorRGBA.Black); + break; + case 2: + obstacleColors.set(0, ColorRGBA.Black); + solidBox = true; + renderer.setBackgroundColor(ColorRGBA.White); + playerMaterial.setColor("Color", ColorRGBA.Gray); + floorMaterial.setColor("Color", ColorRGBA.LightGray); + break; + case 3: + obstacleColors.set(0, ColorRGBA.Pink); + break; + case 4: + obstacleColors.set(0, ColorRGBA.Cyan); + obstacleColors.add(ColorRGBA.Magenta); + renderer.setBackgroundColor(ColorRGBA.Gray); + floorMaterial.setColor("Color", ColorRGBA.Gray); + playerMaterial.setColor("Color", ColorRGBA.White); + break; + case 5: + obstacleColors.remove(0); + renderer.setBackgroundColor(ColorRGBA.Pink); + solidBox = false; + playerMaterial.setColor("Color", ColorRGBA.White); + break; + case 6: + obstacleColors.set(0, ColorRGBA.White); + solidBox = true; + renderer.setBackgroundColor(ColorRGBA.Black); + playerMaterial.setColor("Color", ColorRGBA.Gray); + floorMaterial.setColor("Color", ColorRGBA.LightGray); + break; + case 7: + obstacleColors.set(0, ColorRGBA.Green); + renderer.setBackgroundColor(ColorRGBA.Gray); + playerMaterial.setColor("Color", ColorRGBA.Black); + floorMaterial.setColor("Color", ColorRGBA.Orange); + break; + case 8: + obstacleColors.set(0, ColorRGBA.Red); + floorMaterial.setColor("Color", ColorRGBA.Pink); + break; + case 9: + obstacleColors.set(0, ColorRGBA.Orange); + obstacleColors.add(ColorRGBA.Red); + obstacleColors.add(ColorRGBA.Yellow); + renderer.setBackgroundColor(ColorRGBA.White); + playerMaterial.setColor("Color", ColorRGBA.Red); + floorMaterial.setColor("Color", ColorRGBA.Gray); + colorInt=0; + break; + default: + break; + } + } + } + /** + * Sets up a BitmapText to be displayed + * @param txt the Bitmap Text + * @param text the + * @param font the font of the text + * @param x + * @param y + * @param z + */ + private void loadText(BitmapText txt, String text, BitmapFont font, float x, float y, float z) { + txt.setSize(font.getCharSet().getRenderedSize()); + txt.setLocalTranslation(txt.getLineWidth() * x, txt.getLineHeight() * y, z); + txt.setText(text); + guiNode.attachChild(txt); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/games/WorldOfInception.java b/jme3-examples/src/main/java/jme3test/games/WorldOfInception.java new file mode 100644 index 000000000..6e159263c --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/games/WorldOfInception.java @@ -0,0 +1,534 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.games; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.debug.DebugTools; +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.Ray; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.FogFilter; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * WorldOfInception - Find the galaxy center ;) + * + * @author normenhansen + */ +public class WorldOfInception extends SimpleApplication implements AnalogListener { + + //Assumptions: POI radius in world == 1, only one player, vector3f hash describes enough worlds + private static final Logger logger = Logger.getLogger(WorldOfInception.class.getName()); + private static final Random random = new Random(System.currentTimeMillis()); + private static final float scaleDist = 10; + private static final float poiRadius = 100; + private static final int poiCount = 30; + private static Material poiMaterial; + private static Mesh poiMesh; + private static Material ballMaterial; + private static Mesh ballMesh; + private static CollisionShape poiHorizonCollisionShape; + private static CollisionShape poiCollisionShape; + private static CollisionShape ballCollisionShape; + private InceptionLevel currentLevel; + private final Vector3f walkDirection = new Vector3f(); + private static DebugTools debugTools; + + public WorldOfInception() { + //base level vector position hash == seed + super(new InceptionLevel(null, Vector3f.ZERO)); + currentLevel = super.getStateManager().getState(InceptionLevel.class); + currentLevel.takeOverParent(); + currentLevel.getRootNode().setLocalScale(Vector3f.UNIT_XYZ); + currentLevel.getRootNode().setLocalTranslation(Vector3f.ZERO); + } + + public static void main(String[] args) { + WorldOfInception app = new WorldOfInception(); + app.start(); + } + + @Override + public void simpleInitApp() { + //set far frustum only so we see the outer world longer + cam.setFrustumFar(10000); + cam.setLocation(Vector3f.ZERO); + debugTools = new DebugTools(assetManager); + rootNode.attachChild(debugTools.debugNode); + poiMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + poiMaterial.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + poiMesh = new Sphere(16, 16, 1f); + + ballMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + ballMaterial.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + ballMaterial.setColor("Color", ColorRGBA.Red); + ballMesh = new Sphere(16, 16, 1.0f); + + poiHorizonCollisionShape = new MeshCollisionShape(new Sphere(128, 128, poiRadius)); + poiCollisionShape = new SphereCollisionShape(1f); + ballCollisionShape = new SphereCollisionShape(1f); + setupKeys(); + setupDisplay(); + setupFog(); + } + + private void setupKeys() { + inputManager.addMapping("StrafeLeft", new KeyTrigger(KeyInput.KEY_A)); + inputManager.addMapping("StrafeRight", new KeyTrigger(KeyInput.KEY_D)); + inputManager.addMapping("Forward", new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping("Back", new KeyTrigger(KeyInput.KEY_S)); + inputManager.addMapping("StrafeUp", new KeyTrigger(KeyInput.KEY_Q)); + inputManager.addMapping("StrafeDown", new KeyTrigger(KeyInput.KEY_Z), new KeyTrigger(KeyInput.KEY_Y)); + inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("Return", new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addMapping("Esc", new KeyTrigger(KeyInput.KEY_ESCAPE)); + inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_UP)); + inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_DOWN)); + inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_LEFT)); + inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_RIGHT)); + inputManager.addListener(this, "StrafeLeft", "StrafeRight", "Forward", "Back", "StrafeUp", "StrafeDown", "Space", "Reset", "Esc", "Up", "Down", "Left", "Right"); + } + + private void setupDisplay() { + if (fpsText == null) { + fpsText = new BitmapText(guiFont, false); + } + fpsText.setLocalScale(0.7f, 0.7f, 0.7f); + fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0); + fpsText.setText(""); + fpsText.setCullHint(Spatial.CullHint.Never); + guiNode.attachChild(fpsText); + } + + private void setupFog() { + // use fog to give more sense of depth + FilterPostProcessor fpp; + FogFilter fog; + fpp=new FilterPostProcessor(assetManager); + fog=new FogFilter(); + fog.setFogColor(new ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f)); + fog.setFogDistance(poiRadius); + fog.setFogDensity(2.0f); + fpp.addFilter(fog); + viewPort.addProcessor(fpp); + } + + public void onAnalog(String name, float value, float tpf) { + Vector3f left = rootNode.getLocalRotation().mult(Vector3f.UNIT_X.negate()); + Vector3f forward = rootNode.getLocalRotation().mult(Vector3f.UNIT_Z.negate()); + Vector3f up = rootNode.getLocalRotation().mult(Vector3f.UNIT_Y); + //TODO: properly scale input based on current scaling level + tpf = tpf * (10 - (9.0f * currentLevel.getCurrentScaleAmount())); + if (name.equals("StrafeLeft") && value > 0) { + walkDirection.addLocal(left.mult(tpf)); + } else if (name.equals("StrafeRight") && value > 0) { + walkDirection.addLocal(left.negate().multLocal(tpf)); + } else if (name.equals("Forward") && value > 0) { + walkDirection.addLocal(forward.mult(tpf)); + } else if (name.equals("Back") && value > 0) { + walkDirection.addLocal(forward.negate().multLocal(tpf)); + } else if (name.equals("StrafeUp") && value > 0) { + walkDirection.addLocal(up.mult(tpf)); + } else if (name.equals("StrafeDown") && value > 0) { + walkDirection.addLocal(up.negate().multLocal(tpf)); + } else if (name.equals("Up") && value > 0) { + //TODO: rotate rootNode, needs to be global + } else if (name.equals("Down") && value > 0) { + } else if (name.equals("Left") && value > 0) { + } else if (name.equals("Right") && value > 0) { + } else if (name.equals("Esc")) { + stop(); + } + } + + @Override + public void simpleUpdate(float tpf) { + currentLevel = currentLevel.getCurrentLevel(); + currentLevel.move(walkDirection); + fpsText.setText("Location: " + currentLevel.getCoordinates()); + walkDirection.set(Vector3f.ZERO); + } + + public static class InceptionLevel extends AbstractAppState { + + private final InceptionLevel parent; + private final Vector3f inParentPosition; + private SimpleApplication application; + private BulletAppState physicsState; + private Node rootNode; + private Vector3f playerPos; + private InceptionLevel currentActiveChild; + private InceptionLevel currentReturnLevel; + private float curScaleAmount = 0; + + public InceptionLevel(InceptionLevel parent, Vector3f inParentPosition) { + this.parent = parent; + this.inParentPosition = inParentPosition; + } + + @Override + public void update(float tpf) { + super.update(tpf); + if (currentReturnLevel != this) { + return; + } + debugTools.setYellowArrow(new Vector3f(0, 0, -2), playerPos.divide(poiRadius)); + float curLocalDist = getPlayerPosition().length(); + // If we are outside the range of one point of interest, move out to + // the next upper level + if (curLocalDist > poiRadius + FastMath.ZERO_TOLERANCE) { //DAFUQ normalize? + if (parent == null) { + //TODO: could add new nodes coming in instead for literally endless space + logger.log(Level.INFO, "Hit event horizon"); + currentReturnLevel = this; + return; + } + //give to parent + logger.log(Level.INFO, "give to parent");; + parent.takeOverChild(inParentPosition.add(playerPos.normalize())); + application.getStateManager().attach(parent); + currentReturnLevel = parent; + return; + } + + AppStateManager stateManager = application.getStateManager(); + // We create child positions based on the parent position hash so we + // should in practice get the same galaxy w/o too many doubles + // with each run with the same root vector. + Vector3f[] vectors = getPositions(poiCount, inParentPosition.hashCode()); + for (int i = 0; i < vectors.length; i++) { + Vector3f vector3f = vectors[i]; + //negative rootNode location is our actual player position + Vector3f distVect = vector3f.subtract(playerPos); + float distance = distVect.length(); + if (distance <= 1) { + checkActiveChild(vector3f); + float percent = 0; + curScaleAmount = 0; + this.scaleAsParent(percent, playerPos, distVect); + currentActiveChild.scaleAsChild(percent, distVect); + logger.log(Level.INFO, "Give over to child {0}", currentActiveChild); + currentActiveChild.takeOverParent(); + stateManager.detach(this); + currentReturnLevel = currentActiveChild; + return; + } else if (distance <= 1 + scaleDist) { + debugTools.setRedArrow(Vector3f.ZERO, distVect); + checkActiveChild(vector3f); + //TODO: scale percent nicer for less of an "explosion" effect + float percent = 1 - mapValue(distance - 1, 0, scaleDist, 0, 1); + curScaleAmount = percent; + rootNode.getChild(i).setCullHint(Spatial.CullHint.Always); + this.scaleAsParent(percent, playerPos, distVect); + currentActiveChild.scaleAsChild(percent, distVect); + currentReturnLevel = this; + return; + } else if (currentActiveChild != null && currentActiveChild.getPositionInParent().equals(vector3f)) { + //TODO: doing this here causes problems when close to multiple pois + rootNode.getChild(i).setCullHint(Spatial.CullHint.Inherit); + } + } + checkActiveChild(null); + curScaleAmount = 0; + rootNode.setLocalScale(1); + rootNode.setLocalTranslation(playerPos.negate()); + debugTools.setRedArrow(Vector3f.ZERO, Vector3f.ZERO); + debugTools.setBlueArrow(Vector3f.ZERO, Vector3f.ZERO); + debugTools.setGreenArrow(Vector3f.ZERO, Vector3f.ZERO); + } + + private void checkActiveChild(Vector3f vector3f) { + AppStateManager stateManager = application.getStateManager(); + if(vector3f == null){ + if(currentActiveChild != null){ + logger.log(Level.INFO, "Detach child {0}", currentActiveChild); + stateManager.detach(currentActiveChild); + currentActiveChild = null; + } + return; + } + if (currentActiveChild == null) { + currentActiveChild = new InceptionLevel(this, vector3f); + stateManager.attach(currentActiveChild); + logger.log(Level.INFO, "Attach child {0}", currentActiveChild); + } else if (!currentActiveChild.getPositionInParent().equals(vector3f)) { + logger.log(Level.INFO, "Switching from child {0}", currentActiveChild); + stateManager.detach(currentActiveChild); + currentActiveChild = new InceptionLevel(this, vector3f); + stateManager.attach(currentActiveChild); + logger.log(Level.INFO, "Attach child {0}", currentActiveChild); + } + } + + private void scaleAsChild(float percent, Vector3f dist) { + float childScale = mapValue(percent, 1.0f / poiRadius, 1); + Vector3f distToHorizon = dist.normalize(); + Vector3f scaledDistToHorizon = distToHorizon.mult(childScale * poiRadius); + Vector3f rootOff = dist.add(scaledDistToHorizon); + debugTools.setBlueArrow(Vector3f.ZERO, rootOff); + getRootNode().setLocalScale(childScale); + getRootNode().setLocalTranslation(rootOff); + //prepare player position already + Vector3f playerPosition = dist.normalize().mult(-poiRadius); + setPlayerPosition(playerPosition); + } + + private void scaleAsParent(float percent, Vector3f playerPos, Vector3f dist) { + float scale = mapValue(percent, 1.0f, poiRadius); + Vector3f distToHorizon = dist.subtract(dist.normalize()); + Vector3f offLocation = playerPos.add(distToHorizon); + Vector3f rootOff = offLocation.mult(scale).negate(); + rootOff.addLocal(dist); + debugTools.setGreenArrow(Vector3f.ZERO, offLocation); + getRootNode().setLocalScale(scale); + getRootNode().setLocalTranslation(rootOff); + } + + public void takeOverParent() { + //got playerPos from scaleAsChild before + getPlayerPosition().normalizeLocal().multLocal(poiRadius); + currentReturnLevel = this; + } + + public void takeOverChild(Vector3f playerPos) { + this.playerPos.set(playerPos); + currentReturnLevel = this; + } + + public InceptionLevel getLastLevel(Ray pickRay) { + // TODO: get a level based on positions getting ever more accurate, + // from any given position + return null; + } + + public InceptionLevel getLevel(Vector3f... location) { + // TODO: get a level based on positions getting ever more accurate, + // from any given position + return null; + } + + private void initData() { + getRootNode(); + physicsState = new BulletAppState(); + physicsState.startPhysics(); + physicsState.getPhysicsSpace().setGravity(Vector3f.ZERO); + //horizon + physicsState.getPhysicsSpace().add(new RigidBodyControl(poiHorizonCollisionShape, 0)); + int hashCode = inParentPosition.hashCode(); + Vector3f[] positions = getPositions(poiCount, hashCode); + for (int i = 0; i < positions.length; i++) { + Vector3f vector3f = positions[i]; + Geometry poiGeom = new Geometry("poi", poiMesh); + poiGeom.setLocalTranslation(vector3f); + poiGeom.setMaterial(poiMaterial); + RigidBodyControl control = new RigidBodyControl(poiCollisionShape, 0); + //!!! Important + control.setApplyPhysicsLocal(true); + poiGeom.addControl(control); + physicsState.getPhysicsSpace().add(poiGeom); + rootNode.attachChild(poiGeom); + + } + //add balls after so first 10 geoms == locations + for (int i = 0; i < positions.length; i++) { + Vector3f vector3f = positions[i]; + Geometry ball = getRandomBall(vector3f); + physicsState.getPhysicsSpace().add(ball); + rootNode.attachChild(ball); + } + + } + + private Geometry getRandomBall(Vector3f location) { + Vector3f localLocation = new Vector3f(); + localLocation.set(location); + localLocation.addLocal(new Vector3f(random.nextFloat() - 0.5f, random.nextFloat() - 0.5f, random.nextFloat() - 0.5f).normalize().mult(3)); + Geometry poiGeom = new Geometry("ball", ballMesh); + poiGeom.setLocalTranslation(localLocation); + poiGeom.setMaterial(ballMaterial); + RigidBodyControl control = new RigidBodyControl(ballCollisionShape, 1); + //!!! Important + control.setApplyPhysicsLocal(true); + poiGeom.addControl(control); + float x = (random.nextFloat() - 0.5f) * 100; + float y = (random.nextFloat() - 0.5f) * 100; + float z = (random.nextFloat() - 0.5f) * 100; + control.setLinearVelocity(new Vector3f(x, y, z)); + return poiGeom; + } + + private void cleanupData() { + physicsState.stopPhysics(); + //TODO: remove all objects? + physicsState = null; + rootNode = null; + } + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + //only generate data and attach node when we are actually attached (or picking) + initData(); + application = (SimpleApplication) app; + application.getRootNode().attachChild(getRootNode()); + application.getStateManager().attach(physicsState); + } + + @Override + public void cleanup() { + super.cleanup(); + //detach everything when we are detached + application.getRootNode().detachChild(rootNode); + application.getStateManager().detach(physicsState); + cleanupData(); + } + + public Node getRootNode() { + if (rootNode == null) { + rootNode = new Node("ZoomLevel"); + if (parent != null) { + rootNode.setLocalScale(1.0f / poiRadius); + } + } + return rootNode; + } + + public Vector3f getPositionInParent() { + return inParentPosition; + } + + public Vector3f getPlayerPosition() { + if (playerPos == null) { + playerPos = new Vector3f(); + } + return playerPos; + } + + public void setPlayerPosition(Vector3f vec) { + if (playerPos == null) { + playerPos = new Vector3f(); + } + playerPos.set(vec); + } + + public void move(Vector3f dir) { + if (playerPos == null) { + playerPos = new Vector3f(); + } + playerPos.addLocal(dir); + } + + public float getCurrentScaleAmount() { + return curScaleAmount; + } + + public InceptionLevel getParent() { + return parent; + } + + public InceptionLevel getCurrentLevel() { + return currentReturnLevel; + } + + public String getCoordinates() { + InceptionLevel cur = this; + StringBuilder strb = new StringBuilder(); + strb.insert(0, this.getPlayerPosition()); + strb.insert(0, this.getPositionInParent() + " / "); + cur = cur.getParent(); + while (cur != null) { + strb.insert(0, cur.getPositionInParent() + " / "); + cur = cur.getParent(); + } + return strb.toString(); + } + } + + public static Vector3f[] getPositions(int count, long seed) { + Random rnd = new Random(seed); + Vector3f[] vectors = new Vector3f[count]; + for (int i = 0; i < count; i++) { + vectors[i] = new Vector3f((rnd.nextFloat() - 0.5f) * poiRadius, + (rnd.nextFloat() - 0.5f) * poiRadius, + (rnd.nextFloat() - 0.5f) * poiRadius); + } + return vectors; + } + + /** + * Maps a value from 0-1 to a range from min to max. + * + * @param x + * @param min + * @param max + * @return + */ + public static float mapValue(float x, float min, float max) { + return mapValue(x, 0, 1, min, max); + } + + /** + * Maps a value from inputMin to inputMax to a range from min to max. + * + * @param x + * @param inputMin + * @param inputMax + * @param min + * @param max + * @return + */ + public static float mapValue(float x, float inputMin, float inputMax, float min, float max) { + return (x - inputMin) * (max - min) / (inputMax - inputMin) + min; + } +} diff --git a/jme3-examples/src/main/java/jme3test/gui/TestBitmapFont.java b/jme3-examples/src/main/java/jme3test/gui/TestBitmapFont.java new file mode 100644 index 000000000..11fc69abf --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/gui/TestBitmapFont.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.gui; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.font.LineWrapMode; +import com.jme3.font.Rectangle; +import com.jme3.input.KeyInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.event.*; + +public class TestBitmapFont extends SimpleApplication { + + private String txtB = + "ABCDEFGHIKLMNOPQRSTUVWXYZ1234567 890`~!@#$%^&*()-=_+[]\\;',./{}|:<>?"; + + private BitmapText txt; + private BitmapText txt2; + private BitmapText txt3; + + public static void main(String[] args){ + TestBitmapFont app = new TestBitmapFont(); + app.start(); + } + + @Override + public void simpleInitApp() { + inputManager.addMapping("WordWrap", new KeyTrigger(KeyInput.KEY_TAB)); + inputManager.addListener(keyListener, "WordWrap"); + inputManager.addRawInputListener(textListener); + + BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt"); + txt = new BitmapText(fnt, false); + txt.setBox(new Rectangle(0, 0, settings.getWidth(), settings.getHeight())); + txt.setSize(fnt.getPreferredSize() * 2f); + txt.setText(txtB); + txt.setLocalTranslation(0, txt.getHeight(), 0); + guiNode.attachChild(txt); + + txt2 = new BitmapText(fnt, false); + txt2.setSize(fnt.getPreferredSize() * 1.2f); + txt2.setText("Text without restriction. \nText without restriction. Text without restriction. Text without restriction"); + txt2.setLocalTranslation(0, txt2.getHeight(), 0); + guiNode.attachChild(txt2); + + txt3 = new BitmapText(fnt, false); + txt3.setBox(new Rectangle(0, 0, settings.getWidth(), 0)); + txt3.setText("Press Tab to toggle word-wrap. type text and enter to input text"); + txt3.setLocalTranslation(0, settings.getHeight()/2, 0); + guiNode.attachChild(txt3); + } + + private ActionListener keyListener = new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("WordWrap") && !isPressed) { + txt.setLineWrapMode( txt.getLineWrapMode() == LineWrapMode.Word ? + LineWrapMode.NoWrap : LineWrapMode.Word ); + } + } + }; + + private RawInputListener textListener = new RawInputListener() { + private StringBuilder str = new StringBuilder(); + + @Override + public void onMouseMotionEvent(MouseMotionEvent evt) { } + + @Override + public void onMouseButtonEvent(MouseButtonEvent evt) { } + + @Override + public void onKeyEvent(KeyInputEvent evt) { + if (evt.isReleased()) + return; + if (evt.getKeyChar() == '\n' || evt.getKeyChar() == '\r') { + txt3.setText(str.toString()); + str.setLength(0); + } else { + str.append(evt.getKeyChar()); + } + } + + @Override + public void onJoyButtonEvent(JoyButtonEvent evt) { } + + @Override + public void onJoyAxisEvent(JoyAxisEvent evt) { } + + @Override + public void endInput() { } + + @Override + public void beginInput() { } + + @Override + public void onTouchEvent(TouchEvent evt) { } + + }; + +} diff --git a/jme3-examples/src/main/java/jme3test/gui/TestBitmapText3D.java b/jme3-examples/src/main/java/jme3test/gui/TestBitmapText3D.java new file mode 100644 index 000000000..ce32e7a4d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/gui/TestBitmapText3D.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/gui/TestCursor.java b/jme3-examples/src/main/java/jme3test/gui/TestCursor.java new file mode 100644 index 000000000..d2b10fc89 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/gui/TestCursor.java @@ -0,0 +1,77 @@ +package jme3test.gui; + +import com.jme3.app.SimpleApplication; +import com.jme3.cursors.plugins.JmeCursor; +import java.util.ArrayList; + +/** + * This test class demonstrate how to change cursor in jME3. + * + * NOTE: This will not work on Android as it does not support cursors. + * + * Cursor test + * @author MadJack + */ +public class TestCursor extends SimpleApplication { + + private ArrayList cursors = new ArrayList(); + private long sysTime; + private int count = 0; + + public static void main(String[] args){ + TestCursor app = new TestCursor(); + + app.setShowSettings(false); + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setEnabled(false); + // We need the cursor to be visible. If it is not visible the cursor + // will still be "used" and loaded, you just won't see it on the screen. + inputManager.setCursorVisible(true); + + /* + * To make jME3 use a custom cursor it is as simple as putting the + * .cur/.ico/.ani file in an asset directory. Here we use + * "Textures/GUI/Cursors". + * + * For the purpose of this demonstration we load 3 different cursors and add them + * into an array list and switch cursor every 8 seconds. + * + * The first ico has been made by Sirea and the set can be found here: + * http://www.rw-designer.com/icon-set/nyan-cat + * + * The second cursor has been made by Virum64 and is Public Domain. + * http://www.rw-designer.com/cursor-set/memes-faces-v64 + * + * The animated cursor has been made by Pointer Adic and can be found here: + * http://www.rw-designer.com/cursor-set/monkey + */ + cursors.add((JmeCursor) assetManager.loadAsset("Textures/Cursors/meme.cur")); + cursors.add((JmeCursor) assetManager.loadAsset("Textures/Cursors/nyancat.ico")); + cursors.add((JmeCursor) assetManager.loadAsset("Textures/Cursors/monkey.ani")); + + sysTime = System.currentTimeMillis(); + inputManager.setMouseCursor(cursors.get(count)); + } + + @Override + public void simpleUpdate(float tpf) { + long currentTime = System.currentTimeMillis(); + + if (currentTime - sysTime > 8000) { + count++; + if (count >= cursors.size()) { + count = 0; + } + sysTime = currentTime; + // 8 seconds have passed, + // tell jME3 to swith to a different cursor. + inputManager.setMouseCursor(cursors.get(count)); + } + + } +} + diff --git a/jme3-examples/src/main/java/jme3test/gui/TestOrtho.java b/jme3-examples/src/main/java/jme3test/gui/TestOrtho.java new file mode 100644 index 000000000..2e252aad3 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/gui/TestOrtho.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.gui; + +import com.jme3.app.SimpleApplication; +import com.jme3.ui.Picture; + +public class TestOrtho extends SimpleApplication { + + public static void main(String[] args){ + TestOrtho app = new TestOrtho(); + app.start(); + } + + public void simpleInitApp() { + Picture p = new Picture("Picture"); + p.move(0, 0, -1); // make it appear behind stats view + p.setPosition(0, 0); + p.setWidth(settings.getWidth()); + p.setHeight(settings.getHeight()); + p.setImage(assetManager, "Interface/Logo/Monkey.png", false); + + // attach geometry to orthoNode + guiNode.attachChild(p); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/gui/TestSoftwareMouse.java b/jme3-examples/src/main/java/jme3test/gui/TestSoftwareMouse.java new file mode 100644 index 000000000..44bee2882 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/gui/TestSoftwareMouse.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.gui; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.*; +import com.jme3.math.FastMath; +import com.jme3.system.AppSettings; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.ui.Picture; + +public class TestSoftwareMouse extends SimpleApplication { + + private Picture cursor; + + private RawInputListener inputListener = new RawInputListener() { + + private float x = 0, y = 0; + + public void beginInput() { + } + public void endInput() { + } + public void onJoyAxisEvent(JoyAxisEvent evt) { + } + public void onJoyButtonEvent(JoyButtonEvent evt) { + } + public void onMouseMotionEvent(MouseMotionEvent evt) { + x += evt.getDX(); + y += evt.getDY(); + + // Prevent mouse from leaving screen + AppSettings settings = TestSoftwareMouse.this.settings; + x = FastMath.clamp(x, 0, settings.getWidth()); + y = FastMath.clamp(y, 0, settings.getHeight()); + + // adjust for hotspot + cursor.setPosition(x, y - 64); + } + public void onMouseButtonEvent(MouseButtonEvent evt) { + } + public void onKeyEvent(KeyInputEvent evt) { + } + public void onTouchEvent(TouchEvent evt) { + } + }; + + public static void main(String[] args){ + TestSoftwareMouse app = new TestSoftwareMouse(); + +// AppSettings settings = new AppSettings(true); +// settings.setFrameRate(60); +// app.setSettings(settings); + + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setEnabled(false); +// inputManager.setCursorVisible(false); + + Texture tex = assetManager.loadTexture("Interface/Logo/Cursor.png"); + + cursor = new Picture("cursor"); + cursor.setTexture(assetManager, (Texture2D) tex, true); + cursor.setWidth(64); + cursor.setHeight(64); + guiNode.attachChild(cursor); + + inputManager.addRawInputListener(inputListener); + +// Image img = tex.getImage(); +// ByteBuffer data = img.getData(0); +// IntBuffer image = BufferUtils.createIntBuffer(64 * 64); +// for (int y = 0; y < 64; y++){ +// for (int x = 0; x < 64; x++){ +// int rgba = data.getInt(); +// image.put(rgba); +// } +// } +// image.clear(); +// +// try { +// Cursor cur = new Cursor(64, 64, 2, 62, 1, image, null); +// Mouse.setNativeCursor(cur); +// } catch (LWJGLException ex) { +// Logger.getLogger(TestSoftwareMouse.class.getName()).log(Level.SEVERE, null, ex); +// } + } +} diff --git a/jme3-examples/src/main/java/jme3test/gui/TestZOrder.java b/jme3-examples/src/main/java/jme3test/gui/TestZOrder.java new file mode 100644 index 000000000..5a5683f68 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/gui/TestZOrder.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/helloworld/HelloAnimation.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloAnimation.java new file mode 100644 index 000000000..bb1587b3e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloAnimation.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/helloworld/HelloAssets.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloAssets.java new file mode 100644 index 000000000..9a3085016 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloAssets.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; + +/** Sample 3 - how to load an OBJ model, and OgreXML model, + * a material/texture, or text. */ +public class HelloAssets extends SimpleApplication { + + public static void main(String[] args) { + HelloAssets app = new HelloAssets(); + app.start(); + } + + @Override + public void simpleInitApp() { + + /** Load a teapot model (OBJ file from test-data) */ + Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + Material mat_default = new Material( assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); + teapot.setMaterial(mat_default); + rootNode.attachChild(teapot); + + /** Create a wall (Box with material and texture from test-data) */ + Box box = new Box(Vector3f.ZERO, 2.5f,2.5f,1.0f); + Spatial wall = new Geometry("Box", box ); + Material mat_brick = new Material( assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat_brick.setTexture("ColorMap", assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg")); + wall.setMaterial(mat_brick); + wall.setLocalTranslation(2.0f,-2.5f,0.0f); + rootNode.attachChild(wall); + + /** Display a line of text (default font from test-data) */ + setDisplayStatView(false); + 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/jme3-examples/src/main/java/jme3test/helloworld/HelloAudio.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloAudio.java new file mode 100644 index 000000000..b9943879c --- /dev/null +++ b/jme3-examples/src/main/java/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.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.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(1, 1, 1); + player = new Geometry("Player", box1); + Material mat1 = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md"); + mat1.setColor("Color", ColorRGBA.Blue); + player.setMaterial(mat1); + rootNode.attachChild(player); + + /** custom init methods, see below */ + initKeys(); + initAudio(); + } + + /** We create two audio nodes. */ + private void initAudio() { + /* gun shot sound is to be triggered by a mouse click. */ + audio_gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav", false); + audio_gun.setPositional(false); + audio_gun.setLooping(false); + audio_gun.setVolume(2); + rootNode.attachChild(audio_gun); + + /* nature sound - keeps playing in a loop. */ + audio_nature = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", true); + audio_nature.setLooping(true); // activate continuous playing + audio_nature.setPositional(true); + audio_nature.setVolume(3); + rootNode.attachChild(audio_nature); + audio_nature.play(); // play continuously! + } + + /** Declaring "Shoot" action, mapping it to a trigger (mouse left click). */ + private void initKeys() { + inputManager.addMapping("Shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(actionListener, "Shoot"); + } + + /** Defining the "Shoot" action: Play a gun sound. */ + private ActionListener actionListener = new ActionListener() { + @Override + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("Shoot") && !keyPressed) { + audio_gun.playInstance(); // play each instance once! + } + } + }; + + /** Move the listener with the a camera - for 3D audio. */ + @Override + public void simpleUpdate(float tpf) { + listener.setLocation(cam.getLocation()); + listener.setRotation(cam.getRotation()); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloCollision.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloCollision.java new file mode 100644 index 000000000..60db3b052 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloCollision.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.ZipLocator; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; + +/** + * Example 9 - How to make walls and floors solid. + * This collision code uses Physics and a custom Action Listener. + * @author normen, with edits by Zathras + */ +public class HelloCollision extends SimpleApplication + implements ActionListener { + + private Spatial sceneModel; + private BulletAppState bulletAppState; + private RigidBodyControl landscape; + private CharacterControl player; + private Vector3f walkDirection = new Vector3f(); + private boolean left = false, right = false, up = false, down = false; + + //Temporary vectors used on each frame. + //They here to avoid instanciating new vectors on each frame + private Vector3f camDir = new Vector3f(); + private Vector3f camLeft = new Vector3f(); + + public static void main(String[] args) { + HelloCollision app = new HelloCollision(); + app.start(); + } + + public void simpleInitApp() { + /** Set up Physics */ + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + //bulletAppState.getPhysicsSpace().enableDebug(assetManager); + + // We re-use the flyby camera for rotation, while positioning is handled by physics + viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)); + flyCam.setMoveSpeed(100); + setUpKeys(); + setUpLight(); + + // We load the scene from the zip file and adjust its size. + assetManager.registerLocator("town.zip", ZipLocator.class); + sceneModel = assetManager.loadModel("main.scene"); + sceneModel.setLocalScale(2f); + + // We set up collision detection for the scene by creating a + // compound collision shape and a static RigidBodyControl with mass zero. + CollisionShape sceneShape = + CollisionShapeFactory.createMeshShape((Node) sceneModel); + landscape = new RigidBodyControl(sceneShape, 0); + sceneModel.addControl(landscape); + + // We set up collision detection for the player by creating + // a capsule collision shape and a CharacterControl. + // The CharacterControl offers extra settings for + // size, stepheight, jumping, falling, and gravity. + // We also put the player in its starting position. + CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1); + player = new CharacterControl(capsuleShape, 0.05f); + player.setJumpSpeed(20); + player.setFallSpeed(30); + player.setGravity(30); + player.setPhysicsLocation(new Vector3f(0, 10, 0)); + + // We attach the scene and the player to the rootnode and the physics space, + // to make them appear in the game world. + rootNode.attachChild(sceneModel); + bulletAppState.getPhysicsSpace().add(landscape); + bulletAppState.getPhysicsSpace().add(player); + } + + private void setUpLight() { + // We add light so we see the scene + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(1.3f)); + rootNode.addLight(al); + + DirectionalLight dl = new DirectionalLight(); + dl.setColor(ColorRGBA.White); + dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal()); + rootNode.addLight(dl); + } + + /** We over-write some navigational key mappings here, so we can + * add physics-controlled walking and jumping: */ + private void setUpKeys() { + inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A)); + inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D)); + inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S)); + inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(this, "Left"); + inputManager.addListener(this, "Right"); + inputManager.addListener(this, "Up"); + inputManager.addListener(this, "Down"); + inputManager.addListener(this, "Jump"); + } + + /** These are our custom actions triggered by key presses. + * We do not walk yet, we just keep track of the direction the user pressed. */ + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("Left")) { + if (value) { left = true; } else { left = false; } + } else if (binding.equals("Right")) { + if (value) { right = true; } else { right = false; } + } else if (binding.equals("Up")) { + if (value) { up = true; } else { up = false; } + } else if (binding.equals("Down")) { + if (value) { down = true; } else { down = false; } + } else if (binding.equals("Jump")) { + player.jump(); + } + } + + /** + * This is the main event loop--walking happens here. + * We check in which direction the player is walking by interpreting + * the camera direction forward (camDir) and to the side (camLeft). + * The setWalkDirection() command is what lets a physics-controlled player walk. + * We also make sure here that the camera moves with player. + */ + @Override + public void simpleUpdate(float tpf) { + camDir.set(cam.getDirection()).multLocal(0.6f); + camLeft.set(cam.getLeft()).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/jme3-examples/src/main/java/jme3test/helloworld/HelloEffects.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloEffects.java new file mode 100644 index 000000000..1210c7040 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloEffects.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; + +/** Sample 11 - how to create fire, water, and explosion effects. */ +public class HelloEffects extends SimpleApplication { + + public static void main(String[] args) { + HelloEffects app = new HelloEffects(); + app.start(); + } + + @Override + public void simpleInitApp() { + ParticleEmitter fire = + new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30); + Material mat_red = new Material(assetManager, + "Common/MatDefs/Misc/Particle.j3md"); + mat_red.setTexture("Texture", assetManager.loadTexture( + "Effects/Explosion/flame.png")); + fire.setMaterial(mat_red); + fire.setImagesX(2); + fire.setImagesY(2); // 2x2 texture animation + fire.setEndColor( new ColorRGBA(1f, 0f, 0f, 1f)); // red + fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow + fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 2, 0)); + fire.setStartSize(1.5f); + fire.setEndSize(0.1f); + fire.setGravity(0, 0, 0); + fire.setLowLife(1f); + fire.setHighLife(3f); + fire.getParticleInfluencer().setVelocityVariation(0.3f); + rootNode.attachChild(fire); + + ParticleEmitter debris = + new ParticleEmitter("Debris", ParticleMesh.Type.Triangle, 10); + Material debris_mat = new Material(assetManager, + "Common/MatDefs/Misc/Particle.j3md"); + debris_mat.setTexture("Texture", assetManager.loadTexture( + "Effects/Explosion/Debris.png")); + debris.setMaterial(debris_mat); + debris.setImagesX(3); + debris.setImagesY(3); // 3x3 texture animation + debris.setSelectRandomImage(true); + debris.setRotateSpeed(4); + debris.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 4, 0)); + debris.setStartColor(ColorRGBA.White); + debris.setGravity(0, 6, 0); + debris.getParticleInfluencer().setVelocityVariation(.60f); + rootNode.attachChild(debris); + debris.emitAllParticles(); + +// ParticleEmitter water = +// new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 20); +// Material mat_blue = new Material(assetManager, +// "Common/MatDefs/Misc/Particle.j3md"); +// mat_blue.setTexture("Texture", assetManager.loadTexture( +// "Effects/Explosion/flame.png")); +// water.setMaterial(mat_blue); +// water.setImagesX(2); +// water.setImagesY(2); // 2x2 texture animation +// water.setStartColor( ColorRGBA.Blue); +// water.setEndColor( ColorRGBA.Cyan); +// water.getParticleInfluencer().setInitialVelocity(new Vector3f(0, -4, 0)); +// water.setStartSize(1f); +// water.setEndSize(1.5f); +// water.setGravity(0,1,0); +// water.setLowLife(1f); +// water.setHighLife(1f); +// water.getParticleInfluencer().setVelocityVariation(0.1f); +// water.setLocalTranslation(0, 6, 0); +// rootNode.attachChild(water); + + } +} diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloInput.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloInput.java new file mode 100644 index 000000000..4cf09fe6e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloInput.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; + +/** Sample 5 - how to map keys and mousebuttons to actions */ +public class HelloInput extends SimpleApplication { + + public static void main(String[] args) { + HelloInput app = new HelloInput(); + app.start(); + } + protected Geometry player; + Boolean isRunning=true; + + @Override + public void simpleInitApp() { + Box b = new Box(Vector3f.ZERO, 1, 1, 1); + player = new Geometry("Player", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Blue); + player.setMaterial(mat); + rootNode.attachChild(player); + initKeys(); // load my custom keybinding + } + + /** Custom Keybinding: Map named actions to inputs. */ + private void initKeys() { + /** You can map one or several inputs to one named mapping. */ + inputManager.addMapping("Pause", new KeyTrigger(keyInput.KEY_P)); + inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_K)); + inputManager.addMapping("Rotate", new KeyTrigger(KeyInput.KEY_SPACE), // spacebar! + new MouseButtonTrigger(MouseInput.BUTTON_LEFT) ); // left click! + /** Add the named mappings to the action listeners. */ + inputManager.addListener(actionListener,"Pause"); + inputManager.addListener(analogListener,"Left", "Right", "Rotate"); + } + + /** Use this listener for KeyDown/KeyUp events */ + private ActionListener actionListener = new ActionListener() { + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("Pause") && !keyPressed) { + isRunning = !isRunning; + } + } + }; + + /** Use this listener for continuous events */ + private AnalogListener analogListener = new AnalogListener() { + public void onAnalog(String name, float value, float tpf) { + if (isRunning) { + if (name.equals("Rotate")) { + player.rotate(0, value, 0); + } + if (name.equals("Right")) { + player.move((new Vector3f(value, 0,0)) ); + } + if (name.equals("Left")) { + player.move(new Vector3f(-value, 0,0)); + } + } else { + System.out.println("Press P to unpause."); + } + } + }; + +} diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloJME3.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloJME3.java new file mode 100644 index 000000000..9ffc1931d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloJME3.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; + +/** Sample 1 - how to get started with the most simple JME 3 application. + * Display a blue 3D cube and view from all sides by + * moving the mouse and pressing the WASD keys. */ +public class HelloJME3 extends SimpleApplication { + + public static void main(String[] args){ + HelloJME3 app = new HelloJME3(); + app.start(); // start the game + } + + @Override + public void simpleInitApp() { + Box b = new Box(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/Unshaded.j3md"); // create a simple material + mat.setColor("Color", ColorRGBA.Blue); // set color of material to blue + geom.setMaterial(mat); // set the cube's material + rootNode.attachChild(geom); // make the cube appear in the scene + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloLoop.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloLoop.java new file mode 100644 index 000000000..f154a05cf --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloLoop.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; + +/** Sample 4 - how to trigger repeating actions from the main event loop. + * In this example, you use the loop to make the player character + * rotate continuously. */ +public class HelloLoop extends SimpleApplication { + + public static void main(String[] args){ + HelloLoop app = new HelloLoop(); + app.start(); + } + + protected Geometry player; + + @Override + public void simpleInitApp() { + /** this blue box is our player character */ + Box b = new Box(1, 1, 1); + player = new Geometry("blue cube", b); + Material mat = new Material(assetManager, + "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Blue); + player.setMaterial(mat); + rootNode.attachChild(player); + } + + /* Use the main event loop to trigger repeating actions. */ + @Override + public void simpleUpdate(float tpf) { + // make the player rotate: + player.rotate(0, 2*tpf, 0); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloMaterial.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloMaterial.java new file mode 100644 index 000000000..9d9349206 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloMaterial.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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. 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 cube1Mesh = new Box( 1f,1f,1f); + Geometry cube1Geo = new Geometry("My Textured Box", cube1Mesh); + cube1Geo.setLocalTranslation(new Vector3f(-3f,1.1f,0f)); + Material cube1Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + Texture cube1Tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg"); + cube1Mat.setTexture("ColorMap", cube1Tex); + cube1Geo.setMaterial(cube1Mat); + rootNode.attachChild(cube1Geo); + + /** A translucent/transparent texture, similar to a window frame. */ + Box cube2Mesh = new Box( 1f,1f,0.01f); + Geometry cube2Geo = new Geometry("window frame", cube2Mesh); + Material cube2Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + cube2Mat.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png")); + cube2Mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); // activate transparency + cube2Geo.setQueueBucket(Bucket.Transparent); + cube2Geo.setMaterial(cube2Mat); + rootNode.attachChild(cube2Geo); + + /** A bumpy rock with a shiny light effect. To make bumpy objects you must create a NormalMap. */ + Sphere sphereMesh = new Sphere(32,32, 2f); + Geometry sphereGeo = new Geometry("Shiny rock", sphereMesh); + sphereMesh.setTextureMode(Sphere.TextureMode.Projected); // better quality on spheres + TangentBinormalGenerator.generate(sphereMesh); // for lighting effect + Material sphereMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + sphereMat.setTexture("DiffuseMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg")); + sphereMat.setTexture("NormalMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png")); + sphereMat.setBoolean("UseMaterialColors",true); + sphereMat.setColor("Diffuse",ColorRGBA.White); + sphereMat.setColor("Specular",ColorRGBA.White); + sphereMat.setFloat("Shininess", 64f); // [0,128] + sphereGeo.setMaterial(sphereMat); + //sphereGeo.setMaterial((Material) assetManager.loadMaterial("Materials/MyCustomMaterial.j3m")); + sphereGeo.setLocalTranslation(0,2,-2); // Move it a bit + sphereGeo.rotate(1.6f, 0, 0); // Rotate it a bit + rootNode.attachChild(sphereGeo); + + /** 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/jme3-examples/src/main/java/jme3test/helloworld/HelloNode.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloNode.java new file mode 100644 index 000000000..d70632f33 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloNode.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; + +/** Sample 2 - How to use nodes as handles to manipulate objects in the scene. + * You can rotate, translate, and scale objects by manipulating their parent nodes. + * The Root Node is special: Only what is attached to the Root Node appears in the scene. */ +public class HelloNode extends SimpleApplication { + + public static void main(String[] args){ + HelloNode app = new HelloNode(); + app.start(); + } + + @Override + public void simpleInitApp() { + + /** create a blue box at coordinates (1,-1,1) */ + Box box1 = new Box(1,1,1); + Geometry blue = new Geometry("Box", box1); + blue.setLocalTranslation(new Vector3f(1,-1,1)); + Material mat1 = new Material(assetManager, + "Common/MatDefs/Misc/Unshaded.j3md"); + mat1.setColor("Color", ColorRGBA.Blue); + blue.setMaterial(mat1); + + /** create a red box straight above the blue one at (1,3,1) */ + Box box2 = new Box(1,1,1); + Geometry red = new Geometry("Box", box2); + red.setLocalTranslation(new Vector3f(1,3,1)); + Material mat2 = new Material(assetManager, + "Common/MatDefs/Misc/Unshaded.j3md"); + mat2.setColor("Color", ColorRGBA.Red); + red.setMaterial(mat2); + + /** Create a pivot node at (0,0,0) and attach it to the root node */ + Node pivot = new Node("pivot"); + rootNode.attachChild(pivot); // put this node in the scene + + /** Attach the two boxes to the *pivot* node. (And transitively to the root node.) */ + pivot.attachChild(blue); + pivot.attachChild(red); + /** Rotate the pivot node: Note that both boxes have rotated! */ + pivot.rotate(.4f,.4f,0f); + } +} + diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloPhysics.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloPhysics.java new file mode 100644 index 000000000..f1355e03e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloPhysics.java @@ -0,0 +1,228 @@ +/** + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.font.BitmapText; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; + +/** + * Example 12 - how to give objects physical properties so they bounce and fall. + * @author base code by double1984, updated by zathras + */ +public class HelloPhysics extends SimpleApplication { + + public static void main(String args[]) { + HelloPhysics app = new HelloPhysics(); + app.start(); + } + + /** Prepare the Physics Application State (jBullet) */ + private BulletAppState bulletAppState; + + /** Prepare Materials */ + Material wall_mat; + Material stone_mat; + Material floor_mat; + + /** Prepare geometries and physical nodes for bricks and cannon balls. */ + private RigidBodyControl brick_phy; + private static final Box box; + private RigidBodyControl ball_phy; + private static final Sphere sphere; + private RigidBodyControl floor_phy; + private static final Box floor; + + /** dimensions used for bricks and wall */ + private static final float brickLength = 0.48f; + private static final float brickWidth = 0.24f; + private static final float brickHeight = 0.12f; + + static { + /** Initialize the cannon ball geometry */ + sphere = new Sphere(32, 32, 0.4f, true, false); + sphere.setTextureMode(TextureMode.Projected); + /** Initialize the brick geometry */ + box = new Box(brickLength, brickHeight, brickWidth); + box.scaleTextureCoordinates(new Vector2f(1f, .5f)); + /** Initialize the floor geometry */ + floor = new Box(10f, 0.1f, 5f); + floor.scaleTextureCoordinates(new Vector2f(3, 6)); + } + + @Override + public void simpleInitApp() { + /** Set up Physics Game */ + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + //bulletAppState.getPhysicsSpace().enableDebug(assetManager); + /** Configure cam to look at scene */ + cam.setLocation(new Vector3f(0, 4f, 6f)); + cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y); + /** Initialize the scene, materials, inputs, and physics space */ + initInputs(); + initMaterials(); + initWall(); + initFloor(); + initCrossHairs(); + } + + /** Add InputManager action: Left click triggers shooting. */ + private void initInputs() { + inputManager.addMapping("shoot", + new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(actionListener, "shoot"); + } + + /** + * Every time the shoot action is triggered, a new cannon ball is produced. + * The ball is set up to fly from the camera position in the camera direction. + */ + private ActionListener actionListener = new ActionListener() { + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("shoot") && !keyPressed) { + makeCannonBall(); + } + } + }; + + /** Initialize the materials used in this scene. */ + public void initMaterials() { + wall_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg"); + key.setGenerateMips(true); + Texture tex = assetManager.loadTexture(key); + wall_mat.setTexture("ColorMap", tex); + + stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG"); + key2.setGenerateMips(true); + Texture tex2 = assetManager.loadTexture(key2); + stone_mat.setTexture("ColorMap", tex2); + + floor_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg"); + key3.setGenerateMips(true); + Texture tex3 = assetManager.loadTexture(key3); + tex3.setWrap(WrapMode.Repeat); + floor_mat.setTexture("ColorMap", tex3); + } + + /** Make a solid floor and add it to the scene. */ + public void initFloor() { + Geometry floor_geo = new Geometry("Floor", floor); + floor_geo.setMaterial(floor_mat); + floor_geo.setLocalTranslation(0, -0.1f, 0); + this.rootNode.attachChild(floor_geo); + /* Make the floor physical with mass 0.0f! */ + floor_phy = new RigidBodyControl(0.0f); + floor_geo.addControl(floor_phy); + bulletAppState.getPhysicsSpace().add(floor_phy); + } + + /** This loop builds a wall out of individual bricks. */ + public void initWall() { + float startpt = brickLength / 4; + float height = 0; + for (int j = 0; j < 15; j++) { + for (int i = 0; i < 6; i++) { + Vector3f vt = + new Vector3f(i * brickLength * 2 + startpt, brickHeight + height, 0); + makeBrick(vt); + } + startpt = -startpt; + height += 2 * brickHeight; + } + } + + /** This method creates one individual physical brick. */ + public void makeBrick(Vector3f loc) { + /** Create a brick geometry and attach to scene graph. */ + Geometry brick_geo = new Geometry("brick", box); + brick_geo.setMaterial(wall_mat); + rootNode.attachChild(brick_geo); + /** Position the brick geometry */ + brick_geo.setLocalTranslation(loc); + /** Make brick physical with a mass > 0.0f. */ + brick_phy = new RigidBodyControl(2f); + /** Add physical brick to physics space. */ + brick_geo.addControl(brick_phy); + bulletAppState.getPhysicsSpace().add(brick_phy); + } + + /** This method creates one individual physical cannon ball. + * By defaul, the ball is accelerated and flies + * from the camera position in the camera direction.*/ + public void makeCannonBall() { + /** Create a cannon ball geometry and attach to scene graph. */ + Geometry ball_geo = new Geometry("cannon ball", sphere); + ball_geo.setMaterial(stone_mat); + rootNode.attachChild(ball_geo); + /** Position the cannon ball */ + ball_geo.setLocalTranslation(cam.getLocation()); + /** Make the ball physcial with a mass > 0.0f */ + ball_phy = new RigidBodyControl(1f); + /** Add physical ball to physics space. */ + ball_geo.addControl(ball_phy); + bulletAppState.getPhysicsSpace().add(ball_phy); + /** Accelerate the physcial ball to shoot it. */ + ball_phy.setLinearVelocity(cam.getDirection().mult(25)); + } + + /** A plus sign used as crosshairs to help the player with aiming.*/ + protected void initCrossHairs() { + setDisplayStatView(false); + //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, + settings.getHeight() / 2, 0); + guiNode.attachChild(ch); + } +} diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloPicking.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloPicking.java new file mode 100644 index 000000000..4c74b9012 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloPicking.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Ray; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; + +/** Sample 8 - how to let the user pick (select) objects in the scene + * using the mouse or key presses. Can be used for shooting, opening doors, etc. */ +public class HelloPicking extends SimpleApplication { + + public static void main(String[] args) { + HelloPicking app = new HelloPicking(); + app.start(); + } + private Node shootables; + private 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(1, 1, 1); + Geometry cube = new Geometry(name, box); + cube.setLocalTranslation(x, y, z); + Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat1.setColor("Color", ColorRGBA.randomColor()); + cube.setMaterial(mat1); + return cube; + } + + /** A floor to show that the "shot" can go through several objects. */ + protected Geometry makeFloor() { + Box box = new Box(15, .2f, 15); + Geometry floor = new Geometry("the Floor", box); + floor.setLocalTranslation(0, -4, -5); + Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat1.setColor("Color", ColorRGBA.Gray); + floor.setMaterial(mat1); + return floor; + } + + /** A red ball that marks the last spot that was "hit" by the "shot". */ + protected void initMark() { + Sphere sphere = new Sphere(30, 30, 0.2f); + mark = new Geometry("BOOM!", sphere); + Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mark_mat.setColor("Color", ColorRGBA.Red); + mark.setMaterial(mark_mat); + } + + /** A centred plus sign to help the player aim. */ + protected void initCrossHairs() { + setDisplayStatView(false); + 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 - ch.getLineWidth()/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/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrain.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrain.java new file mode 100644 index 000000000..69b8caf84 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrain.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; + +public class HelloTerrain extends SimpleApplication { + + private TerrainQuad terrain; + Material mat_terrain; + + public static void main(String[] args) { + HelloTerrain app = new HelloTerrain(); + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(50); + + /** 1. Create terrain material and load four textures into it. */ + mat_terrain = new Material(assetManager, + "Common/MatDefs/Terrain/Terrain.j3md"); + + /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */ + mat_terrain.setTexture("Alpha", assetManager.loadTexture( + "Textures/Terrain/splat/alphamap.png")); + + /** 1.2) Add GRASS texture into the red layer (Tex1). */ + Texture grass = assetManager.loadTexture( + "Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + mat_terrain.setTexture("Tex1", grass); + mat_terrain.setFloat("Tex1Scale", 64f); + + /** 1.3) Add DIRT texture into the green layer (Tex2) */ + Texture dirt = assetManager.loadTexture( + "Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + mat_terrain.setTexture("Tex2", dirt); + mat_terrain.setFloat("Tex2Scale", 32f); + + /** 1.4) Add ROAD texture into the blue layer (Tex3) */ + Texture rock = assetManager.loadTexture( + "Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + mat_terrain.setTexture("Tex3", rock); + mat_terrain.setFloat("Tex3Scale", 128f); + + /** 2.a Create a custom height map from an image */ + AbstractHeightMap heightmap = null; + Texture heightMapImage = assetManager.loadTexture( + "Textures/Terrain/splat/mountains512.png"); + heightmap = new ImageBasedHeightMap(heightMapImage.getImage()); + +/** 2.b Create a random height map */ +// HillHeightMap heightmap = null; +// HillHeightMap.NORMALIZE_RANGE = 100; +// try { +// heightmap = new HillHeightMap(513, 1000, 50, 100, (byte) 3); +// } catch (Exception ex) { +// ex.printStackTrace(); +// } + + heightmap.load(); + + /** 3. We have prepared material and heightmap. + * Now we create the actual terrain: + * 3.1) Create a TerrainQuad and name it "my terrain". + * 3.2) A good value for terrain tiles is 64x64 -- so we supply 64+1=65. + * 3.3) We prepared a heightmap of size 512x512 -- so we supply 512+1=513. + * 3.4) As LOD step scale we supply Vector3f(1,1,1). + * 3.5) We supply the prepared heightmap itself. + */ + int patchSize = 65; + terrain = new TerrainQuad("my terrain", patchSize, 513, heightmap.getHeightMap()); + + /** 4. We give the terrain its material, position & scale it, and attach it. */ + terrain.setMaterial(mat_terrain); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(2f, 1f, 2f); + rootNode.attachChild(terrain); + + /** 5. The LOD (level of detail) depends on were the camera is: */ + TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); + control.setLodCalculator( new DistanceLodCalculator(patchSize, 2.7f) ); // patch size, and a multiplier + terrain.addControl(control); + } +} diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrainCollision.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrainCollision.java new file mode 100644 index 000000000..d7ea68f63 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrainCollision.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.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; + +/** + * This demo shows a terrain with collision detection, + * that you can walk around in with a first-person perspective. + * This code combines HelloCollision and HelloTerrain. + */ +public class HelloTerrainCollision extends SimpleApplication + implements ActionListener { + + private BulletAppState bulletAppState; + private RigidBodyControl landscape; + private CharacterControl player; + private Vector3f walkDirection = new Vector3f(); + private boolean left = false, right = false, up = false, down = false; + private TerrainQuad terrain; + private Material mat_terrain; + //Temporary vectors used on each frame. + //They here to avoid instanciating new vectors on each frame + private Vector3f camDir = new Vector3f(); + private Vector3f camLeft = new Vector3f(); + + public static void main(String[] args) { + HelloTerrainCollision app = new HelloTerrainCollision(); + app.start(); + } + + @Override + public void simpleInitApp() { + /** Set up Physics */ + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + //bulletAppState.getPhysicsSpace().enableDebug(assetManager); + + flyCam.setMoveSpeed(100); + setUpKeys(); + + /** 1. Create terrain material and load four textures into it. */ + mat_terrain = new Material(assetManager, + "Common/MatDefs/Terrain/Terrain.j3md"); + + /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */ + mat_terrain.setTexture("Alpha", assetManager.loadTexture( + "Textures/Terrain/splat/alphamap.png")); + + /** 1.2) Add GRASS texture into the red layer (Tex1). */ + Texture grass = assetManager.loadTexture( + "Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + mat_terrain.setTexture("Tex1", grass); + mat_terrain.setFloat("Tex1Scale", 64f); + + /** 1.3) Add DIRT texture into the green layer (Tex2) */ + Texture dirt = assetManager.loadTexture( + "Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + mat_terrain.setTexture("Tex2", dirt); + mat_terrain.setFloat("Tex2Scale", 32f); + + /** 1.4) Add ROAD texture into the blue layer (Tex3) */ + Texture rock = assetManager.loadTexture( + "Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + mat_terrain.setTexture("Tex3", rock); + mat_terrain.setFloat("Tex3Scale", 128f); + + /** 2. Create the height map */ + AbstractHeightMap heightmap = null; + Texture heightMapImage = assetManager.loadTexture( + "Textures/Terrain/splat/mountains512.png"); + heightmap = new ImageBasedHeightMap(heightMapImage.getImage()); + heightmap.load(); + + /** 3. We have prepared material and heightmap. + * Now we create the actual terrain: + * 3.1) Create a TerrainQuad and name it "my terrain". + * 3.2) A good value for terrain tiles is 64x64 -- so we supply 64+1=65. + * 3.3) We prepared a heightmap of size 512x512 -- so we supply 512+1=513. + * 3.4) As LOD step scale we supply Vector3f(1,1,1). + * 3.5) We supply the prepared heightmap itself. + */ + terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap()); + + /** 4. We give the terrain its material, position & scale it, and attach it. */ + terrain.setMaterial(mat_terrain); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(2f, 1f, 2f); + rootNode.attachChild(terrain); + + /** 5. The LOD (level of detail) depends on were the camera is: */ + List cameras = new ArrayList(); + cameras.add(getCamera()); + TerrainLodControl control = new TerrainLodControl(terrain, cameras); + terrain.addControl(control); + + /** 6. Add physics: */ + /* We set up collision detection for the scene by creating a static + * RigidBodyControl with mass zero.*/ + terrain.addControl(new RigidBodyControl(0)); + + // We set up collision detection for the player by creating + // a capsule collision shape and a CharacterControl. + // The CharacterControl offers extra settings for + // size, stepheight, jumping, falling, and gravity. + // We also put the player in its starting position. + CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1); + player = new CharacterControl(capsuleShape, 0.05f); + player.setJumpSpeed(20); + player.setFallSpeed(30); + player.setGravity(30); + player.setPhysicsLocation(new Vector3f(-10, 10, 10)); + + // We attach the scene and the player to the rootnode and the physics space, + // to make them appear in the game world. + bulletAppState.getPhysicsSpace().add(terrain); + bulletAppState.getPhysicsSpace().add(player); + + } + /** We over-write some navigational key mappings here, so we can + * add physics-controlled walking and jumping: */ + private void setUpKeys() { + inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A)); + inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D)); + inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S)); + inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(this, "Left"); + inputManager.addListener(this, "Right"); + inputManager.addListener(this, "Up"); + inputManager.addListener(this, "Down"); + inputManager.addListener(this, "Jump"); + } + + /** These are our custom actions triggered by key presses. + * We do not walk yet, we just keep track of the direction the user pressed. */ + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("Left")) { + if (value) { left = true; } else { left = false; } + } else if (binding.equals("Right")) { + if (value) { right = true; } else { right = false; } + } else if (binding.equals("Up")) { + if (value) { up = true; } else { up = false; } + } else if (binding.equals("Down")) { + if (value) { down = true; } else { down = false; } + } else if (binding.equals("Jump")) { + player.jump(); + } + } + + /** + * This is the main event loop--walking happens here. + * We check in which direction the player is walking by interpreting + * the camera direction forward (camDir) and to the side (camLeft). + * The setWalkDirection() command is what lets a physics-controlled player walk. + * We also make sure here that the camera moves with player. + */ + @Override + public void simpleUpdate(float tpf) { + camDir.set(cam.getDirection()).multLocal(0.6f); + camLeft.set(cam.getLeft()).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/jme3-examples/src/main/java/jme3test/input/TestCameraNode.java b/jme3-examples/src/main/java/jme3test/input/TestCameraNode.java new file mode 100644 index 000000000..c1ebfeb8f --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/input/TestCameraNode.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. + *

      + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + *

      + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + *

      + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + *

      + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.input; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.*; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.CameraNode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.control.CameraControl.ControlDirection; +import com.jme3.scene.shape.Quad; +import com.jme3.system.AppSettings; + +/** + * A 3rd-person camera node follows a target (teapot). Follow the teapot with + * WASD keys, rotate by dragging the mouse. + */ +public class TestCameraNode extends SimpleApplication implements AnalogListener, ActionListener { + + private Geometry teaGeom; + private Node teaNode; + CameraNode camNode; + boolean rotate = false; + Vector3f direction = new Vector3f(); + + public static void main(String[] args) { + TestCameraNode app = new TestCameraNode(); + AppSettings s = new AppSettings(true); + s.setFrameRate(100); + app.setSettings(s); + app.start(); + } + + public void simpleInitApp() { + // load a teapot model + teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); + teaGeom.setMaterial(mat); + //create a node to attach the geometry and the camera node + teaNode = new Node("teaNode"); + teaNode.attachChild(teaGeom); + rootNode.attachChild(teaNode); + // create a floor + mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + Geometry ground = new Geometry("ground", new Quad(50, 50)); + ground.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); + ground.setLocalTranslation(-25, -1, 25); + ground.setMaterial(mat); + rootNode.attachChild(ground); + + //creating the camera Node + camNode = new CameraNode("CamNode", cam); + //Setting the direction to Spatial to camera, this means the camera will copy the movements of the Node + camNode.setControlDir(ControlDirection.SpatialToCamera); + //attaching the camNode to the teaNode + teaNode.attachChild(camNode); + //setting the local translation of the cam node to move it away from the teanNode a bit + camNode.setLocalTranslation(new Vector3f(-10, 0, 0)); + //setting the camNode to look at the teaNode + camNode.lookAt(teaNode.getLocalTranslation(), Vector3f.UNIT_Y); + + //disable the default 1st-person flyCam (don't forget this!!) + flyCam.setEnabled(false); + + registerInput(); + } + + public void registerInput() { + inputManager.addMapping("moveForward", new KeyTrigger(keyInput.KEY_UP), new KeyTrigger(keyInput.KEY_W)); + inputManager.addMapping("moveBackward", new KeyTrigger(keyInput.KEY_DOWN), new KeyTrigger(keyInput.KEY_S)); + inputManager.addMapping("moveRight", new KeyTrigger(keyInput.KEY_RIGHT), new KeyTrigger(keyInput.KEY_D)); + inputManager.addMapping("moveLeft", new KeyTrigger(keyInput.KEY_LEFT), new KeyTrigger(keyInput.KEY_A)); + inputManager.addMapping("toggleRotate", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addMapping("rotateRight", new MouseAxisTrigger(MouseInput.AXIS_X, true)); + inputManager.addMapping("rotateLeft", new MouseAxisTrigger(MouseInput.AXIS_X, false)); + inputManager.addListener(this, "moveForward", "moveBackward", "moveRight", "moveLeft"); + inputManager.addListener(this, "rotateRight", "rotateLeft", "toggleRotate"); + } + + public void onAnalog(String name, float value, float tpf) { + //computing the normalized direction of the cam to move the teaNode + direction.set(cam.getDirection()).normalizeLocal(); + if (name.equals("moveForward")) { + direction.multLocal(5 * tpf); + teaNode.move(direction); + } + if (name.equals("moveBackward")) { + direction.multLocal(-5 * tpf); + teaNode.move(direction); + } + if (name.equals("moveRight")) { + direction.crossLocal(Vector3f.UNIT_Y).multLocal(5 * tpf); + teaNode.move(direction); + } + if (name.equals("moveLeft")) { + direction.crossLocal(Vector3f.UNIT_Y).multLocal(-5 * tpf); + teaNode.move(direction); + } + if (name.equals("rotateRight") && rotate) { + teaNode.rotate(0, 5 * tpf, 0); + } + if (name.equals("rotateLeft") && rotate) { + teaNode.rotate(0, -5 * tpf, 0); + } + + } + + public void onAction(String name, boolean keyPressed, float tpf) { + //toggling rotation on or off + if (name.equals("toggleRotate") && keyPressed) { + rotate = true; + inputManager.setCursorVisible(false); + } + if (name.equals("toggleRotate") && !keyPressed) { + rotate = false; + inputManager.setCursorVisible(true); + } + + } +} diff --git a/jme3-examples/src/main/java/jme3test/input/TestChaseCamera.java b/jme3-examples/src/main/java/jme3test/input/TestChaseCamera.java new file mode 100644 index 000000000..b6041a519 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/input/TestChaseCamera.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.input; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.ChaseCamera; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; + +/** A 3rd-person chase camera orbits a target (teapot). + * Follow the teapot with WASD keys, rotate by dragging the mouse. */ +public class TestChaseCamera extends SimpleApplication implements AnalogListener, ActionListener { + + private Geometry teaGeom; + private ChaseCamera chaseCam; + + public static void main(String[] args) { + TestChaseCamera app = new TestChaseCamera(); + app.start(); + } + + public void simpleInitApp() { + // Load a teapot model + teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); + Material mat_tea = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); + teaGeom.setMaterial(mat_tea); + rootNode.attachChild(teaGeom); + + // Load a floor model + Material mat_ground = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat_ground.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + Geometry ground = new Geometry("ground", new Quad(50, 50)); + ground.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); + ground.setLocalTranslation(-25, -1, 25); + ground.setMaterial(mat_ground); + rootNode.attachChild(ground); + + // Disable the default first-person cam! + flyCam.setEnabled(false); + + // Enable a chase cam + chaseCam = new ChaseCamera(cam, teaGeom, inputManager); + + //Uncomment this to invert the camera's vertical rotation Axis + //chaseCam.setInvertVerticalAxis(true); + + //Uncomment this to invert the camera's horizontal rotation Axis + //chaseCam.setInvertHorizontalAxis(true); + + //Comment this to disable smooth camera motion + chaseCam.setSmoothMotion(true); + + //Uncomment this to disable trailing of the camera + //WARNING, trailing only works with smooth motion enabled. It is true by default. + //chaseCam.setTrailingEnabled(false); + + //Uncomment this to look 3 world units above the target + //chaseCam.setLookAtOffset(Vector3f.UNIT_Y.mult(3)); + + //Uncomment this to enable rotation when the middle mouse button is pressed (like Blender) + //WARNING : setting this trigger disable the rotation on right and left mouse button click + //chaseCam.setToggleRotationTrigger(new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE)); + + //Uncomment this to set mutiple triggers to enable rotation of the cam + //Here spade bar and middle mouse button + //chaseCam.setToggleRotationTrigger(new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE),new KeyTrigger(KeyInput.KEY_SPACE)); + + //registering inputs for target's movement + registerInput(); + + } + + public void registerInput() { + inputManager.addMapping("moveForward", new KeyTrigger(keyInput.KEY_UP), new KeyTrigger(keyInput.KEY_W)); + inputManager.addMapping("moveBackward", new KeyTrigger(keyInput.KEY_DOWN), new KeyTrigger(keyInput.KEY_S)); + inputManager.addMapping("moveRight", new KeyTrigger(keyInput.KEY_RIGHT), new KeyTrigger(keyInput.KEY_D)); + inputManager.addMapping("moveLeft", new KeyTrigger(keyInput.KEY_LEFT), new KeyTrigger(keyInput.KEY_A)); + inputManager.addMapping("displayPosition", new KeyTrigger(keyInput.KEY_P)); + inputManager.addListener(this, "moveForward", "moveBackward", "moveRight", "moveLeft"); + inputManager.addListener(this, "displayPosition"); + } + + public void onAnalog(String name, float value, float tpf) { + if (name.equals("moveForward")) { + teaGeom.move(0, 0, -5 * tpf); + } + if (name.equals("moveBackward")) { + teaGeom.move(0, 0, 5 * tpf); + } + if (name.equals("moveRight")) { + teaGeom.move(5 * tpf, 0, 0); + } + if (name.equals("moveLeft")) { + teaGeom.move(-5 * tpf, 0, 0); + + } + + } + + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("displayPosition") && keyPressed) { + teaGeom.move(10, 10, 10); + + } + } + + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + // teaGeom.move(new Vector3f(0.001f, 0, 0)); + // pivot.rotate(0, 0.00001f, 0); + // rootNode.updateGeometricState(); + } +// public void update() { +// super.update(); +//// render the viewports +// float tpf = timer.getTimePerFrame(); +// state.getRootNode().rotate(0, 0.000001f, 0); +// stateManager.update(tpf); +// stateManager.render(renderManager); +// renderManager.render(tpf); +// } +} diff --git a/jme3-examples/src/main/java/jme3test/input/TestControls.java b/jme3-examples/src/main/java/jme3test/input/TestControls.java new file mode 100644 index 000000000..07568b2b5 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/input/TestControls.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package 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/jme3-examples/src/main/java/jme3test/input/TestJoystick.java b/jme3-examples/src/main/java/jme3test/input/TestJoystick.java new file mode 100644 index 000000000..9d9abc914 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/input/TestJoystick.java @@ -0,0 +1,392 @@ +package jme3test.input; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.input.Joystick; +import com.jme3.input.JoystickAxis; +import com.jme3.input.JoystickButton; +import com.jme3.input.RawInputListener; +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.input.event.TouchEvent; +import com.jme3.material.Material; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Quad; +import com.jme3.system.AppSettings; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; + +public class TestJoystick extends SimpleApplication { + + private Joystick viewedJoystick; + private GamepadView gamepad; + private Node joystickInfo; + private float yInfo = 0; + + public static void main(String[] args){ + TestJoystick app = new TestJoystick(); + AppSettings settings = new AppSettings(true); + settings.setUseJoysticks(true); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() { + Joystick[] joysticks = inputManager.getJoysticks(); + if (joysticks == null) + throw new IllegalStateException("Cannot find any joysticks!"); + + try { + PrintWriter out = new PrintWriter( new FileWriter( "joysticks-" + System.currentTimeMillis() + ".txt" ) ); + dumpJoysticks( joysticks, out ); + out.close(); + } catch( IOException e ) { + throw new RuntimeException( "Error writing joystick dump", e ); + } + + + int gamepadSize = cam.getHeight() / 2; + float scale = gamepadSize / 512.0f; + gamepad = new GamepadView(); + gamepad.setLocalTranslation( cam.getWidth() - gamepadSize - (scale * 20), 0, 0 ); + gamepad.setLocalScale( scale, scale, scale ); + guiNode.attachChild(gamepad); + + joystickInfo = new Node( "joystickInfo" ); + joystickInfo.setLocalTranslation( 0, cam.getHeight(), 0 ); + guiNode.attachChild( joystickInfo ); + + // Add a raw listener because it's eisier to get all joystick events + // this way. + inputManager.addRawInputListener( new JoystickEventListener() ); + } + + protected void dumpJoysticks( Joystick[] joysticks, PrintWriter out ) { + for( Joystick j : joysticks ) { + out.println( "Joystick[" + j.getJoyId() + "]:" + j.getName() ); + out.println( " buttons:" + j.getButtonCount() ); + for( JoystickButton b : j.getButtons() ) { + out.println( " " + b ); + } + + out.println( " axes:" + j.getAxisCount() ); + for( JoystickAxis axis : j.getAxes() ) { + out.println( " " + axis ); + } + } + } + + protected void addInfo( String info, int column ) { + + BitmapText t = new BitmapText(guiFont); + t.setText( info ); + t.setLocalTranslation( column * 200, yInfo, 0 ); + joystickInfo.attachChild(t); + yInfo -= t.getHeight(); + } + + protected void setViewedJoystick( Joystick stick ) { + if( this.viewedJoystick == stick ) + return; + + if( this.viewedJoystick != null ) { + joystickInfo.detachAllChildren(); + } + + this.viewedJoystick = stick; + + if( this.viewedJoystick != null ) { + // Draw the hud + yInfo = 0; + + addInfo( "Joystick:\"" + stick.getName() + "\" id:" + stick.getJoyId(), 0 ); + + yInfo -= 5; + + float ySave = yInfo; + + // Column one for the buttons + addInfo( "Buttons:", 0 ); + for( JoystickButton b : stick.getButtons() ) { + addInfo( " '" + b.getName() + "' id:'" + b.getLogicalId() + "'", 0 ); + } + yInfo = ySave; + + // Column two for the axes + addInfo( "Axes:", 1 ); + for( JoystickAxis a : stick.getAxes() ) { + addInfo( " '" + a.getName() + "' id:'" + a.getLogicalId() + "' analog:" + a.isAnalog(), 1 ); + } + + } + } + + /** + * Easier to watch for all button and axis events with a raw input listener. + */ + protected class JoystickEventListener implements RawInputListener { + + public void onJoyAxisEvent(JoyAxisEvent evt) { + setViewedJoystick( evt.getAxis().getJoystick() ); + gamepad.setAxisValue( evt.getAxis(), evt.getValue() ); + } + + public void onJoyButtonEvent(JoyButtonEvent evt) { + setViewedJoystick( evt.getButton().getJoystick() ); + gamepad.setButtonValue( evt.getButton(), evt.isPressed() ); + } + + public void beginInput() {} + public void endInput() {} + public void onMouseMotionEvent(MouseMotionEvent evt) {} + public void onMouseButtonEvent(MouseButtonEvent evt) {} + public void onKeyEvent(KeyInputEvent evt) {} + public void onTouchEvent(TouchEvent evt) {} + } + + protected class GamepadView extends Node { + + float xAxis = 0; + float yAxis = 0; + float zAxis = 0; + float zRotation = 0; + + float lastPovX = 0; + float lastPovY = 0; + + Geometry leftStick; + Geometry rightStick; + + Map buttons = new HashMap(); + + public GamepadView() { + super( "gamepad" ); + + // Sizes naturally for the texture size. All positions will + // be in that space because it's easier. + int size = 512; + + Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + m.setTexture( "ColorMap", assetManager.loadTexture( "Interface/Joystick/gamepad-buttons.png" ) ); + m.getAdditionalRenderState().setBlendMode( BlendMode.Alpha ); + Geometry buttonPanel = new Geometry( "buttons", new Quad(size, size) ); + buttonPanel.setLocalTranslation( 0, 0, -1 ); + buttonPanel.setMaterial(m); + attachChild(buttonPanel); + + m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + m.setTexture( "ColorMap", assetManager.loadTexture( "Interface/Joystick/gamepad-frame.png" ) ); + m.getAdditionalRenderState().setBlendMode( BlendMode.Alpha ); + Geometry frame = new Geometry( "frame", new Quad(size, size) ); + frame.setMaterial(m); + attachChild(frame); + + m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + m.setTexture( "ColorMap", assetManager.loadTexture( "Interface/Joystick/gamepad-stick.png" ) ); + m.getAdditionalRenderState().setBlendMode( BlendMode.Alpha ); + leftStick = new Geometry( "leftStick", new Quad(64, 64) ); + leftStick.setMaterial(m); + attachChild(leftStick); + rightStick = new Geometry( "leftStick", new Quad(64, 64) ); + rightStick.setMaterial(m); + attachChild(rightStick); + + // A "standard" mapping... fits a majority of my game pads + addButton( JoystickButton.BUTTON_0, 371, 512 - 176, 42, 42 ); + addButton( JoystickButton.BUTTON_1, 407, 512 - 212, 42, 42 ); + addButton( JoystickButton.BUTTON_2, 371, 512 - 248, 42, 42 ); + addButton( JoystickButton.BUTTON_3, 334, 512 - 212, 42, 42 ); + + // Front buttons Some of these have the top ones and the bottoms ones flipped. + addButton( JoystickButton.BUTTON_4, 67, 512 - 111, 95, 21 ); + addButton( JoystickButton.BUTTON_5, 348, 512 - 111, 95, 21 ); + addButton( JoystickButton.BUTTON_6, 67, 512 - 89, 95, 21 ); + addButton( JoystickButton.BUTTON_7, 348, 512 - 89, 95, 21 ); + + // Select and start buttons + addButton( JoystickButton.BUTTON_8, 206, 512 - 198, 48, 30 ); + addButton( JoystickButton.BUTTON_9, 262, 512 - 198, 48, 30 ); + + // Joystick push buttons + addButton( JoystickButton.BUTTON_10, 147, 512 - 300, 75, 70 ); + addButton( JoystickButton.BUTTON_11, 285, 512 - 300, 75, 70 ); + + // Fake button highlights for the POV axes + // + // +Y + // -X +X + // -Y + // + addButton( "POV +Y", 96, 512 - 174, 40, 38 ); + addButton( "POV +X", 128, 512 - 208, 40, 38 ); + addButton( "POV -Y", 96, 512 - 239, 40, 38 ); + addButton( "POV -X", 65, 512 - 208, 40, 38 ); + + resetPositions(); + } + + private void addButton( String name, float x, float y, float width, float height ) { + ButtonView b = new ButtonView(name, x, y, width, height); + attachChild(b); + buttons.put(name, b); + } + + public void setAxisValue( JoystickAxis axis, float value ) { + System.out.println( "Axis:" + axis.getName() + "=" + value ); + if( axis == axis.getJoystick().getXAxis() ) { + setXAxis(value); + } else if( axis == axis.getJoystick().getYAxis() ) { + setYAxis(-value); + } else if( axis == axis.getJoystick().getAxis(JoystickAxis.Z_AXIS) ) { + // Note: in the above condition, we could check the axis name but + // I have at least one joystick that reports 2 "Z Axis" axes. + // In this particular case, the first one is the right one so + // a name based lookup will find the proper one. It's a problem + // because the erroneous axis sends a constant stream of values. + setZAxis(value); + } else if( axis == axis.getJoystick().getAxis(JoystickAxis.Z_ROTATION) ) { + setZRotation(-value); + } else if( axis == axis.getJoystick().getPovXAxis() ) { + if( lastPovX < 0 ) { + setButtonValue( "POV -X", false ); + } else if( lastPovX > 0 ) { + setButtonValue( "POV +X", false ); + } + if( value < 0 ) { + setButtonValue( "POV -X", true ); + } else if( value > 0 ) { + setButtonValue( "POV +X", true ); + } + lastPovX = value; + } else if( axis == axis.getJoystick().getPovYAxis() ) { + if( lastPovY < 0 ) { + setButtonValue( "POV -Y", false ); + } else if( lastPovY > 0 ) { + setButtonValue( "POV +Y", false ); + } + if( value < 0 ) { + setButtonValue( "POV -Y", true ); + } else if( value > 0 ) { + setButtonValue( "POV +Y", true ); + } + lastPovY = value; + } + } + + public void setButtonValue( JoystickButton button, boolean isPressed ) { + System.out.println( "Button:" + button.getName() + "=" + (isPressed ? "Down" : "Up") ); + setButtonValue( button.getLogicalId(), isPressed ); + } + + protected void setButtonValue( String name, boolean isPressed ) { + ButtonView view = buttons.get(name); + if( view != null ) { + if( isPressed ) { + view.down(); + } else { + view.up(); + } + } + } + + public void setXAxis( float f ) { + xAxis = f; + resetPositions(); + } + + public void setYAxis( float f ) { + yAxis = f; + resetPositions(); + } + + public void setZAxis( float f ) { + zAxis = f; + resetPositions(); + } + + public void setZRotation( float f ) { + zRotation = f; + resetPositions(); + } + + private void resetPositions() { + + float xBase = 155; + float yBase = 212; + + Vector2f dir = new Vector2f(xAxis, yAxis); + float length = Math.min(1, dir.length()); + dir.normalizeLocal(); + + float angle = dir.getAngle(); + float x = FastMath.cos(angle) * length * 10; + float y = FastMath.sin(angle) * length * 10; + leftStick.setLocalTranslation( xBase + x, yBase + y, 0 ); + + + xBase = 291; + dir = new Vector2f(zAxis, zRotation); + length = Math.min(1, dir.length()); + dir.normalizeLocal(); + + angle = dir.getAngle(); + x = FastMath.cos(angle) * length * 10; + y = FastMath.sin(angle) * length * 10; + rightStick.setLocalTranslation( xBase + x, yBase + y, 0 ); + } + } + + protected class ButtonView extends Node { + + private int state = 0; + private Material material; + private ColorRGBA hilite = new ColorRGBA( 0.0f, 0.75f, 0.75f, 0.5f ); + + public ButtonView( String name, float x, float y, float width, float height ) { + super( "Button:" + name ); + setLocalTranslation( x, y, -0.5f ); + + material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + material.setColor( "Color", hilite ); + material.getAdditionalRenderState().setBlendMode( BlendMode.Alpha ); + + Geometry g = new Geometry( "highlight", new Quad(width, height) ); + g.setMaterial(material); + attachChild(g); + + resetState(); + } + + private void resetState() { + if( state <= 0 ) { + setCullHint( CullHint.Always ); + } else { + setCullHint( CullHint.Dynamic ); + } + + System.out.println( getName() + " state:" + state ); + } + + public void down() { + state++; + resetState(); + } + + public void up() { + state--; + resetState(); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMove.java b/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMove.java new file mode 100644 index 000000000..ee1a3c348 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMove.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package 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/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMoveExecution.java b/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMoveExecution.java new file mode 100644 index 000000000..9b8e266ec --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMoveExecution.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/input/combomoves/TestComboMoves.java b/jme3-examples/src/main/java/jme3test/input/combomoves/TestComboMoves.java new file mode 100644 index 000000000..3ca46dbd3 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/input/combomoves/TestComboMoves.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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 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() { + setDisplayFps(false); + setDisplayStatView(false); + + // Create debug text + BitmapText helpText = new BitmapText(guiFont); + helpText.setLocalTranslation(0, settings.getHeight(), 0); + helpText.setText("Moves:\n" + + "Fireball: Down, Down+Right, Right\n"+ + "Shuriken: Left, Down, Attack1(Z)\n"+ + "Jab: Attack1(Z)\n"+ + "Punch: Attack1(Z), Attack1(Z)\n"); + guiNode.attachChild(helpText); + + fireballText = new BitmapText(guiFont); + fireballText.setColor(ColorRGBA.Orange); + fireballText.setLocalTranslation(0, fireballText.getLineHeight(), 0); + guiNode.attachChild(fireballText); + + shurikenText = new BitmapText(guiFont); + shurikenText.setColor(ColorRGBA.Cyan); + shurikenText.setLocalTranslation(0, shurikenText.getLineHeight()*2f, 0); + guiNode.attachChild(shurikenText); + + jabText = new BitmapText(guiFont); + jabText.setColor(ColorRGBA.Red); + jabText.setLocalTranslation(0, jabText.getLineHeight()*3f, 0); + guiNode.attachChild(jabText); + + punchText = new BitmapText(guiFont); + punchText.setColor(ColorRGBA.Green); + punchText.setLocalTranslation(0, punchText.getLineHeight()*4f, 0); + guiNode.attachChild(punchText); + + inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_LEFT)); + inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_RIGHT)); + inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_UP)); + inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_DOWN)); + inputManager.addMapping("Attack1", new KeyTrigger(KeyInput.KEY_Z)); + inputManager.addListener(this, "Left", "Right", "Up", "Down", "Attack1"); + + fireball = new ComboMove("Fireball"); + fireball.press("Down").notPress("Right").done(); + fireball.press("Right", "Down").done(); + fireball.press("Right").notPress("Down").done(); + fireball.notPress("Right", "Down").done(); + fireball.setUseFinalState(false); // no waiting on final state + + shuriken = new ComboMove("Shuriken"); + shuriken.press("Left").notPress("Down", "Attack1").done(); + shuriken.press("Down").notPress("Attack1").timeElapsed(0.11f).done(); + shuriken.press("Attack1").notPress("Left").timeElapsed(0.11f).done(); + shuriken.notPress("Left", "Down", "Attack1").done(); + + jab = new ComboMove("Jab"); + jab.setPriority(0.5f); // make jab less important than other moves + jab.press("Attack1").done(); + + punch = new ComboMove("Punch"); + punch.press("Attack1").done(); + punch.notPress("Attack1").done(); + punch.press("Attack1").done(); + + fireballExec = new ComboMoveExecution(fireball); + shurikenExec = new ComboMoveExecution(shuriken); + jabExec = new ComboMoveExecution(jab); + punchExec = new ComboMoveExecution(punch); + } + + @Override + public void simpleUpdate(float tpf){ + time += tpf; + + // check every frame if any executions are expired + shurikenExec.updateExpiration(time); + shurikenText.setText("Shuriken Exec: " + shurikenExec.getDebugString()); + + fireballExec.updateExpiration(time); + fireballText.setText("Fireball Exec: " + fireballExec.getDebugString()); + + jabExec.updateExpiration(time); + jabText.setText("Jab Exec: " + jabExec.getDebugString()); + + punchExec.updateExpiration(time); + punchText.setText("Punch Exec: " + punchExec.getDebugString()); + + if (currentMove != null){ + currentMoveCastTime -= tpf; + if (currentMoveCastTime <= 0){ + System.out.println("DONE CASTING " + currentMove.getMoveName()); + currentMoveCastTime = 0; + currentMove = null; + } + } + } + + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed){ + pressedMappings.add(name); + }else{ + pressedMappings.remove(name); + } + + // the pressed mappings was changed. update combo executions + List 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/jme3-examples/src/main/java/jme3test/light/ShadowTestUIManager.java b/jme3-examples/src/main/java/jme3test/light/ShadowTestUIManager.java new file mode 100644 index 000000000..34bc20d89 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/ShadowTestUIManager.java @@ -0,0 +1,154 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package jme3test.light; + +import com.jme3.asset.AssetManager; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +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.renderer.Camera; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Node; +import com.jme3.shadow.AbstractShadowFilter; +import com.jme3.shadow.AbstractShadowRenderer; +import com.jme3.shadow.CompareMode; +import com.jme3.shadow.EdgeFilteringMode; + +/** + * + * @author Nehon + */ +public class ShadowTestUIManager implements ActionListener { + + private BitmapText shadowTypeText; + private BitmapText shadowCompareText; + private BitmapText shadowFilterText; + private BitmapText shadowIntensityText; + private final static String TYPE_TEXT = "(Space) Shadow type : "; + private final static String COMPARE_TEXT = "(enter) Shadow compare "; + private final static String FILTERING_TEXT = "(f) Edge filtering : "; + private final static String INTENSITY_TEXT = "(t:up, g:down) Shadow intensity : "; + private boolean hardwareShadows = true; + private AbstractShadowRenderer plsr; + private AbstractShadowFilter plsf; + private ViewPort viewPort; + private int filteringIndex = 0; + private int renderModeIndex = 0; + + + public ShadowTestUIManager(AssetManager assetManager,AbstractShadowRenderer plsr, AbstractShadowFilter plsf, + Node guiNode, InputManager inputManager, ViewPort viewPort) { + this.plsr = plsr; + this.plsf = plsf; + this.viewPort = viewPort; + BitmapFont guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + shadowTypeText = createText(guiFont); + shadowCompareText = createText(guiFont); + shadowFilterText = createText(guiFont); + shadowIntensityText = createText(guiFont); + + shadowTypeText.setText(TYPE_TEXT + "Processor"); + shadowCompareText.setText(COMPARE_TEXT + (hardwareShadows ? "Hardware" : "Software")); + shadowFilterText.setText(FILTERING_TEXT + plsr.getEdgeFilteringMode().toString()); + shadowIntensityText.setText(INTENSITY_TEXT + plsr.getShadowIntensity()); + + shadowTypeText.setLocalTranslation(10, viewPort.getCamera().getHeight() - 20, 0); + shadowCompareText.setLocalTranslation(10, viewPort.getCamera().getHeight() - 40, 0); + shadowFilterText.setLocalTranslation(10, viewPort.getCamera().getHeight() - 60, 0); + shadowIntensityText.setLocalTranslation(10, viewPort.getCamera().getHeight() - 80, 0); + + guiNode.attachChild(shadowTypeText); + guiNode.attachChild(shadowCompareText); + guiNode.attachChild(shadowFilterText); + guiNode.attachChild(shadowIntensityText); + + inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("changeFiltering", new KeyTrigger(KeyInput.KEY_F)); + 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("toggleHW", new KeyTrigger(KeyInput.KEY_RETURN)); + + + inputManager.addListener(this, "toggleHW", "toggle", "ShadowUp", "ShadowDown", "ThicknessUp", "ThicknessDown", "changeFiltering"); + + } + + + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("toggle") && keyPressed) { + renderModeIndex += 1; + renderModeIndex %= 3; + + switch (renderModeIndex) { + case 0: + viewPort.addProcessor(plsr); + shadowTypeText.setText(TYPE_TEXT + "Processor"); + break; + case 1: + viewPort.removeProcessor(plsr); + plsf.setEnabled(true); + shadowTypeText.setText(TYPE_TEXT + "Filter"); + break; + case 2: + plsf.setEnabled(false); + shadowTypeText.setText(TYPE_TEXT + "None"); + break; + } + + + + } else if (name.equals("toggleHW") && keyPressed) { + hardwareShadows = !hardwareShadows; + plsr.setShadowCompareMode(hardwareShadows ? CompareMode.Hardware : CompareMode.Software); + plsf.setShadowCompareMode(hardwareShadows ? CompareMode.Hardware : CompareMode.Software); + + shadowCompareText.setText(COMPARE_TEXT + (hardwareShadows ? "Hardware" : "Software")); + } + + + if (name.equals("changeFiltering") && keyPressed) { + filteringIndex = plsr.getEdgeFilteringMode().ordinal(); + filteringIndex = (filteringIndex + 1) % EdgeFilteringMode.values().length; + EdgeFilteringMode m = EdgeFilteringMode.values()[filteringIndex]; + plsr.setEdgeFilteringMode(m); + plsf.setEdgeFilteringMode(m); + shadowFilterText.setText(FILTERING_TEXT + m.toString()); + } + + if (name.equals("ShadowUp") && keyPressed) { + plsr.setShadowIntensity(plsr.getShadowIntensity() + 0.1f); + plsf.setShadowIntensity(plsf.getShadowIntensity() + 0.1f); + + shadowIntensityText.setText(INTENSITY_TEXT + plsr.getShadowIntensity()); + } + if (name.equals("ShadowDown") && keyPressed) { + plsr.setShadowIntensity(plsr.getShadowIntensity() - 0.1f); + plsf.setShadowIntensity(plsf.getShadowIntensity() - 0.1f); + shadowIntensityText.setText(INTENSITY_TEXT + plsr.getShadowIntensity()); + } + if (name.equals("ThicknessUp") && keyPressed) { + plsr.setEdgesThickness(plsr.getEdgesThickness() + 1); + plsf.setEdgesThickness(plsf.getEdgesThickness() + 1); + System.out.println("Shadow thickness : " + plsr.getEdgesThickness()); + } + if (name.equals("ThicknessDown") && keyPressed) { + plsr.setEdgesThickness(plsr.getEdgesThickness() - 1); + plsf.setEdgesThickness(plsf.getEdgesThickness() - 1); + System.out.println("Shadow thickness : " + plsr.getEdgesThickness()); + } + + } + + private BitmapText createText(BitmapFont guiFont) { + BitmapText t = new BitmapText(guiFont, false); + t.setSize(guiFont.getCharSet().getRenderedSize() * 0.75f); + return t; + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java new file mode 100644 index 000000000..6ee2b4f6a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +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.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +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.DirectionalLightShadowFilter; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; +import com.jme3.util.TangentBinormalGenerator; + +public class TestDirectionalLightShadow extends SimpleApplication implements ActionListener, AnalogListener { + public static final int SHADOWMAP_SIZE = 1024; + + private Spatial[] obj; + private Material[] mat; + private DirectionalLightShadowRenderer dlsr; + private DirectionalLightShadowFilter dlsf; + private Geometry ground; + private Material matGroundU; + private Material matGroundL; + + public static void main(String[] args) { + TestDirectionalLightShadow app = new TestDirectionalLightShadow(); + app.start(); + } + private float frustumSize = 100; + + public void onAnalog(String name, float value, float tpf) { + if (cam.isParallelProjection()) { + // Instead of moving closer/farther to object, we zoom in/out. + if (name.equals("Size-")) { + frustumSize += 5f * tpf; + } else { + frustumSize -= 5f * tpf; + } + + float aspect = (float) cam.getWidth() / cam.getHeight(); + cam.setFrustum(-1000, 1000, -aspect * frustumSize, aspect * frustumSize, frustumSize, -frustumSize); + } + } + + public void loadScene() { + obj = new Spatial[2]; + // Setup first view + + + mat = new Material[2]; + mat[0] = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); + mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); + mat[1].setBoolean("UseMaterialColors", true); + mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f)); + mat[1].setColor("Diffuse", ColorRGBA.White.clone()); + + + obj[0] = new Geometry("sphere", new Sphere(30, 30, 2)); + obj[0].setShadowMode(ShadowMode.CastAndReceive); + obj[1] = new Geometry("cube", new Box(1.0f, 1.0f, 1.0f)); + obj[1].setShadowMode(ShadowMode.CastAndReceive); + TangentBinormalGenerator.generate(obj[1]); + TangentBinormalGenerator.generate(obj[0]); + + + for (int i = 0; i < 60; i++) { + Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false); + t.setLocalScale(FastMath.nextRandomFloat() * 10f); + t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]); + rootNode.attachChild(t); + t.setLocalTranslation(FastMath.nextRandomFloat() * 200f, FastMath.nextRandomFloat() * 30f + 20, 30f * (i + 2f)); + } + + Box b = new Box(new Vector3f(0, 10, 550), 1000, 2, 1000); + b.scaleTextureCoordinates(new Vector2f(10, 10)); + ground = new Geometry("soil", b); + matGroundU = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matGroundU.setColor("Color", ColorRGBA.Green); + + + matGroundL = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matGroundL.setTexture("DiffuseMap", grass); + + ground.setMaterial(matGroundL); + + ground.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(ground); + + l = new DirectionalLight(); + //l.setDirection(new Vector3f(0.5973172f, -0.16583486f, 0.7846725f).normalizeLocal()); + l.setDirection(new Vector3f(-1, -1, -1)); + rootNode.addLight(l); + + + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(0.5f)); + rootNode.addLight(al); + + Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false); + sky.setLocalScale(350); + + rootNode.attachChild(sky); + } + DirectionalLight l; + + @Override + public void simpleInitApp() { + // put the camera in a bad position + cam.setLocation(new Vector3f(65.25412f, 44.38738f, 9.087874f)); + cam.setRotation(new Quaternion(0.078139365f, 0.050241485f, -0.003942559f, 0.9956679f)); + + flyCam.setMoveSpeed(100); + + loadScene(); + + dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 3); + dlsr.setLight(l); + dlsr.setLambda(0.55f); + dlsr.setShadowIntensity(0.6f); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest); + dlsr.displayDebug(); + viewPort.addProcessor(dlsr); + + dlsf = new DirectionalLightShadowFilter(assetManager, SHADOWMAP_SIZE, 3); + dlsf.setLight(l); + dlsf.setLambda(0.55f); + dlsf.setShadowIntensity(0.6f); + dlsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest); + dlsf.setEnabled(false); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + fpp.addFilter(dlsf); + + viewPort.addProcessor(fpp); + + initInputs(); + } + + private void initInputs() { + + 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("switchGroundMat", new KeyTrigger(KeyInput.KEY_M)); + inputManager.addMapping("debug", new KeyTrigger(KeyInput.KEY_X)); + inputManager.addMapping("stabilize", new KeyTrigger(KeyInput.KEY_B)); + + + inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_NUMPAD8)); + inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_NUMPAD2)); + inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_NUMPAD6)); + inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_NUMPAD4)); + inputManager.addMapping("fwd", new KeyTrigger(KeyInput.KEY_PGUP)); + inputManager.addMapping("back", new KeyTrigger(KeyInput.KEY_PGDN)); + inputManager.addMapping("pp", new KeyTrigger(KeyInput.KEY_P)); + + + inputManager.addListener(this, "lambdaUp", "lambdaDown", "ThicknessUp", "ThicknessDown", + "switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back", "pp","stabilize"); + + ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, dlsr, dlsf, guiNode, inputManager, viewPort); + + inputManager.addListener(this, "Size+", "Size-"); + inputManager.addMapping("Size+", new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping("Size-", new KeyTrigger(KeyInput.KEY_S)); + + shadowStabilizationText = new BitmapText(guiFont, false); + shadowStabilizationText.setSize(guiFont.getCharSet().getRenderedSize() * 0.75f); + shadowStabilizationText.setText("(b:on/off) Shadow stabilization : " + dlsr.isEnabledStabilization()); + shadowStabilizationText.setLocalTranslation(10, viewPort.getCamera().getHeight() - 100, 0); + guiNode.attachChild(shadowStabilizationText); + } + private BitmapText shadowStabilizationText ; + + public void onAction(String name, boolean keyPressed, float tpf) { + + + if (name.equals("pp") && keyPressed) { + if (cam.isParallelProjection()) { + cam.setFrustumPerspective(45, (float) cam.getWidth() / cam.getHeight(), 1, 1000); + } else { + cam.setParallelProjection(true); + float aspect = (float) cam.getWidth() / cam.getHeight(); + cam.setFrustum(-1000, 1000, -aspect * frustumSize, aspect * frustumSize, frustumSize, -frustumSize); + + } + } + + if (name.equals("lambdaUp") && keyPressed) { + dlsr.setLambda(dlsr.getLambda() + 0.01f); + dlsf.setLambda(dlsr.getLambda() + 0.01f); + System.out.println("Lambda : " + dlsr.getLambda()); + } else if (name.equals("lambdaDown") && keyPressed) { + dlsr.setLambda(dlsr.getLambda() - 0.01f); + dlsf.setLambda(dlsr.getLambda() - 0.01f); + System.out.println("Lambda : " + dlsr.getLambda()); + } + + + if (name.equals("debug") && keyPressed) { + dlsr.displayFrustum(); + } + + if (name.equals("stabilize") && keyPressed) { + dlsr.setEnabledStabilization(!dlsr.isEnabledStabilization()); + dlsf.setEnabledStabilization(!dlsf.isEnabledStabilization()); + shadowStabilizationText.setText("(b:on/off) Shadow stabilization : " + dlsr.isEnabledStabilization()); + } + + if (name.equals("switchGroundMat") && keyPressed) { + if (ground.getMaterial() == matGroundL) { + ground.setMaterial(matGroundU); + } else { + ground.setMaterial(matGroundL); + } + } + + if (name.equals("up")) { + up = keyPressed; + } + if (name.equals("down")) { + down = keyPressed; + } + if (name.equals("right")) { + right = keyPressed; + } + if (name.equals("left")) { + left = keyPressed; + } + if (name.equals("fwd")) { + fwd = keyPressed; + } + if (name.equals("back")) { + back = keyPressed; + } + + } + boolean up = false; + boolean down = false; + boolean left = false; + boolean right = false; + boolean fwd = false; + boolean back = false; + float time = 0; + float s = 1f; + + @Override + public void simpleUpdate(float tpf) { + if (up) { + Vector3f v = l.getDirection(); + v.y += tpf / s; + setDir(v); + } + if (down) { + Vector3f v = l.getDirection(); + v.y -= tpf / s; + setDir(v); + } + if (right) { + Vector3f v = l.getDirection(); + v.x += tpf / s; + setDir(v); + } + if (left) { + Vector3f v = l.getDirection(); + v.x -= tpf / s; + setDir(v); + } + if (fwd) { + Vector3f v = l.getDirection(); + v.z += tpf / s; + setDir(v); + } + if (back) { + Vector3f v = l.getDirection(); + v.z -= tpf / s; + setDir(v); + } + + } + + private void setDir(Vector3f v) { + l.setDirection(v); + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestEnvironmentMapping.java b/jme3-examples/src/main/java/jme3test/light/TestEnvironmentMapping.java new file mode 100644 index 000000000..49dc9c19d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestEnvironmentMapping.java @@ -0,0 +1,66 @@ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.input.ChaseCamera; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.texture.Texture; +import com.jme3.util.SkyFactory; + +/** + * test + * @author nehon + */ +public class TestEnvironmentMapping extends SimpleApplication { + + public static void main(String[] args) { + TestEnvironmentMapping app = new TestEnvironmentMapping(); + app.start(); + } + + @Override + public void simpleInitApp() { + final Node buggy = (Node) assetManager.loadModel("Models/Buggy/Buggy.j3o"); + + TextureKey key = new TextureKey("Textures/Sky/Bright/BrightSky.dds", true); + key.setGenerateMips(true); + key.setAsCube(true); + final Texture tex = assetManager.loadTexture(key); + + for (Spatial geom : buggy.getChildren()) { + if (geom instanceof Geometry) { + Material m = ((Geometry) geom).getMaterial(); + m.setTexture("EnvMap", tex); + m.setVector3("FresnelParams", new Vector3f(0.05f, 0.18f, 0.11f)); + } + } + + flyCam.setEnabled(false); + + ChaseCamera chaseCam = new ChaseCamera(cam, inputManager); + chaseCam.setLookAtOffset(new Vector3f(0,0.5f,-1.0f)); + buggy.addControl(chaseCam); + rootNode.attachChild(buggy); + rootNode.attachChild(SkyFactory.createSky(assetManager, tex, false)); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + BloomFilter bf = new BloomFilter(BloomFilter.GlowMode.Objects); + bf.setBloomIntensity(2.3f); + bf.setExposurePower(0.6f); + + fpp.addFilter(bf); + + DirectionalLight l = new DirectionalLight(); + l.setDirection(new Vector3f(0, -1, -1)); + rootNode.addLight(l); + + viewPort.addProcessor(fpp); + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestLightNode.java b/jme3-examples/src/main/java/jme3test/light/TestLightNode.java new file mode 100644 index 000000000..e2d337b83 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestLightNode.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.LightNode; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Torus; + +public class TestLightNode extends SimpleApplication { + + float angle; + Node movingNode; + + public static void main(String[] args){ + TestLightNode app = new TestLightNode(); + app.start(); + } + + @Override + public void simpleInitApp() { + Torus torus = new Torus(10, 6, 1, 3); +// Torus torus = new Torus(50, 30, 1, 3); + Geometry g = new Geometry("Torus Geom", torus); + g.rotate(-FastMath.HALF_PI, 0, 0); + g.center(); +// g.move(0, 1, 0); + + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setFloat("Shininess", 32f); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Ambient", ColorRGBA.Black); + mat.setColor("Diffuse", ColorRGBA.White); + mat.setColor("Specular", ColorRGBA.White); +// mat.setBoolean("VertexLighting", true); +// mat.setBoolean("LowQuality", true); + g.setMaterial(mat); + + rootNode.attachChild(g); + + Geometry lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + + movingNode=new Node("lightParentNode"); + movingNode.attachChild(lightMdl); + rootNode.attachChild(movingNode); + + PointLight pl = new PointLight(); + pl.setColor(ColorRGBA.Green); + pl.setRadius(4f); + rootNode.addLight(pl); + + LightNode lightNode=new LightNode("pointLight", pl); + movingNode.attachChild(lightNode); + + DirectionalLight dl = new DirectionalLight(); + dl.setColor(ColorRGBA.Red); + dl.setDirection(new Vector3f(0, 1, 0)); + rootNode.addLight(dl); + } + + @Override + public void simpleUpdate(float tpf){ +// cam.setLocation(new Vector3f(5.0347548f, 6.6481347f, 3.74853f)); +// cam.setRotation(new Quaternion(-0.19183293f, 0.80776674f, -0.37974006f, -0.40805697f)); + + angle += tpf; + angle %= FastMath.TWO_PI; + + movingNode.setLocalTranslation(new Vector3f(FastMath.cos(angle) * 3f, 2, FastMath.sin(angle) * 3f)); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestLightRadius.java b/jme3-examples/src/main/java/jme3test/light/TestLightRadius.java new file mode 100644 index 000000000..f3133099b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestLightRadius.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Torus; + +public class TestLightRadius extends SimpleApplication { + + float pos, vel=1; + PointLight pl; + Geometry lightMdl; + + public static void main(String[] args){ + TestLightRadius app = new TestLightRadius(); + app.start(); + } + + @Override + public void simpleInitApp() { + Torus torus = new Torus(10, 6, 1, 3); +// Torus torus = new Torus(50, 30, 1, 3); + Geometry g = new Geometry("Torus Geom", torus); + g.rotate(-FastMath.HALF_PI, 0, 0); + g.center(); +// g.move(0, 1, 0); + + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setFloat("Shininess", 32f); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Ambient", ColorRGBA.Black); + mat.setColor("Diffuse", ColorRGBA.White); + mat.setColor("Specular", ColorRGBA.White); +// mat.setBoolean("VertexLighting", true); +// mat.setBoolean("LowQuality", true); + g.setMaterial(mat); + + rootNode.attachChild(g); + + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + rootNode.attachChild(lightMdl); + + pl = new PointLight(); + pl.setColor(ColorRGBA.Green); + pl.setRadius(4f); + rootNode.addLight(pl); + + DirectionalLight dl = new DirectionalLight(); + dl.setColor(ColorRGBA.Red); + dl.setDirection(new Vector3f(0, 1, 0)); + rootNode.addLight(dl); + } + + @Override + public void simpleUpdate(float tpf){ +// cam.setLocation(new Vector3f(5.0347548f, 6.6481347f, 3.74853f)); +// cam.setRotation(new Quaternion(-0.19183293f, 0.80776674f, -0.37974006f, -0.40805697f)); + + pos += tpf * vel * 5f; + if (pos > 15){ + vel *= -1; + }else if (pos < -15){ + vel *= -1; + } + + pl.setPosition(new Vector3f(pos, 2, 0)); + lightMdl.setLocalTranslation(pl.getPosition()); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestManyLights.java b/jme3-examples/src/main/java/jme3test/light/TestManyLights.java new file mode 100644 index 000000000..644344b02 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestManyLights.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java new file mode 100644 index 000000000..114c3b6fb --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.PointLight; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.renderer.queue.RenderQueue; +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.shadow.EdgeFilteringMode; +import com.jme3.shadow.PointLightShadowFilter; +import com.jme3.shadow.PointLightShadowRenderer; + +public class TestPointLightShadows extends SimpleApplication { + public static final int SHADOWMAP_SIZE = 512; + + public static void main(String[] args) { + TestPointLightShadows app = new TestPointLightShadows(); + app.start(); + } + Node lightNode; + PointLightShadowRenderer plsr; + PointLightShadowFilter plsf; + + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(10); + cam.setLocation(new Vector3f(0.040581334f, 1.7745866f, 6.155161f)); + cam.setRotation(new Quaternion(4.3868728E-5f, 0.9999293f, -0.011230096f, 0.0039059948f)); + + + Node scene = (Node) assetManager.loadModel("Models/Test/CornellBox.j3o"); + scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + rootNode.attachChild(scene); + rootNode.getChild("Cube").setShadowMode(RenderQueue.ShadowMode.Receive); + lightNode = (Node) rootNode.getChild("Lamp"); + Geometry lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + //Geometry lightMdl = new Geometry("Light", new Box(.1f,.1f,.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightMdl.setShadowMode(RenderQueue.ShadowMode.Off); + lightNode.attachChild(lightMdl); + //lightMdl.setLocalTranslation(lightNode.getLocalTranslation()); + + + Geometry box = new Geometry("box", new Box(0.2f, 0.2f, 0.2f)); + //Geometry lightMdl = new Geometry("Light", new Box(.1f,.1f,.1f)); + box.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + box.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + rootNode.attachChild(box); + box.setLocalTranslation(-1f, 0.5f, -2); + + + plsr = new PointLightShadowRenderer(assetManager, SHADOWMAP_SIZE); + plsr.setLight((PointLight) scene.getLocalLightList().get(0)); + plsr.setEdgeFilteringMode(EdgeFilteringMode.PCF4); + // plsr.setFlushQueues(false); + //plsr.displayFrustum(); + plsr.displayDebug(); + viewPort.addProcessor(plsr); + + +// PointLight pl = new PointLight(); +// pl.setPosition(new Vector3f(0, 0.5f, 0)); +// pl.setRadius(5); +// rootNode.addLight(pl); +// +// Geometry lightMdl2 = new Geometry("Light2", new Sphere(10, 10, 0.1f)); +// //Geometry lightMdl = new Geometry("Light", new Box(.1f,.1f,.1f)); +// lightMdl2.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); +// lightMdl2.setShadowMode(RenderQueue.ShadowMode.Off); +// rootNode.attachChild(lightMdl2); +// lightMdl2.setLocalTranslation(pl.getPosition()); +// PointLightShadowRenderer plsr2 = new PointLightShadowRenderer(assetManager, 512); +// plsr2.setShadowIntensity(0.3f); +// plsr2.setLight(pl); +// plsr2.setEdgeFilteringMode(EdgeFilteringMode.PCF4); +// // plsr.displayDebug(); +// viewPort.addProcessor(plsr2); + + + plsf = new PointLightShadowFilter(assetManager, SHADOWMAP_SIZE); + plsf.setLight((PointLight) scene.getLocalLightList().get(0)); + plsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4); + plsf.setEnabled(false); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + fpp.addFilter(plsf); + viewPort.addProcessor(fpp); + + ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, plsr, plsf, guiNode, inputManager, viewPort); + } + + @Override + public void simpleUpdate(float tpf) { + // lightNode.move(FastMath.cos(tpf) * 0.4f, 0, FastMath.sin(tpf) * 0.4f); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java b/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java new file mode 100644 index 000000000..4c74b0664 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.Savable; +import com.jme3.font.BitmapText; +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.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.FXAAFilter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.control.Control; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.shadow.PssmShadowFilter; +import com.jme3.shadow.PssmShadowRenderer; +import com.jme3.shadow.PssmShadowRenderer.CompareMode; +import com.jme3.shadow.PssmShadowRenderer.FilterMode; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; +import com.jme3.util.TangentBinormalGenerator; +import java.io.IOException; + +public class TestPssmShadow extends SimpleApplication implements ActionListener { + + private Spatial[] obj; + private Material[] mat; + private boolean hardwareShadows = false; + private PssmShadowRenderer pssmRenderer; + private PssmShadowFilter pssmFilter; + private Geometry ground; + private Material matGroundU; + private Material matGroundL; + + public static void main(String[] args) { + TestPssmShadow app = new TestPssmShadow(); + app.start(); + } + + public void loadScene() { + obj = new Spatial[2]; + mat = new Material[2]; + mat[0] = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); + mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); + mat[1].setBoolean("UseMaterialColors", true); + mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f)); + mat[1].setColor("Diffuse", ColorRGBA.White.clone()); + + + obj[0] = new Geometry("sphere", new Sphere(30, 30, 2)); + obj[0].setShadowMode(ShadowMode.CastAndReceive); + obj[1] = new Geometry("cube", new Box(1.0f, 1.0f, 1.0f)); + obj[1].setShadowMode(ShadowMode.CastAndReceive); + TangentBinormalGenerator.generate(obj[1]); + TangentBinormalGenerator.generate(obj[0]); + + + for (int i = 0; i < 60; i++) { + Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false); + t.setLocalScale(FastMath.nextRandomFloat() * 10f); + t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]); + rootNode.attachChild(t); + t.setLocalTranslation(FastMath.nextRandomFloat() * 200f, FastMath.nextRandomFloat() * 30f + 20, 30f * (i + 2f)); + } + + Box b = new Box(new Vector3f(0, 10, 550), 1000, 2, 1000); + b.scaleTextureCoordinates(new Vector2f(10, 10)); + ground = new Geometry("soil", b); + matGroundU = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matGroundU.setColor("Color", ColorRGBA.Green); + + + matGroundL = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matGroundL.setTexture("DiffuseMap", grass); + + ground.setMaterial(matGroundL); + + ground.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(ground); + + l = new DirectionalLight(); + l.setDirection(new Vector3f(-1, -1, -1)); + rootNode.addLight(l); + + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(0.5f)); + rootNode.addLight(al); + + Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false); + sky.setLocalScale(350); + + rootNode.attachChild(sky); + } + DirectionalLight l; + + @Override + public void simpleInitApp() { + // put the camera in a bad position + cam.setLocation(new Vector3f(65.25412f, 44.38738f, 9.087874f)); + cam.setRotation(new Quaternion(0.078139365f, 0.050241485f, -0.003942559f, 0.9956679f)); + + flyCam.setMoveSpeed(100); + + loadScene(); + + pssmRenderer = new PssmShadowRenderer(assetManager, 1024, 3); + //pssmRenderer.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + pssmRenderer.setDirection(new Vector3f(-0.5973172f, -0.56583486f, 0.8846725f).normalizeLocal()); + pssmRenderer.setLambda(0.55f); + pssmRenderer.setShadowIntensity(0.6f); + pssmRenderer.setCompareMode(CompareMode.Software); + pssmRenderer.setFilterMode(FilterMode.Dither); + + pssmRenderer.displayFrustum(); + viewPort.addProcessor(pssmRenderer); + + + + pssmFilter = new PssmShadowFilter(assetManager, 1024, 3); + //pssmFilter.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + pssmRenderer.setDirection(new Vector3f(-0.5973172f, -0.56583486f, 0.8846725f).normalizeLocal()); + pssmFilter.setLambda(0.55f); + pssmFilter.setShadowIntensity(0.6f); + pssmFilter.setCompareMode(CompareMode.Software); + pssmFilter.setFilterMode(FilterMode.Dither); + pssmFilter.setEnabled(false); + + +// pssmFilter.setShadowZFadeLength(300); +// pssmFilter.setShadowZExtend(500); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + // fpp.setNumSamples(4); + fpp.addFilter(pssmFilter); + + viewPort.addProcessor(fpp); + + + initInputs(); + } + BitmapText infoText; + + private void initInputs() { + /** Write text on the screen (HUD) */ + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + infoText = new BitmapText(guiFont, false); + infoText.setSize(guiFont.getCharSet().getRenderedSize()); + + + inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("changeFiltering", new KeyTrigger(KeyInput.KEY_F)); + 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.addMapping("switchGroundMat", new KeyTrigger(KeyInput.KEY_M)); + inputManager.addMapping("splits", new KeyTrigger(KeyInput.KEY_X)); + + inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_NUMPAD8)); + inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_NUMPAD2)); + inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_NUMPAD6)); + inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_NUMPAD4)); + inputManager.addMapping("fwd", new KeyTrigger(KeyInput.KEY_PGUP)); + inputManager.addMapping("back", new KeyTrigger(KeyInput.KEY_PGDN)); + + + + inputManager.addListener(this, "lambdaUp", "lambdaDown", "toggleHW", "toggle", "ShadowUp", "ShadowDown", "ThicknessUp", "ThicknessDown", "changeFiltering", + "switchGroundMat", "splits", "up", "down", "right", "left", "fwd", "back"); + + } + + private void print(String str) { + infoText.setText(str); + infoText.setLocalTranslation(cam.getWidth() * 0.5f - infoText.getLineWidth() * 0.5f, infoText.getLineHeight(), 0); + guiNode.attachChild(infoText); + infoText.removeControl(ctrl); + infoText.addControl(ctrl); + } + AbstractControl ctrl = new AbstractControl() { + + float time; + + @Override + protected void controlUpdate(float tpf) { + time += tpf; + if (time > 3) { + spatial.removeFromParent(); + spatial.removeControl(this); + } + } + + @Override + public void setSpatial(Spatial spatial) { + super.setSpatial(spatial); + time = 0; + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } + + public Control cloneForSpatial(Spatial spatial) { + return null; + } + }; + int filteringIndex = 2; + int renderModeIndex = 0; + + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("toggle") && keyPressed) { + renderModeIndex += 1; + renderModeIndex %= 3; + + switch (renderModeIndex) { + case 0: + viewPort.addProcessor(pssmRenderer); + break; + case 1: + viewPort.removeProcessor(pssmRenderer); + pssmFilter.setEnabled(true); + break; + case 2: + pssmFilter.setEnabled(false); + break; + } + + + + } else if (name.equals("toggleHW") && keyPressed) { + hardwareShadows = !hardwareShadows; + pssmRenderer.setCompareMode(hardwareShadows ? CompareMode.Hardware : CompareMode.Software); + pssmFilter.setCompareMode(hardwareShadows ? CompareMode.Hardware : CompareMode.Software); + System.out.println("HW Shadows: " + hardwareShadows); + } +// +// renderShadows = true; +// viewPort.addProcessor(pssmRenderer); + + if (name.equals("changeFiltering") && keyPressed) { + filteringIndex = (filteringIndex + 1) % FilterMode.values().length; + FilterMode m = FilterMode.values()[filteringIndex]; + pssmRenderer.setFilterMode(m); + pssmFilter.setFilterMode(m); + print("Filter mode : " + m.toString()); + } + + if (name.equals("lambdaUp") && keyPressed) { + pssmRenderer.setLambda(pssmRenderer.getLambda() + 0.01f); + pssmFilter.setLambda(pssmRenderer.getLambda() + 0.01f); + System.out.println("Lambda : " + pssmRenderer.getLambda()); + } else if (name.equals("lambdaDown") && keyPressed) { + pssmRenderer.setLambda(pssmRenderer.getLambda() - 0.01f); + pssmFilter.setLambda(pssmRenderer.getLambda() - 0.01f); + System.out.println("Lambda : " + pssmRenderer.getLambda()); + } + + if (name.equals("ShadowUp") && keyPressed) { + pssmRenderer.setShadowIntensity(pssmRenderer.getShadowIntensity() + 0.1f); + pssmFilter.setShadowIntensity(pssmRenderer.getShadowIntensity() + 0.1f); + System.out.println("Shadow intensity : " + pssmRenderer.getShadowIntensity()); + } + if (name.equals("ShadowDown") && keyPressed) { + pssmRenderer.setShadowIntensity(pssmRenderer.getShadowIntensity() - 0.1f); + pssmFilter.setShadowIntensity(pssmRenderer.getShadowIntensity() - 0.1f); + System.out.println("Shadow intensity : " + pssmRenderer.getShadowIntensity()); + } + if (name.equals("ThicknessUp") && keyPressed) { + pssmRenderer.setEdgesThickness(pssmRenderer.getEdgesThickness() + 1); + pssmFilter.setEdgesThickness(pssmRenderer.getEdgesThickness() + 1); + System.out.println("Shadow thickness : " + pssmRenderer.getEdgesThickness()); + } + if (name.equals("ThicknessDown") && keyPressed) { + pssmRenderer.setEdgesThickness(pssmRenderer.getEdgesThickness() - 1); + pssmFilter.setEdgesThickness(pssmRenderer.getEdgesThickness() - 1); + System.out.println("Shadow thickness : " + pssmRenderer.getEdgesThickness()); + } + if (name.equals("switchGroundMat") && keyPressed) { + if (ground.getMaterial() == matGroundL) { + ground.setMaterial(matGroundU); + } else { + ground.setMaterial(matGroundL); + } + } + +// if (name.equals("splits") && keyPressed) { +// pssmRenderer.displayFrustum(); +// } + + + if (name.equals("up")) { + up = keyPressed; + } + if (name.equals("down")) { + down = keyPressed; + } + if (name.equals("right")) { + right = keyPressed; + } + if (name.equals("left") ) { + left = keyPressed; + } + if (name.equals("fwd")) { + fwd = keyPressed; + } + if (name.equals("back")) { + back = keyPressed; + } + + } + boolean up = false; + boolean down = false; + boolean left = false; + boolean right = false; + boolean fwd = false; + boolean back = false; + float time = 0; + float s = 1f; + + @Override + public void simpleUpdate(float tpf) { + if (up) { + Vector3f v = l.getDirection(); + v.y += tpf / s; + setDir(v); + } + if (down) { + Vector3f v = l.getDirection(); + v.y -= tpf / s; + setDir(v); + } + if (right) { + Vector3f v = l.getDirection(); + v.x += tpf / s; + setDir(v); + } + if (left) { + Vector3f v = l.getDirection(); + v.x -= tpf / s; + setDir(v); + } + if (fwd) { + Vector3f v = l.getDirection(); + v.z += tpf / s; + setDir(v); + } + if (back) { + Vector3f v = l.getDirection(); + v.z -= tpf / s; + setDir(v); + } + + } + + private void setDir(Vector3f v) { + l.setDirection(v); + pssmFilter.setDirection(v); + pssmRenderer.setDirection(v); + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestShadow.java b/jme3-examples/src/main/java/jme3test/light/TestShadow.java new file mode 100644 index 000000000..db0c3768f --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestShadow.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.WireFrustum; +import com.jme3.scene.shape.Box; +import com.jme3.shadow.BasicShadowRenderer; +import com.jme3.shadow.ShadowUtil; + +public class TestShadow extends SimpleApplication { + + float angle; + Spatial lightMdl; + Spatial teapot; + Geometry frustumMdl; + WireFrustum frustum; + + private BasicShadowRenderer bsr; + private Vector3f[] points; + + { + points = new Vector3f[8]; + for (int i = 0; i < points.length; i++) points[i] = new Vector3f(); + } + + public static void main(String[] args){ + TestShadow app = new TestShadow(); + app.start(); + } + + @Override + public void simpleInitApp() { + // put the camera in a bad position + cam.setLocation(new Vector3f(0.7804813f, 1.7502685f, -2.1556435f)); + cam.setRotation(new Quaternion(0.1961598f, -0.7213164f, 0.2266092f, 0.6243975f)); + cam.setFrustumFar(10); + + Material mat = assetManager.loadMaterial("Common/Materials/WhiteColor.j3m"); + rootNode.setShadowMode(ShadowMode.Off); + Box floor = new Box(Vector3f.ZERO, 3, 0.1f, 3); + Geometry floorGeom = new Geometry("Floor", floor); + floorGeom.setMaterial(mat); + floorGeom.setLocalTranslation(0,-0.2f,0); + floorGeom.setShadowMode(ShadowMode.Receive); + rootNode.attachChild(floorGeom); + + teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + teapot.setLocalScale(2f); + teapot.setMaterial(mat); + teapot.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(teapot); +// lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); +// lightMdl.setMaterial(mat); +// // disable shadowing for light representation +// lightMdl.setShadowMode(ShadowMode.Off); +// rootNode.attachChild(lightMdl); + + bsr = new BasicShadowRenderer(assetManager, 512); + bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + viewPort.addProcessor(bsr); + + frustum = new WireFrustum(bsr.getPoints()); + frustumMdl = new Geometry("f", frustum); + frustumMdl.setCullHint(Spatial.CullHint.Never); + frustumMdl.setShadowMode(ShadowMode.Off); + frustumMdl.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")); + frustumMdl.getMaterial().getAdditionalRenderState().setWireframe(true); + frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red); + rootNode.attachChild(frustumMdl); + } + + @Override + public void simpleUpdate(float tpf){ + Camera shadowCam = bsr.getShadowCamera(); + ShadowUtil.updateFrustumPoints2(shadowCam, points); + + frustum.update(points); + // rotate teapot around Y axis + teapot.rotate(0, tpf * 0.25f, 0); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java b/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java new file mode 100644 index 000000000..e9ca341b8 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.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.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.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.util.TangentBinormalGenerator; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TestShadowsPerf extends SimpleApplication { + + float angle; + PointLight pl; + Spatial lightMdl; + + public static void main(String[] args) { + TestShadowsPerf app = new TestShadowsPerf(); + app.start(); + } + Geometry sphere; + Material mat; + + @Override + public void simpleInitApp() { + Logger.getLogger("com.jme3").setLevel(Level.SEVERE); + flyCam.setMoveSpeed(50); + flyCam.setEnabled(false); + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + cam.setLocation(new Vector3f(-53.952988f, 27.15874f, -32.875023f)); + cam.setRotation(new Quaternion(0.1564309f, 0.6910534f, -0.15713608f, 0.6879555f)); + +// cam.setLocation(new Vector3f(53.64627f, 130.56f, -11.247704f)); +// cam.setRotation(new Quaternion(-6.5737107E-4f, 0.76819664f, -0.64021313f, -7.886125E-4f)); +//// + cam.setFrustumFar(500); + + mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); + + Box b = new Box(Vector3f.ZERO, 800, 1, 700); + b.scaleTextureCoordinates(new Vector2f(50, 50)); + Geometry ground = new Geometry("ground", b); + ground.setMaterial(mat); + rootNode.attachChild(ground); + ground.setShadowMode(ShadowMode.Receive); + + Sphere sphMesh = new Sphere(32, 32, 1); + sphMesh.setTextureMode(Sphere.TextureMode.Projected); + sphMesh.updateGeometry(32, 32, 1, false, false); + TangentBinormalGenerator.generate(sphMesh); + + sphere = new Geometry("Rock Ball", sphMesh); + sphere.setLocalTranslation(0, 5, 0); + sphere.setMaterial(mat); + sphere.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(sphere); + + + + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(0, -1, 0).normalizeLocal()); + dl.setColor(ColorRGBA.White); + rootNode.addLight(dl); + + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(0.7f)); + rootNode.addLight(al); + //rootNode.setShadowMode(ShadowMode.CastAndReceive); + + createballs(); + + final DirectionalLightShadowRenderer pssmRenderer = new DirectionalLightShadowRenderer(assetManager, 1024, 4); + viewPort.addProcessor(pssmRenderer); +// +// final PssmShadowFilter pssmRenderer = new PssmShadowFilter(assetManager, 1024, 4); +// FilterPostProcessor fpp = new FilterPostProcessor(assetManager); +// fpp.addFilter(pssmRenderer); +// viewPort.addProcessor(fpp); + + pssmRenderer.setLight(dl); + pssmRenderer.setLambda(0.55f); + pssmRenderer.setShadowIntensity(0.55f); + pssmRenderer.setShadowCompareMode(com.jme3.shadow.CompareMode.Software); + pssmRenderer.setEdgeFilteringMode(EdgeFilteringMode.PCF4); + //pssmRenderer.displayDebug(); + + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("display") && isPressed) { + //pssmRenderer.debugFrustrums(); + System.out.println("tetetetet"); + } + if (name.equals("add") && isPressed) { + createballs(); + } + } + }, "display", "add"); + inputManager.addMapping("display", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("add", new KeyTrigger(KeyInput.KEY_RETURN)); + } + int val = 0; + + private void createballs() { + System.out.println((frames / time) + ";" + val); + + + for (int i = val; i < val+1 ; i++) { + + Geometry s = sphere.clone().clone(false); + s.setMaterial(mat); + s.setLocalTranslation(i - 30, 5, (((i) * 2) % 40) - 50); + s.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(s); + } + if (val == 300) { + stop(); + } + val += 1; + time = 0; + frames = 0; + } + float time; + float frames = 0; + + @Override + public void simpleUpdate(float tpf) { + time += tpf; + frames++; + if (time > 1) { + createballs(); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestSimpleLighting.java b/jme3-examples/src/main/java/jme3test/light/TestSimpleLighting.java new file mode 100644 index 000000000..2f1cd3403 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestSimpleLighting.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.TangentBinormalGenerator; + +public class TestSimpleLighting extends SimpleApplication { + + float angle; + PointLight pl; + Geometry lightMdl; + + public static void main(String[] args){ + TestSimpleLighting app = new TestSimpleLighting(); + app.start(); + } + + @Override + public void simpleInitApp() { + Geometry teapot = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); + TangentBinormalGenerator.generate(teapot.getMesh(), true); + + teapot.setLocalScale(2f); + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); +// mat.selectTechnique("GBuf"); + mat.setFloat("Shininess", 12); + mat.setBoolean("UseMaterialColors", true); + +// mat.setTexture("ColorRamp", assetManager.loadTexture("Textures/ColorRamp/cloudy.png")); +// +// mat.setBoolean("VTangent", true); +// mat.setBoolean("Minnaert", true); +// mat.setBoolean("WardIso", true); +// mat.setBoolean("VertexLighting", true); +// mat.setBoolean("LowQuality", true); +// mat.setBoolean("HighQuality", true); + + mat.setColor("Ambient", ColorRGBA.Black); + mat.setColor("Diffuse", ColorRGBA.Gray); + mat.setColor("Specular", ColorRGBA.Gray); + + teapot.setMaterial(mat); + rootNode.attachChild(teapot); + + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightMdl.getMesh().setStatic(); + rootNode.attachChild(lightMdl); + + pl = new PointLight(); + pl.setColor(ColorRGBA.White); + pl.setRadius(4f); + rootNode.addLight(pl); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + dl.setColor(ColorRGBA.Green); + rootNode.addLight(dl); + } + + @Override + public void simpleUpdate(float tpf){ +// cam.setLocation(new Vector3f(2.0632997f, 1.9493936f, 2.6885238f)); +// cam.setRotation(new Quaternion(-0.053555284f, 0.9407851f, -0.17754152f, -0.28378546f)); + + angle += tpf; + angle %= FastMath.TWO_PI; + + pl.setPosition(new Vector3f(FastMath.cos(angle) * 2f, 0.5f, FastMath.sin(angle) * 2f)); + lightMdl.setLocalTranslation(pl.getPosition()); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java new file mode 100644 index 000000000..8d29ae498 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.SpotLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.TangentBinormalGenerator; + +public class TestSpotLight extends SimpleApplication { + + private Vector3f lightTarget = new Vector3f(12, 3.5f, 30); + + public static void main(String[] args){ + TestSpotLight app = new TestSpotLight(); + app.start(); + } + + SpotLight spot; + Geometry lightMdl; + public void setupLighting(){ + AmbientLight al=new AmbientLight(); + al.setColor(ColorRGBA.White.mult(0.8f)); + rootNode.addLight(al); + + spot=new SpotLight(); + + spot.setSpotRange(1000); + spot.setSpotInnerAngle(5*FastMath.DEG_TO_RAD); + spot.setSpotOuterAngle(10*FastMath.DEG_TO_RAD); + spot.setPosition(new Vector3f(77.70334f, 34.013165f, 27.1017f)); + spot.setDirection(lightTarget.subtract(spot.getPosition())); + spot.setColor(ColorRGBA.White.mult(2)); + rootNode.addLight(spot); + + +// PointLight pl=new PointLight(); +// pl.setPosition(new Vector3f(77.70334f, 34.013165f, 27.1017f)); +// pl.setRadius(1000); +// pl.setColor(ColorRGBA.White.mult(2)); +// rootNode.addLight(pl); + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightMdl.setLocalTranslation(new Vector3f(77.70334f, 34.013165f, 27.1017f)); + lightMdl.setLocalScale(5); + rootNode.attachChild(lightMdl); + +// DirectionalLight dl = new DirectionalLight(); +// dl.setDirection(lightTarget.subtract(new Vector3f(77.70334f, 34.013165f, 27.1017f))); +// dl.setColor(ColorRGBA.White.mult(2)); +// rootNode.addLight(dl); + + + } + + public void setupFloor(){ + Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); + mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat); + mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat); + // mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat); + mat.setFloat("Shininess",3); + // mat.setBoolean("VertexLighting", true); + + + Box floor = new Box(Vector3f.ZERO, 50, 1f, 50); + TangentBinormalGenerator.generate(floor); + floor.scaleTextureCoordinates(new Vector2f(5, 5)); + Geometry floorGeom = new Geometry("Floor", floor); + floorGeom.setMaterial(mat); + floorGeom.setShadowMode(ShadowMode.Receive); + rootNode.attachChild(floorGeom); + } + + + + public void setupSignpost(){ + Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml"); + Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); + // mat.setBoolean("VertexLighting", true); + signpost.setMaterial(mat); + signpost.rotate(0, FastMath.HALF_PI, 0); + signpost.setLocalTranslation(12, 3.5f, 30); + signpost.setLocalScale(4); + signpost.setShadowMode(ShadowMode.CastAndReceive); + TangentBinormalGenerator.generate(signpost); + rootNode.attachChild(signpost); + } + + @Override + public void simpleInitApp() { + cam.setLocation(new Vector3f(27.492603f, 29.138166f, -13.232513f)); + cam.setRotation(new Quaternion(0.25168246f, -0.10547892f, 0.02760565f, 0.96164864f)); + flyCam.setMoveSpeed(30); + + setupLighting(); + setupFloor(); + setupSignpost(); + + + } + + float angle; + + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + angle += tpf; + angle %= FastMath.TWO_PI; + + spot.setPosition(new Vector3f(FastMath.cos(angle) * 30f, 34.013165f, FastMath.sin(angle) * 30f)); + lightMdl.setLocalTranslation(spot.getPosition()); + spot.setDirection(lightTarget.subtract(spot.getPosition())); + } + + + +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java new file mode 100644 index 000000000..88f5db1c6 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.SpotLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.post.FilterPostProcessor; +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.shader.VarType; +import com.jme3.shadow.CompareMode; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.shadow.SpotLightShadowFilter; +import com.jme3.shadow.SpotLightShadowRenderer; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.TangentBinormalGenerator; + +public class TestSpotLightShadows extends SimpleApplication { + + private Vector3f lightTarget = new Vector3f(12, 3.5f, 30); + + public static void main(String[] args) { + TestSpotLightShadows app = new TestSpotLightShadows(); + app.start(); + } + SpotLight spot; + Geometry lightMdl; + + public void setupLighting() { + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(0.3f)); + rootNode.addLight(al); + + rootNode.setShadowMode(ShadowMode.CastAndReceive); + + spot = new SpotLight(); + + spot.setSpotRange(1000); + spot.setSpotInnerAngle(5f * FastMath.DEG_TO_RAD); + spot.setSpotOuterAngle(10 * FastMath.DEG_TO_RAD); + spot.setPosition(new Vector3f(70.70334f, 34.013165f, 27.1017f)); + spot.setDirection(lightTarget.subtract(spot.getPosition())); + spot.setColor(ColorRGBA.White.mult(2)); + rootNode.addLight(spot); + + +// PointLight pl=new PointLight(); +// pl.setPosition(new Vector3f(77.70334f, 34.013165f, 27.1017f)); +// pl.setRadius(1000); +// pl.setColor(ColorRGBA.White.mult(2)); +// rootNode.addLight(pl); + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightMdl.setLocalTranslation(new Vector3f(77.70334f, 34.013165f, 27.1017f)); + lightMdl.setLocalScale(5); + rootNode.attachChild(lightMdl); + +// DirectionalLight dl = new DirectionalLight(); +// dl.setDirection(lightTarget.subtract(new Vector3f(77.70334f, 34.013165f, 27.1017f))); +// dl.setColor(ColorRGBA.White.mult(0.7f)); +// rootNode.addLight(dl); + + + final SpotLightShadowRenderer slsr = new SpotLightShadowRenderer(assetManager, 512); + slsr.setLight(spot); + slsr.setShadowIntensity(0.5f); + slsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + viewPort.addProcessor(slsr); + + SpotLightShadowFilter slsf = new SpotLightShadowFilter(assetManager, 512); + slsf.setLight(spot); + slsf.setShadowIntensity(0.5f); + slsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + slsf.setEnabled(false); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + fpp.addFilter(slsf); + viewPort.addProcessor(fpp); + + ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, slsr, slsf, guiNode, inputManager, viewPort); + + inputManager.addListener(new ActionListener() { + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("stop") && isPressed) { + stop = !stop; + // slsr.displayFrustum(); + System.out.println("pos : " + spot.getPosition()); + System.out.println("dir : " + spot.getDirection()); + } + } + }, "stop"); + + inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_1)); + + } + + public void setupFloor() { + Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); + mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat); + mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Diffuse", ColorRGBA.White.clone()); + mat.setColor("Ambient", ColorRGBA.White.clone()); + // mat.setColor("Specular", ColorRGBA.White.clone()); + // mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat); + mat.setFloat("Shininess", 0); + // mat.setBoolean("VertexLighting", true); + + + Box floor = new Box(Vector3f.ZERO, 50, 1f, 50); + TangentBinormalGenerator.generate(floor); + floor.scaleTextureCoordinates(new Vector2f(5, 5)); + Geometry floorGeom = new Geometry("Floor", floor); + floorGeom.setMaterial(mat); + floorGeom.setShadowMode(ShadowMode.Receive); + rootNode.attachChild(floorGeom); + } + + public void setupSignpost() { + Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml"); + Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); + // mat.setBoolean("VertexLighting", true); + signpost.setMaterial(mat); + signpost.rotate(0, FastMath.HALF_PI, 0); + signpost.setLocalTranslation(12, 3.5f, 30); + signpost.setLocalScale(4); + signpost.setShadowMode(ShadowMode.CastAndReceive); + TangentBinormalGenerator.generate(signpost); + rootNode.attachChild(signpost); + } + + @Override + public void simpleInitApp() { + cam.setLocation(new Vector3f(27.492603f, 29.138166f, -13.232513f)); + cam.setRotation(new Quaternion(0.25168246f, -0.10547892f, 0.02760565f, 0.96164864f)); + flyCam.setMoveSpeed(30); + + setupLighting(); + setupFloor(); + setupSignpost(); + + + } + float angle; + boolean stop = true; + + @Override + public void simpleUpdate(float tpf) { + if (!stop) { + super.simpleUpdate(tpf); + angle += tpf; + angle %= FastMath.TWO_PI; + + spot.setPosition(new Vector3f(FastMath.cos(angle) * 30f, 34.013165f, FastMath.sin(angle) * 30f)); + lightMdl.setLocalTranslation(spot.getPosition()); + spot.setDirection(lightTarget.subtract(spot.getPosition())); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java new file mode 100644 index 000000000..4fb58602c --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +import com.jme3.font.BitmapText; +import com.jme3.light.AmbientLight; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; + +/** + * Uses the terrain's lighting texture with normal maps and lights. + * + * @author bowens + */ +public class TestSpotLightTerrain extends SimpleApplication { + + private TerrainQuad terrain; + Material matTerrain; + Material matWire; + boolean wireframe = false; + boolean triPlanar = false; + boolean wardiso = false; + boolean minnaert = false; + protected BitmapText hintText; + PointLight pl; + Geometry lightMdl; + private float grassScale = 64; + private float dirtScale = 16; + private float rockScale = 128; + SpotLight sl; + + public static void main(String[] args) { + TestSpotLightTerrain app = new TestSpotLightTerrain(); + app.start(); + } + + + @Override + public void simpleInitApp() { + makeTerrain(); + flyCam.setMoveSpeed(50); + + sl = new SpotLight(); + sl.setSpotRange(100); + sl.setSpotOuterAngle(20 * FastMath.DEG_TO_RAD); + sl.setSpotInnerAngle(15 * FastMath.DEG_TO_RAD); + sl.setDirection(new Vector3f(-0.39820394f, -0.73094344f, 0.55421597f)); + sl.setPosition(new Vector3f(-64.61567f, -87.615425f, -202.41328f)); + rootNode.addLight(sl); + + AmbientLight ambLight = new AmbientLight(); + ambLight.setColor(new ColorRGBA(0.8f, 0.8f, 0.8f, 0.2f)); + rootNode.addLight(ambLight); + + cam.setLocation(new Vector3f(-41.219646f, -84.8363f, -171.67267f)); + cam.setRotation(new Quaternion(-0.04562731f, 0.89917684f, -0.09668826f, -0.4243236f)); + sl.setDirection(cam.getDirection()); + sl.setPosition(cam.getLocation()); + + } + + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + sl.setDirection(cam.getDirection()); + sl.setPosition(cam.getLocation()); + + } + + private void makeTerrain() { + // TERRAIN TEXTURE material + matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matTerrain.setBoolean("useTriPlanarMapping", false); + matTerrain.setBoolean("WardIso", true); + + // ALPHA map (for splat textures) + matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png")); + matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png")); + + // HEIGHTMAP image (for the terrain heightmap) + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); + + + // GRASS texture + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap", grass); + matTerrain.setFloat("DiffuseMap_0_scale", grassScale); + + // DIRT texture + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_1", dirt); + matTerrain.setFloat("DiffuseMap_1_scale", dirtScale); + + // ROCK texture + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_2", rock); + matTerrain.setFloat("DiffuseMap_2_scale", rockScale); + + // BRICK texture + Texture brick = assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"); + brick.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_3", brick); + matTerrain.setFloat("DiffuseMap_3_scale", rockScale); + + // RIVER ROCK texture + Texture riverRock = assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg"); + riverRock.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_4", riverRock); + matTerrain.setFloat("DiffuseMap_4_scale", rockScale); + + + Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); + normalMap0.setWrap(WrapMode.Repeat); + Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); + normalMap1.setWrap(WrapMode.Repeat); + Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); + normalMap2.setWrap(WrapMode.Repeat); + matTerrain.setTexture("NormalMap", normalMap0); + matTerrain.setTexture("NormalMap_1", normalMap2); + matTerrain.setTexture("NormalMap_2", normalMap2); + matTerrain.setTexture("NormalMap_4", normalMap2); + + // WIREFRAME material + matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matWire.getAdditionalRenderState().setWireframe(true); + matWire.setColor("Color", ColorRGBA.Green); + + createSky(); + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + //heightmap = new HillHeightMap(1025, 1000, 50, 100, (byte) 3); + + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f); + heightmap.load(); + + } catch (Exception e) { + e.printStackTrace(); + } + + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations + TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); + control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); + terrain.addControl(control); + terrain.setMaterial(matTerrain); + terrain.setModelBound(new BoundingBox()); + terrain.updateModelBound(); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(1f, 1f, 1f); + rootNode.attachChild(terrain); + } + + private void createSky() { + Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg"); + Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg"); + Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg"); + Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg"); + Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg"); + Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg"); + + Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down); + rootNode.attachChild(sky); + } + + +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentGen.java b/jme3-examples/src/main/java/jme3test/light/TestTangentGen.java new file mode 100644 index 000000000..042a85705 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentGen.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java new file mode 100644 index 000000000..a786d767d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java @@ -0,0 +1,136 @@ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.*; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.TangentBinormalGenerator; + +/** + * + * @author Kirusha + */ +public class TestTangentGenBadModels extends SimpleApplication { + + float angle; + PointLight pl; + Geometry lightMdl; + + public static void main(String[] args){ + TestTangentGenBadModels app = new TestTangentGenBadModels(); + app.start(); + } + + @Override + public void simpleInitApp() { +// assetManager.registerLocator("http://jme-glsl-shaders.googlecode.com/hg/assets/Models/LightBlow/", UrlLocator.class); +// assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/", UrlLocator.class); + + final Spatial badModel = assetManager.loadModel("Models/TangentBugs/test.blend"); +// badModel.setLocalScale(1f); + + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setTexture("NormalMap", assetManager.loadTexture("Models/TangentBugs/test_normal.png")); +// Material mat = assetManager.loadMaterial("Textures/BumpMapTest/Tangent.j3m"); + badModel.setMaterial(mat); + rootNode.attachChild(badModel); + + // TODO: For some reason blender loader fails to load this. + // need to check it +// Spatial model = assetManager.loadModel("test.blend"); +// rootNode.attachChild(model); + + final Node debugTangents = new Node("debug tangents"); + debugTangents.setCullHint(CullHint.Always); + rootNode.attachChild(debugTangents); + + final Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m"); + + badModel.depthFirstTraversal(new SceneGraphVisitorAdapter(){ + @Override + public void visit(Geometry g){ + Mesh m = g.getMesh(); + Material mat = g.getMaterial(); + +// if (mat.getParam("DiffuseMap") != null){ +// mat.setTexture("DiffuseMap", null); +// } + TangentBinormalGenerator.generate(m); + + Geometry debug = new Geometry( + "debug tangents geom", + TangentBinormalGenerator.genTbnLines(g.getMesh(), 0.2f) + ); + debug.getMesh().setLineWidth(1); + debug.setMaterial(debugMat); + debug.setCullHint(Spatial.CullHint.Never); + debug.setLocalTransform(g.getWorldTransform()); + debugTangents.attachChild(debug); + } + }); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.8f, -0.6f, -0.08f).normalizeLocal()); + dl.setColor(new ColorRGBA(1,1,1,1)); + rootNode.addLight(dl); + + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightMdl.getMesh().setStatic(); + rootNode.attachChild(lightMdl); + + pl = new PointLight(); + pl.setColor(ColorRGBA.White); +// rootNode.addLight(pl); + + + BitmapText info = new BitmapText(guiFont); + info.setText("Press SPACE to switch between lighting and tangent display"); + info.setQueueBucket(Bucket.Gui); + info.move(0, settings.getHeight() - info.getLineHeight(), 0); + rootNode.attachChild(info); + + inputManager.addMapping("space", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(new ActionListener() { + + private boolean isLit = true; + + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) return; + Material mat; + if (isLit){ + mat = assetManager.loadMaterial("Textures/BumpMapTest/Tangent.j3m"); + debugTangents.setCullHint(CullHint.Inherit); + }else{ + mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setTexture("NormalMap", assetManager.loadTexture("Models/TangentBugs/test_normal.png")); + debugTangents.setCullHint(CullHint.Always); + } + isLit = !isLit; + badModel.setMaterial(mat); + } + }, "space"); + } + + @Override + public void simpleUpdate(float tpf){ + angle += tpf; + angle %= FastMath.TWO_PI; + + pl.setPosition(new Vector3f(FastMath.cos(angle) * 2f, 2f, FastMath.sin(angle) * 2f)); + lightMdl.setLocalTranslation(pl.getPosition()); + } + + +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadUV.java b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadUV.java new file mode 100644 index 000000000..6b7a630e8 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadUV.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java b/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java new file mode 100644 index 000000000..caddeb583 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh; +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.*; +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; +import com.jme3.scene.shape.Sphere; +import com.jme3.shadow.CompareMode; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; + +public class TestTransparentShadow extends SimpleApplication { + + public static void main(String[] args){ + TestTransparentShadow app = new TestTransparentShadow(); + app.start(); + } + + public void simpleInitApp() { + + cam.setLocation(new Vector3f(5.700248f, 6.161693f, 5.1404157f)); + cam.setRotation(new Quaternion(-0.09441641f, 0.8993388f, -0.24089815f, -0.35248178f)); + + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + + Quad q = new Quad(20, 20); + q.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(10)); + Geometry geom = new Geometry("floor", q); + Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); + mat.setFloat("Shininess", 0); + geom.setMaterial(mat); + + geom.rotate(-FastMath.HALF_PI, 0, 0); + geom.center(); + geom.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(geom); + + // create the geometry and attach it + Spatial tree = assetManager.loadModel("Models/Tree/Tree.mesh.j3o"); + tree.setQueueBucket(Bucket.Transparent); + tree.setShadowMode(ShadowMode.CastAndReceive); + + + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(0.7f)); + rootNode.addLight(al); + + DirectionalLight dl1 = new DirectionalLight(); + dl1.setDirection(new Vector3f(0, -1, 0.5f).normalizeLocal()); + dl1.setColor(ColorRGBA.White.mult(1.5f)); + rootNode.addLight(dl1); + + rootNode.attachChild(tree); + + /** Uses Texture from jme3-test-data library! */ + ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30); + Material mat_red = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat_red.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png")); + //mat_red.getAdditionalRenderState().setDepthTest(true); + //mat_red.getAdditionalRenderState().setDepthWrite(true); + fire.setMaterial(mat_red); + fire.setImagesX(2); + fire.setImagesY(2); // 2x2 texture animation + fire.setEndColor(new ColorRGBA(1f, 0f, 0f, 1f)); // red + fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow + fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 2, 0)); + fire.setStartSize(0.6f); + fire.setEndSize(0.1f); + fire.setGravity(0, 0, 0); + fire.setLowLife(0.5f); + fire.setHighLife(1.5f); + fire.getParticleInfluencer().setVelocityVariation(0.3f); + fire.setLocalTranslation(1.0f, 0, 1.0f); + fire.setLocalScale(0.3f); + fire.setQueueBucket(Bucket.Translucent); + // rootNode.attachChild(fire); + + + Material mat2 = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); + + + Geometry ball = new Geometry("sphere", new Sphere(16, 16, 0.5f)); + ball.setMaterial(mat2); + ball.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(ball); + ball.setLocalTranslation(-1.0f, 1.5f, 1.0f); + + + final DirectionalLightShadowRenderer dlsRenderer = new DirectionalLightShadowRenderer(assetManager, 1024, 1); + dlsRenderer.setLight(dl1); + dlsRenderer.setLambda(0.55f); + dlsRenderer.setShadowIntensity(0.8f); + dlsRenderer.setShadowCompareMode(CompareMode.Software); + dlsRenderer.setEdgeFilteringMode(EdgeFilteringMode.Nearest); + dlsRenderer.displayDebug(); + viewPort.addProcessor(dlsRenderer); + inputManager.addMapping("stabilize", new KeyTrigger(KeyInput.KEY_B)); + + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("stabilize") && isPressed) { + dlsRenderer.setEnabledStabilization(!dlsRenderer.isEnabledStabilization()) ; + } + } + }, "stabilize"); + + } +} diff --git a/jme3-examples/src/main/java/jme3test/material/TestBumpModel.java b/jme3-examples/src/main/java/jme3test/material/TestBumpModel.java new file mode 100644 index 000000000..49028ba7e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/TestBumpModel.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.material; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.ogre.OgreMeshKey; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.TangentBinormalGenerator; + +public class TestBumpModel extends SimpleApplication { + + float angle; + PointLight pl; + Spatial lightMdl; + + public static void main(String[] args){ + TestBumpModel app = new TestBumpModel(); + app.start(); + } + + @Override + public void simpleInitApp() { + Spatial signpost = (Spatial) assetManager.loadAsset(new OgreMeshKey("Models/Sign Post/Sign Post.mesh.xml")); + signpost.setMaterial( (Material) assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m")); + TangentBinormalGenerator.generate(signpost); + rootNode.attachChild(signpost); + + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial( (Material) assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + rootNode.attachChild(lightMdl); + + // flourescent main light + pl = new PointLight(); + pl.setColor(new ColorRGBA(0.88f, 0.92f, 0.95f, 1.0f)); + rootNode.addLight(pl); + + // sunset light + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f,-0.7f,1).normalizeLocal()); + dl.setColor(new ColorRGBA(0.44f, 0.30f, 0.20f, 1.0f)); + rootNode.addLight(dl); + + // skylight + dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.6f,-1,-0.6f).normalizeLocal()); + dl.setColor(new ColorRGBA(0.10f, 0.22f, 0.44f, 1.0f)); + rootNode.addLight(dl); + + // white ambient light + dl = new DirectionalLight(); + dl.setDirection(new Vector3f(1, -0.5f,-0.1f).normalizeLocal()); + dl.setColor(new ColorRGBA(0.50f, 0.40f, 0.50f, 1.0f)); + rootNode.addLight(dl); + } + + @Override + public void simpleUpdate(float tpf){ + angle += tpf * 0.25f; + angle %= FastMath.TWO_PI; + + pl.setPosition(new Vector3f(FastMath.cos(angle) * 6f, 3f, FastMath.sin(angle) * 6f)); + lightMdl.setLocalTranslation(pl.getPosition()); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/material/TestColoredTexture.java b/jme3-examples/src/main/java/jme3test/material/TestColoredTexture.java new file mode 100644 index 000000000..0c22e4125 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/TestColoredTexture.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/material/TestMaterialCompare.java b/jme3-examples/src/main/java/jme3test/material/TestMaterialCompare.java new file mode 100644 index 000000000..a37855185 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/TestMaterialCompare.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.material; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.material.Material; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.math.ColorRGBA; +import com.jme3.system.JmeSystem; +import com.jme3.texture.Texture; + +public class TestMaterialCompare { + + public static void main(String[] args) { + AssetManager assetManager = JmeSystem.newAssetManager( + TestMaterialCompare.class.getResource("/com/jme3/asset/Desktop.cfg")); + + // Cloned materials + Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat1.setName("mat1"); + mat1.setColor("Color", ColorRGBA.Blue); + + Material mat2 = mat1.clone(); + mat2.setName("mat2"); + testEquality(mat1, mat2, true); + + // Cloned material with different render states + Material mat3 = mat1.clone(); + mat3.setName("mat3"); + mat3.getAdditionalRenderState().setBlendMode(BlendMode.ModulateX2); + testEquality(mat1, mat3, false); + + // Two separately loaded materials + Material mat4 = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); + mat4.setName("mat4"); + Material mat5 = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); + mat5.setName("mat5"); + testEquality(mat4, mat5, true); + + // Comparing same textures + TextureKey originalKey = (TextureKey) mat4.getTextureParam("DiffuseMap").getTextureValue().getKey(); + TextureKey tex1key = new TextureKey("Models/Sign Post/Sign Post.jpg", false); + tex1key.setGenerateMips(true); + + // Texture keys from the original and the loaded texture + // must be identical, otherwise the resultant textures not identical + // and thus materials are not identical! + if (!originalKey.equals(tex1key)){ + System.out.println("TEXTURE KEYS ARE NOT EQUAL"); + } + + Texture tex1 = assetManager.loadTexture(tex1key); + mat4.setTexture("DiffuseMap", tex1); + testEquality(mat4, mat5, true); + + // Change some stuff on the texture and compare, materials no longer equal + tex1.setWrap(Texture.WrapMode.MirroredRepeat); + testEquality(mat4, mat5, false); + + // Comparing different textures + Texture tex2 = assetManager.loadTexture("Interface/Logo/Monkey.jpg"); + mat4.setTexture("DiffuseMap", tex2); + testEquality(mat4, mat5, false); + + // Two materials created the same way + Material mat6 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat6.setName("mat6"); + mat6.setColor("Color", ColorRGBA.Blue); + testEquality(mat1, mat6, true); + + // Changing a material param + mat6.setColor("Color", ColorRGBA.Green); + testEquality(mat1, mat6, false); + } + + private static void testEquality(Material mat1, Material mat2, boolean expected) { + if (mat2.contentEquals(mat1)) { + System.out.print(mat1.getName() + " == " + mat2.getName()); + if (expected) { + System.out.println(" EQUAL OK"); + } else { + System.out.println(" EQUAL FAIL!"); + } + } else { + System.out.print(mat1.getName() + " != " + mat2.getName()); + if (!expected) { + System.out.println(" EQUAL OK"); + } else { + System.out.println(" EQUAL FAIL!"); + } + } + if (mat2.hashCode() == mat1.hashCode()){ + System.out.print(mat1.getName() + " == " + mat2.getName()); + if (expected) { + System.out.println(" HASH OK"); + } else { + System.out.println(" HASH FAIL!"); + } + } else { + System.out.print(mat1.getName() + " != " + mat2.getName()); + if (!expected) { + System.out.println(" HASH OK"); + } else { + System.out.println(" HASH FAIL!"); + } + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/material/TestNormalMapping.java b/jme3-examples/src/main/java/jme3test/material/TestNormalMapping.java new file mode 100644 index 000000000..c955b3467 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/TestNormalMapping.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/material/TestParallax.java b/jme3-examples/src/main/java/jme3test/material/TestParallax.java new file mode 100644 index 000000000..37b5e09b0 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/TestParallax.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.material; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.FXAAFilter; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; +import com.jme3.util.TangentBinormalGenerator; + +public class TestParallax extends SimpleApplication { + + private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); + + public static void main(String[] args) { + TestParallax app = new TestParallax(); + app.start(); + } + + public void setupSkyBox() { + rootNode.attachChild(SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false)); + } + DirectionalLight dl; + + public void setupLighting() { + + dl = new DirectionalLight(); + dl.setDirection(lightDir); + dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1)); + rootNode.addLight(dl); + } + Material mat; + + public void setupFloor() { + mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall2.j3m"); + mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat); + mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat); + + // Node floorGeom = (Node) assetManager.loadAsset("Models/WaterTest/WaterTest.mesh.xml"); + //Geometry g = ((Geometry) floorGeom.getChild(0)); + //g.getMesh().scaleTextureCoordinates(new Vector2f(10, 10)); + + Node floorGeom = new Node("floorGeom"); + Quad q = new Quad(100, 100); + q.scaleTextureCoordinates(new Vector2f(10, 10)); + Geometry g = new Geometry("geom", q); + g.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); + floorGeom.attachChild(g); + + + TangentBinormalGenerator.generate(floorGeom); + floorGeom.setLocalTranslation(-50, 22, 60); + //floorGeom.setLocalScale(100); + + floorGeom.setMaterial(mat); + rootNode.attachChild(floorGeom); + } + + public void setupSignpost() { + Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml"); + Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); + TangentBinormalGenerator.generate(signpost); + signpost.setMaterial(mat); + signpost.rotate(0, FastMath.HALF_PI, 0); + signpost.setLocalTranslation(12, 23.5f, 30); + signpost.setLocalScale(4); + signpost.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(signpost); + } + + @Override + public void simpleInitApp() { + cam.setLocation(new Vector3f(-15.445636f, 30.162927f, 60.252777f)); + cam.setRotation(new Quaternion(0.05173137f, 0.92363626f, -0.13454558f, 0.35513034f)); + flyCam.setMoveSpeed(30); + + + setupLighting(); + setupSkyBox(); + setupFloor(); + setupSignpost(); + + inputManager.addListener(new AnalogListener() { + + public void onAnalog(String name, float value, float tpf) { + if ("heightUP".equals(name)) { + parallaxHeigh += 0.0001; + mat.setFloat("ParallaxHeight", parallaxHeigh); + } + if ("heightDown".equals(name)) { + parallaxHeigh -= 0.0001; + parallaxHeigh = Math.max(parallaxHeigh, 0); + mat.setFloat("ParallaxHeight", parallaxHeigh); + } + + } + }, "heightUP", "heightDown"); + inputManager.addMapping("heightUP", new KeyTrigger(KeyInput.KEY_I)); + inputManager.addMapping("heightDown", new KeyTrigger(KeyInput.KEY_K)); + + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed && "toggleSteep".equals(name)) { + steep = !steep; + mat.setBoolean("SteepParallax", steep); + } + } + }, "toggleSteep"); + inputManager.addMapping("toggleSteep", new KeyTrigger(KeyInput.KEY_SPACE)); + } + float parallaxHeigh = 0.05f; + float time = 0; + boolean steep = false; + + @Override + public void simpleUpdate(float tpf) { +// time+=tpf; +// lightDir.set(FastMath.sin(time), -1, FastMath.cos(time)); +// bsr.setDirection(lightDir); +// dl.setDirection(lightDir); + } +} diff --git a/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java b/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java new file mode 100644 index 000000000..db8a21c97 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java @@ -0,0 +1,43 @@ +package jme3test.material; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.material.Technique; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.shader.Shader; +import com.jme3.texture.Texture; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TestShaderNodes extends SimpleApplication { + + public static void main(String[] args) { + TestShaderNodes app = new TestShaderNodes(); + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(20); + Logger.getLogger("com.jme3").setLevel(Level.WARNING); + Box boxshape1 = new Box(1f, 1f, 1f); + Geometry cube_tex = new Geometry("A Textured Box", boxshape1); + Texture tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg"); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/UnshadedNodes.j3md"); + mat.selectTechnique("Default", renderManager); + Technique t = mat.getActiveTechnique(); + + for (Shader.ShaderSource shaderSource : t.getShader().getSources()) { + System.out.println(shaderSource.getSource()); + } + + + mat.setColor("Color", ColorRGBA.Yellow); + mat.setTexture("ColorMap", tex); + cube_tex.setMaterial(mat); + rootNode.attachChild(cube_tex); + } +} diff --git a/jme3-examples/src/main/java/jme3test/material/TestSimpleBumps.java b/jme3-examples/src/main/java/jme3test/material/TestSimpleBumps.java new file mode 100644 index 000000000..5be1adf68 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/TestSimpleBumps.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.material; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.TangentBinormalGenerator; + +// phong cutoff for light to normal angle > 90? +public class TestSimpleBumps extends SimpleApplication { + + float angle; + PointLight pl; + Spatial lightMdl; + + public static void main(String[] args){ + TestSimpleBumps app = new TestSimpleBumps(); + app.start(); + } + + @Override + public void simpleInitApp() { + Quad quadMesh = new Quad(1, 1); + + Geometry sphere = new Geometry("Rock Ball", quadMesh); + Material mat = assetManager.loadMaterial("Textures/BumpMapTest/SimpleBump.j3m"); + sphere.setMaterial(mat); + TangentBinormalGenerator.generate(sphere); + rootNode.attachChild(sphere); + + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + rootNode.attachChild(lightMdl); + + pl = new PointLight(); + pl.setColor(ColorRGBA.White); + pl.setPosition(new Vector3f(0f, 0f, 4f)); + rootNode.addLight(pl); + +// DirectionalLight dl = new DirectionalLight(); +// dl.setDirection(new Vector3f(1, -1, -1).normalizeLocal()); +// dl.setColor(new ColorRGBA(0.22f, 0.15f, 0.1f, 1.0f)); +// rootNode.addLight(dl); + } + + @Override + public void simpleUpdate(float tpf){ + angle += tpf * 0.25f; + angle %= FastMath.TWO_PI; + + pl.setPosition(new Vector3f(FastMath.cos(angle) * 4f, 0.5f, FastMath.sin(angle) * 4f)); + lightMdl.setLocalTranslation(pl.getPosition()); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/material/TestUnshadedModel.java b/jme3-examples/src/main/java/jme3test/material/TestUnshadedModel.java new file mode 100644 index 000000000..826ef448d --- /dev/null +++ b/jme3-examples/src/main/java/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/jme3-examples/src/main/java/jme3test/math/TestHalfFloat.java b/jme3-examples/src/main/java/jme3test/math/TestHalfFloat.java new file mode 100644 index 000000000..1bd9361bb --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/math/TestHalfFloat.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/model/TestHoverTank.java b/jme3-examples/src/main/java/jme3test/model/TestHoverTank.java new file mode 100644 index 000000000..648f7390d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/TestHoverTank.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.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/jme3-examples/src/main/java/jme3test/model/TestMonkeyHead.java b/jme3-examples/src/main/java/jme3test/model/TestMonkeyHead.java new file mode 100644 index 000000000..c75e221f0 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/TestMonkeyHead.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.model; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; + +public class TestMonkeyHead extends SimpleApplication { + + float angle; + PointLight pl; + Spatial lightMdl; + + public static void main(String[] args){ + TestMonkeyHead app = new TestMonkeyHead(); + app.start(); + } + + @Override + public void simpleInitApp() { + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + + Spatial bumpy = (Spatial) assetManager.loadModel("Models/MonkeyHead/MonkeyHead.mesh.xml"); + rootNode.attachChild(bumpy); + + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + rootNode.attachChild(lightMdl); + + // flourescent main light + pl = new PointLight(); + pl.setColor(new ColorRGBA(0.88f, 0.92f, 0.95f, 1.0f)); + rootNode.addLight(pl); + + // sunset light + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f,-0.7f,1).normalizeLocal()); + dl.setColor(new ColorRGBA(0.44f, 0.30f, 0.20f, 1.0f)); + rootNode.addLight(dl); + + // skylight + dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.6f,-1,-0.6f).normalizeLocal()); + dl.setColor(new ColorRGBA(0.10f, 0.22f, 0.44f, 1.0f)); + rootNode.addLight(dl); + + // white ambient light + dl = new DirectionalLight(); + dl.setDirection(new Vector3f(1, -0.5f,-0.1f).normalizeLocal()); + dl.setColor(new ColorRGBA(0.50f, 0.40f, 0.50f, 1.0f)); + rootNode.addLight(dl); + } + + @Override + public void simpleUpdate(float tpf){ + angle += tpf * 0.25f; + angle %= FastMath.TWO_PI; + + pl.setPosition(new Vector3f(FastMath.cos(angle) * 6f, 3f, FastMath.sin(angle) * 6f)); + lightMdl.setLocalTranslation(pl.getPosition()); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/model/TestObjLoading.java b/jme3-examples/src/main/java/jme3test/model/TestObjLoading.java new file mode 100644 index 000000000..2546fdd79 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/TestObjLoading.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/model/TestOgreLoading.java b/jme3-examples/src/main/java/jme3test/model/TestOgreLoading.java new file mode 100644 index 000000000..f8d1e85f1 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/TestOgreLoading.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.model; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; + +public class TestOgreLoading extends SimpleApplication { + + float angle1; + float angle2; + PointLight pl; + PointLight p2; + Spatial lightMdl; + Spatial lightMd2; + + public static void main(String[] args) { + TestOgreLoading app = new TestOgreLoading(); + app.start(); + } + + public void simpleInitApp() { +// PointLight pl = new PointLight(); +// pl.setPosition(new Vector3f(10, 10, -10)); +// rootNode.addLight(pl); + flyCam.setMoveSpeed(10f); + + // sunset light + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f, -0.7f, 1).normalizeLocal()); + dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); + rootNode.addLight(dl); + + + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + rootNode.attachChild(lightMdl); + + lightMd2 = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMd2.setMaterial(assetManager.loadMaterial("Common/Materials/WhiteColor.j3m")); + rootNode.attachChild(lightMd2); + + + pl = new PointLight(); + pl.setColor(new ColorRGBA(1, 0.9f, 0.9f, 0)); + pl.setPosition(new Vector3f(0f, 0f, 4f)); + rootNode.addLight(pl); + + p2 = new PointLight(); + p2.setColor(new ColorRGBA(0.9f, 1, 0.9f, 0)); + p2.setPosition(new Vector3f(0f, 0f, 3f)); + rootNode.addLight(p2); + + + // create the geometry and attach it + Spatial elephant = (Spatial) assetManager.loadModel("Models/Elephant/Elephant.mesh.xml"); + float scale = 0.05f; + elephant.scale(scale, scale, scale); + rootNode.attachChild(elephant); + } + + @Override + public void simpleUpdate(float tpf) { + angle1 += tpf * 0.25f; + angle1 %= FastMath.TWO_PI; + + angle2 += tpf * 0.50f; + angle2 %= FastMath.TWO_PI; + + pl.setPosition(new Vector3f(FastMath.cos(angle1) * 4f, 0.5f, FastMath.sin(angle1) * 4f)); + p2.setPosition(new Vector3f(FastMath.cos(angle2) * 4f, 0.5f, FastMath.sin(angle2) * 4f)); + lightMdl.setLocalTranslation(pl.getPosition()); + lightMd2.setLocalTranslation(p2.getPosition()); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimBlendBug.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimBlendBug.java new file mode 100644 index 000000000..54948ef02 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimBlendBug.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/model/anim/TestAnimationFactory.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimationFactory.java new file mode 100644 index 000000000..55768517c --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimationFactory.java @@ -0,0 +1,85 @@ +package jme3test.model.anim; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.AnimationFactory; +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.util.TangentBinormalGenerator; + +public class TestAnimationFactory extends SimpleApplication { + + public static void main(String[] args) { + TestSpatialAnim app = new TestSpatialAnim(); + app.start(); + } + + @Override + public void simpleInitApp() { + + AmbientLight al = new AmbientLight(); + rootNode.addLight(al); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(Vector3f.UNIT_XYZ.negate()); + rootNode.addLight(dl); + + // Create model + Box box = new Box(1, 1, 1); + Geometry geom = new Geometry("box", box); + geom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m")); + Node model = new Node("model"); + model.attachChild(geom); + + Box child = new Box(0.5f, 0.5f, 0.5f); + Geometry childGeom = new Geometry("box", child); + childGeom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m")); + Node childModel = new Node("childmodel"); + childModel.setLocalTranslation(2, 2, 2); + childModel.attachChild(childGeom); + model.attachChild(childModel); + TangentBinormalGenerator.generate(model); + + //creating quite complex animation witht the AnimationHelper + // animation of 6 seconds named "anim" and with 25 frames per second + AnimationFactory animationFactory = new AnimationFactory(6, "anim", 25); + + //creating a translation keyFrame at time = 3 with a translation on the x axis of 5 WU + animationFactory.addTimeTranslation(3, new Vector3f(5, 0, 0)); + //reseting the translation to the start position at time = 6 + animationFactory.addTimeTranslation(6, new Vector3f(0, 0, 0)); + + //Creating a scale keyFrame at time = 2 with the unit scale. + animationFactory.addTimeScale(2, new Vector3f(1, 1, 1)); + //Creating a scale keyFrame at time = 4 scaling to 1.5 + animationFactory.addTimeScale(4, new Vector3f(1.5f, 1.5f, 1.5f)); + //reseting the scale to the start value at time = 5 + animationFactory.addTimeScale(5, new Vector3f(1, 1, 1)); + + + //Creating a rotation keyFrame at time = 0.5 of quarter PI around the Z axis + animationFactory.addTimeRotation(0.5f,new Quaternion().fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Z)); + //rotating back to initial rotation value at time = 1 + animationFactory.addTimeRotation(1,Quaternion.IDENTITY); + //Creating a rotation keyFrame at time = 2. Note that i used the Euler angle version because the angle is higher than PI + //this should result in a complete revolution of the spatial around the x axis in 1 second (from 1 to 2) + animationFactory.addTimeRotationAngles(2, FastMath.TWO_PI,0, 0); + + + AnimControl control = new AnimControl(); + control.addAnim(animationFactory.buildAnimation()); + + model.addControl(control); + + rootNode.attachChild(model); + + //run animation + control.createChannel().setAnim("anim"); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestBlenderAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestBlenderAnim.java new file mode 100644 index 000000000..0fea65ae9 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestBlenderAnim.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.model.anim; + +import com.jme3.animation.AnimChannel; +import com.jme3.animation.AnimControl; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.BlenderKey; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; + +public class TestBlenderAnim extends SimpleApplication { + + private AnimChannel channel; + private AnimControl control; + + public static void main(String[] args) { + TestBlenderAnim app = new TestBlenderAnim(); + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(10f); + cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f)); + cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f)); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal()); + dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); + rootNode.addLight(dl); + + BlenderKey blenderKey = new BlenderKey("Blender/2.4x/BaseMesh_249.blend"); + + Spatial scene = (Spatial) assetManager.loadModel(blenderKey); + rootNode.attachChild(scene); + + Spatial model = this.findNode(rootNode, "BaseMesh_01"); + model.center(); + + control = model.getControl(AnimControl.class); + channel = control.createChannel(); + + channel.setAnim("run_01"); + } + + /** + * This method finds a node of a given name. + * @param rootNode the root node to search + * @param name the name of the searched node + * @return the found node or null + */ + private Spatial findNode(Node rootNode, String name) { + if (name.equals(rootNode.getName())) { + return rootNode; + } + return rootNode.getChild(name); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestBlenderObjectAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestBlenderObjectAnim.java new file mode 100644 index 000000000..f7171f2bd --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestBlenderObjectAnim.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.model.anim; + +import com.jme3.animation.AnimChannel; +import com.jme3.animation.AnimControl; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.BlenderKey; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; + +public class TestBlenderObjectAnim extends SimpleApplication { + + private AnimChannel channel; + private AnimControl control; + + public static void main(String[] args) { + TestBlenderObjectAnim app = new TestBlenderObjectAnim(); + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(10f); + cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f)); + cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f)); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal()); + dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); + rootNode.addLight(dl); + + BlenderKey blenderKey = new BlenderKey("Blender/2.4x/animtest.blend"); + + Spatial scene = (Spatial) assetManager.loadModel(blenderKey); + rootNode.attachChild(scene); + + Spatial model = this.findNode(rootNode, "TestAnim"); + model.center(); + + control = model.getControl(AnimControl.class); + channel = control.createChannel(); + + channel.setAnim("TestAnim"); + } + + /** + * This method finds a node of a given name. + * @param rootNode the root node to search + * @param name the name of the searched node + * @return the found node or null + */ + private Spatial findNode(Node rootNode, String name) { + if (name.equals(rootNode.getName())) { + return rootNode; + } + return rootNode.getChild(name); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestCustomAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestCustomAnim.java new file mode 100644 index 000000000..e220eeb37 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestCustomAnim.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.model.anim; + +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.animation.SkeletonControl; +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.shape.Box; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +public class TestCustomAnim extends SimpleApplication { + + private Bone bone; + private Skeleton skeleton; + private Quaternion rotation = new Quaternion(); + + public static void main(String[] args) { + TestCustomAnim app = new TestCustomAnim(); + app.start(); + } + + @Override + public void simpleInitApp() { + + AmbientLight al = new AmbientLight(); + rootNode.addLight(al); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(Vector3f.UNIT_XYZ.negate()); + rootNode.addLight(dl); + + Box box = new Box(1, 1, 1); + + // Setup bone weight buffer + FloatBuffer weights = FloatBuffer.allocate( box.getVertexCount() * 4 ); + VertexBuffer weightsBuf = new VertexBuffer(Type.BoneWeight); + weightsBuf.setupData(Usage.CpuOnly, 4, Format.Float, weights); + box.setBuffer(weightsBuf); + + // Setup bone index buffer + ByteBuffer indices = ByteBuffer.allocate( box.getVertexCount() * 4 ); + VertexBuffer indicesBuf = new VertexBuffer(Type.BoneIndex); + indicesBuf.setupData(Usage.CpuOnly, 4, Format.UnsignedByte, indices); + box.setBuffer(indicesBuf); + + // Create bind pose buffers + box.generateBindPose(true); + + // Create skeleton + bone = new Bone("root"); + bone.setBindTransforms(Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ); + bone.setUserControl(true); + skeleton = new Skeleton(new Bone[]{ bone }); + + // Assign all verticies to bone 0 with weight 1 + for (int i = 0; i < box.getVertexCount() * 4; i += 4){ + // assign vertex to bone index 0 + indices.array()[i+0] = 0; + indices.array()[i+1] = 0; + indices.array()[i+2] = 0; + indices.array()[i+3] = 0; + + // set weight to 1 only for first entry + weights.array()[i+0] = 1; + weights.array()[i+1] = 0; + weights.array()[i+2] = 0; + weights.array()[i+3] = 0; + } + + // Maximum number of weights per bone is 1 + box.setMaxNumWeights(1); + + // Create model + Geometry geom = new Geometry("box", box); + geom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m")); + Node model = new Node("model"); + model.attachChild(geom); + + // Create skeleton control + SkeletonControl skeletonControl = new SkeletonControl(skeleton); + model.addControl(skeletonControl); + + rootNode.attachChild(model); + } + + @Override + public void simpleUpdate(float tpf){ + // Rotate around X axis + Quaternion rotate = new Quaternion(); + rotate.fromAngleAxis(tpf, Vector3f.UNIT_X); + + // Combine rotation with previous + rotation.multLocal(rotate); + + // Set new rotation into bone + bone.setUserTransforms(Vector3f.ZERO, rotation, Vector3f.UNIT_XYZ); + + // After changing skeleton transforms, must update world data + skeleton.updateWorldVectors(); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java b/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java new file mode 100644 index 000000000..9a2e78804 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.model.anim; + +import com.jme3.animation.*; +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.util.ArrayList; +import java.util.List; + +public class TestHWSkinning extends SimpleApplication implements ActionListener{ + + private AnimChannel channel; + private AnimControl control; + private String[] animNames = {"Dodge", "Walk", "pull", "push"}; + private final static int SIZE = 10; + private boolean hwSkinningEnable = true; + private List skControls = new ArrayList(); + private BitmapText hwsText; + + public static void main(String[] args) { + TestHWSkinning app = new TestHWSkinning(); + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(10f); + cam.setLocation(new Vector3f(3.8664846f, 6.2704787f, 9.664585f)); + cam.setRotation(new Quaternion(-0.054774776f, 0.94064945f, -0.27974048f, -0.18418397f)); + makeHudText(); + + 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); + + for (int i = 0; i < SIZE; i++) { + for (int j = 0; j < SIZE; j++) { + Spatial model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + model.setLocalScale(0.1f); + model.setLocalTranslation(i - SIZE / 2, 0, j - SIZE / 2); + control = model.getControl(AnimControl.class); + + channel = control.createChannel(); + channel.setAnim(animNames[(i + j) % 4]); + SkeletonControl skeletonControl = model.getControl(SkeletonControl.class); + skeletonControl.setHardwareSkinningPreferred(hwSkinningEnable); + skControls.add(skeletonControl); + rootNode.attachChild(model); + } + } + + inputManager.addListener(this, "toggleHWS"); + inputManager.addMapping("toggleHWS", new KeyTrigger(KeyInput.KEY_SPACE)); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if(isPressed && name.equals("toggleHWS")){ + hwSkinningEnable = !hwSkinningEnable; + for (SkeletonControl control : skControls) { + control.setHardwareSkinningPreferred(hwSkinningEnable); + hwsText.setText("HWS : "+ hwSkinningEnable); + } + } + } + + private void makeHudText() { + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + hwsText = new BitmapText(guiFont, false); + hwsText.setSize(guiFont.getCharSet().getRenderedSize()); + hwsText.setText("HWS : "+ hwSkinningEnable); + hwsText.setLocalTranslation(0, cam.getHeight(), 0); + guiNode.attachChild(hwsText); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestOgreAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestOgreAnim.java new file mode 100644 index 000000000..b6bd4cd63 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestOgreAnim.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.model.anim; + +import com.jme3.animation.*; +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; + +public class TestOgreAnim extends SimpleApplication + implements AnimEventListener, ActionListener { + + private AnimChannel channel; + private AnimControl control; + private Geometry geom; + + 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"); + geom = (Geometry)((Node)model).getChild(0); + SkeletonControl skeletonControl = model.getControl(SkeletonControl.class); + + Box b = new Box(.25f,3f,.25f); + Geometry item = new Geometry("Item", b); + item.move(0, 1.5f, 0); + item.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + Node n = skeletonControl.getAttachmentsNode("hand.right"); + n.attachChild(item); + + rootNode.attachChild(model); + + inputManager.addListener(this, "Attack"); + inputManager.addMapping("Attack", new KeyTrigger(KeyInput.KEY_SPACE)); + } + + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); +// geom.getMesh().createCollisionData(); + + } + + + 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/jme3-examples/src/main/java/jme3test/model/anim/TestOgreComplexAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestOgreComplexAnim.java new file mode 100644 index 000000000..6717f5a20 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestOgreComplexAnim.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.model.anim; + +import com.jme3.animation.AnimChannel; +import com.jme3.animation.AnimControl; +import com.jme3.animation.Bone; +import com.jme3.animation.LoopMode; +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.debug.SkeletonDebugger; + +public class TestOgreComplexAnim extends SimpleApplication { + + private AnimControl control; + private float angle = 0; + private float scale = 1; + private float rate = 1; + + public static void main(String[] args) { + TestOgreComplexAnim app = new TestOgreComplexAnim(); + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(10f); + cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f)); + cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f)); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal()); + dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); + rootNode.addLight(dl); + + Node model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + + control = model.getControl(AnimControl.class); + + AnimChannel feet = control.createChannel(); + AnimChannel leftHand = control.createChannel(); + AnimChannel rightHand = control.createChannel(); + + // feet will dodge + feet.addFromRootBone("hip.right"); + feet.addFromRootBone("hip.left"); + feet.setAnim("Dodge"); + feet.setSpeed(2); + feet.setLoopMode(LoopMode.Cycle); + + // will blend over 15 seconds to stand + feet.setAnim("Walk", 15); + feet.setSpeed(0.25f); + feet.setLoopMode(LoopMode.Cycle); + + // left hand will pull + leftHand.addFromRootBone("uparm.right"); + leftHand.setAnim("pull"); + leftHand.setSpeed(.5f); + + // will blend over 15 seconds to stand + leftHand.setAnim("stand", 15); + + // right hand will push + rightHand.addBone("spinehigh"); + rightHand.addFromRootBone("uparm.left"); + rightHand.setAnim("push"); + + SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton", control.getSkeleton()); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + mat.setColor("Color", ColorRGBA.Green); + mat.getAdditionalRenderState().setDepthTest(false); + skeletonDebug.setMaterial(mat); + + model.attachChild(skeletonDebug); + rootNode.attachChild(model); + } + + @Override + public void simpleUpdate(float tpf){ + Bone b = control.getSkeleton().getBone("spinehigh"); + Bone b2 = control.getSkeleton().getBone("uparm.left"); + + angle += tpf * rate; + if (angle > FastMath.HALF_PI / 2f){ + angle = FastMath.HALF_PI / 2f; + rate = -1; + }else if (angle < -FastMath.HALF_PI / 2f){ + angle = -FastMath.HALF_PI / 2f; + rate = 1; + } + + Quaternion q = new Quaternion(); + q.fromAngles(0, angle, 0); + + b.setUserControl(true); + b.setUserTransforms(Vector3f.ZERO, q, Vector3f.UNIT_XYZ); + + b2.setUserControl(true); + b2.setUserTransforms(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(1+angle,1+ angle, 1+angle)); + + + } + +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestSkeletonControlRefresh.java b/jme3-examples/src/main/java/jme3test/model/anim/TestSkeletonControlRefresh.java new file mode 100644 index 000000000..d594f197f --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestSkeletonControlRefresh.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.model.anim; + +/** + * + * @author Nehon + */ + + + +import com.jme3.animation.*; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +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.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; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.shadow.DirectionalLightShadowFilter; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import java.util.ArrayList; +import java.util.List; +import jme3test.post.SSAOUI; + +public class TestSkeletonControlRefresh extends SimpleApplication implements ActionListener{ + + private AnimChannel channel; + private AnimControl control; + private String[] animNames = {"Dodge", "Walk", "pull", "push"}; + private final static int SIZE = 10; + private boolean hwSkinningEnable = true; + private List skControls = new ArrayList(); + private BitmapText hwsText; + + public static void main(String[] args) { + TestSkeletonControlRefresh app = new TestSkeletonControlRefresh(); + app.start(); + } + + @Override + public void simpleInitApp() { + viewPort.setBackgroundColor(ColorRGBA.White); + flyCam.setMoveSpeed(10f); + cam.setLocation(new Vector3f(3.8664846f, 6.2704787f, 9.664585f)); + cam.setRotation(new Quaternion(-0.054774776f, 0.94064945f, -0.27974048f, -0.18418397f)); + makeHudText(); + + 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); + Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey k = new TextureKey("Models/Oto/Oto.jpg", false); + m.setTexture("ColorMap", assetManager.loadTexture(k)); + + for (int i = 0; i < SIZE; i++) { + for (int j = 0; j < SIZE; j++) { + Spatial model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + //setting a different material + model.setMaterial(m.clone()); + model.setLocalScale(0.1f); + model.setLocalTranslation(i - SIZE / 2, 0, j - SIZE / 2); + control = model.getControl(AnimControl.class); + + channel = control.createChannel(); + channel.setAnim(animNames[(i + j) % 4]); + channel.setLoopMode(LoopMode.DontLoop); + SkeletonControl skeletonControl = model.getControl(SkeletonControl.class); + + //This is a workaround the issue. this call will make the SkeletonControl gather the targets again. + //skeletonControl.setSpatial(model); + skeletonControl.setHardwareSkinningPreferred(hwSkinningEnable); + skControls.add(skeletonControl); + rootNode.attachChild(model); + } + } + + rootNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + setupFloor(); + + inputManager.addListener(this, "toggleHWS"); + inputManager.addMapping("toggleHWS", new KeyTrigger(KeyInput.KEY_SPACE)); + +// DirectionalLightShadowRenderer pssm = new DirectionalLightShadowRenderer(assetManager, 1024, 2); +// pssm.setLight(dl); +// viewPort.addProcessor(pssm); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + + DirectionalLightShadowFilter sf = new DirectionalLightShadowFilter(assetManager, 1024, 2); + sf.setLight(dl); + fpp.addFilter(sf); + fpp.addFilter(new SSAOFilter()); + viewPort.addProcessor(fpp); + + + } + + public void setupFloor() { + Quad q = new Quad(20, 20); + q.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(10)); + Geometry geom = new Geometry("floor", q); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.White); + geom.setMaterial(mat); + + geom.rotate(-FastMath.HALF_PI, 0, 0); + geom.center(); + geom.move(0, -0.3f, 0); + geom.setShadowMode(RenderQueue.ShadowMode.Receive); + rootNode.attachChild(geom); + } + + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if(isPressed && name.equals("toggleHWS")){ + hwSkinningEnable = !hwSkinningEnable; + for (SkeletonControl skControl : skControls) { + skControl.setHardwareSkinningPreferred(hwSkinningEnable); + hwsText.setText("HWS : "+ hwSkinningEnable); + } + } + } + + private void makeHudText() { + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + hwsText = new BitmapText(guiFont, false); + hwsText.setSize(guiFont.getCharSet().getRenderedSize()); + hwsText.setText("HWS : "+ hwSkinningEnable); + hwsText.setLocalTranslation(0, cam.getHeight(), 0); + guiNode.attachChild(hwsText); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestSpatialAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestSpatialAnim.java new file mode 100644 index 000000000..94dfd03a4 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestSpatialAnim.java @@ -0,0 +1,87 @@ +package jme3test.model.anim; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.Animation; +import com.jme3.animation.SpatialTrack; +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import java.util.HashMap; + +public class TestSpatialAnim extends SimpleApplication { + + public static void main(String[] args) { + TestSpatialAnim app = new TestSpatialAnim(); + app.start(); + } + + @Override + public void simpleInitApp() { + + AmbientLight al = new AmbientLight(); + rootNode.addLight(al); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(Vector3f.UNIT_XYZ.negate()); + rootNode.addLight(dl); + + // Create model + Box box = new Box(1, 1, 1); + Geometry geom = new Geometry("box", box); + geom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m")); + Node model = new Node("model"); + model.attachChild(geom); + + Box child = new Box(0.5f, 0.5f, 0.5f); + Geometry childGeom = new Geometry("box", child); + childGeom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m")); + Node childModel = new Node("childmodel"); + childModel.setLocalTranslation(2, 2, 2); + childModel.attachChild(childGeom); + model.attachChild(childModel); + + //animation parameters + float animTime = 5; + int fps = 25; + float totalXLength = 10; + + //calculating frames + int totalFrames = (int) (fps * animTime); + float dT = animTime / totalFrames, t = 0; + float dX = totalXLength / totalFrames, x = 0; + float[] times = new float[totalFrames]; + Vector3f[] translations = new Vector3f[totalFrames]; + Quaternion[] rotations = new Quaternion[totalFrames]; + Vector3f[] scales = new Vector3f[totalFrames]; + for (int i = 0; i < totalFrames; ++i) { + times[i] = t; + t += dT; + translations[i] = new Vector3f(x, 0, 0); + x += dX; + rotations[i] = Quaternion.IDENTITY; + scales[i] = Vector3f.UNIT_XYZ; + } + SpatialTrack spatialTrack = new SpatialTrack(times, translations, rotations, scales); + + //creating the animation + Animation spatialAnimation = new Animation("anim", animTime); + spatialAnimation.setTracks(new SpatialTrack[] { spatialTrack }); + + //create spatial animation control + AnimControl control = new AnimControl(); + HashMap animations = new HashMap(); + animations.put("anim", spatialAnimation); + control.setAnimations(animations); + model.addControl(control); + + rootNode.attachChild(model); + + //run animation + control.createChannel().setAnim("anim"); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestBillboard.java b/jme3-examples/src/main/java/jme3test/model/shape/TestBillboard.java new file mode 100644 index 000000000..ba7acb1f7 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestBillboard.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.model.shape; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.control.BillboardControl; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Quad; + +/** + * + * @author Kirill Vainer + */ +public class TestBillboard extends SimpleApplication { + + public void simpleInitApp() { + flyCam.setMoveSpeed(10); + + Quad q = new Quad(2, 2); + Geometry g = new Geometry("Quad", q); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Blue); + g.setMaterial(mat); + + Quad q2 = new Quad(1, 1); + Geometry g3 = new Geometry("Quad2", q2); + Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat2.setColor("Color", ColorRGBA.Yellow); + g3.setMaterial(mat2); + g3.setLocalTranslation(.5f, .5f, .01f); + + Box b = new Box(new Vector3f(0, 0, 3), .25f, .5f, .25f); + Geometry g2 = new Geometry("Box", b); + g2.setMaterial(mat); + + Node bb = new Node("billboard"); + + BillboardControl control=new BillboardControl(); + + bb.addControl(control); + bb.attachChild(g); + bb.attachChild(g3); + + + n=new Node("parent"); + n.attachChild(g2); + n.attachChild(bb); + rootNode.attachChild(n); + + n2=new Node("parentParent"); + n2.setLocalTranslation(Vector3f.UNIT_X.mult(5)); + n2.attachChild(n); + + rootNode.attachChild(n2); + + +// rootNode.attachChild(bb); +// rootNode.attachChild(g2); + } + Node n; + Node n2; + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + n.rotate(0, tpf, 0); + n.move(0.1f*tpf, 0, 0); + n2.rotate(0, 0, -tpf); + } + + + + public static void main(String[] args) { + TestBillboard app = new TestBillboard(); + app.start(); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestBox.java b/jme3-examples/src/main/java/jme3test/model/shape/TestBox.java new file mode 100644 index 000000000..19d26b5ac --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestBox.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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(1, 1, 1); + Geometry geom = new Geometry("Box", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + geom.setMaterial(mat); + rootNode.attachChild(geom); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestCustomMesh.java b/jme3-examples/src/main/java/jme3test/model/shape/TestCustomMesh.java new file mode 100644 index 000000000..e11b5264d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestCustomMesh.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.model.shape; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; + +/** + * How to create custom meshes by specifying vertices + * We render the mesh in three different ways, once with a solid blue color, + * once with vertex colors, and once with a wireframe material. + * @author KayTrance + */ +public class TestCustomMesh extends SimpleApplication { + + public static void main(String[] args){ + TestCustomMesh app = new TestCustomMesh(); + app.start(); + } + + @Override + public void simpleInitApp() { + + Mesh m = new Mesh(); + + // Vertex positions in space + Vector3f [] vertices = new Vector3f[4]; + vertices[0] = new Vector3f(0,0,0); + vertices[1] = new Vector3f(3,0,0); + vertices[2] = new Vector3f(0,3,0); + vertices[3] = new Vector3f(3,3,0); + + // Texture coordinates + Vector2f [] texCoord = new Vector2f[4]; + texCoord[0] = new Vector2f(0,0); + texCoord[1] = new Vector2f(1,0); + texCoord[2] = new Vector2f(0,1); + texCoord[3] = new Vector2f(1,1); + + // Indexes. We define the order in which mesh should be constructed + short[] 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.createShortBuffer(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/jme3-examples/src/main/java/jme3test/model/shape/TestCylinder.java b/jme3-examples/src/main/java/jme3test/model/shape/TestCylinder.java new file mode 100644 index 000000000..bec3847fa --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestCylinder.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java b/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java new file mode 100644 index 000000000..6c4c45e2f --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/model/shape/TestExpandingTorus.java b/jme3-examples/src/main/java/jme3test/model/shape/TestExpandingTorus.java new file mode 100644 index 000000000..9512946d5 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestExpandingTorus.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.model.shape; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Torus; + +public class TestExpandingTorus extends SimpleApplication { + + private float outerRadius = 1.5f; + private float rate = 1; + private Torus torus; + private Geometry geom; + + public static void main(String[] args) { + TestExpandingTorus app = new TestExpandingTorus(); + app.start(); + } + + @Override + public void simpleInitApp() { + torus = new Torus(30, 10, .5f, 1f); + geom = new Geometry("Torus", torus); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + geom.setMaterial(mat); + rootNode.attachChild(geom); + } + + @Override + public void simpleUpdate(float tpf){ + if (outerRadius > 2.5f){ + outerRadius = 2.5f; + rate = -rate; + }else if (outerRadius < 1f){ + outerRadius = 1f; + rate = -rate; + } + outerRadius += rate * tpf; + torus.updateGeometry(30, 10, .5f, outerRadius); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestSphere.java b/jme3-examples/src/main/java/jme3test/model/shape/TestSphere.java new file mode 100644 index 000000000..0bdf0e7ac --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestSphere.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.model.shape; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Sphere; + +public class TestSphere extends SimpleApplication { + + public static void main(String[] args){ + TestSphere app = new TestSphere(); + app.start(); + } + + @Override + public void simpleInitApp() { + Sphere sphMesh = new Sphere(14, 14, 1); + Material solidColor = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); + + for (int y = -5; y < 5; y++){ + for (int x = -5; x < 5; x++){ + Geometry sphere = new Geometry("sphere", sphMesh); + sphere.setMaterial(solidColor); + sphere.setLocalTranslation(x * 2, 0, y * 2); + rootNode.attachChild(sphere); + } + } + cam.setLocation(new Vector3f(0, 5, 0)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/network/MovingAverage.java b/jme3-examples/src/main/java/jme3test/network/MovingAverage.java new file mode 100644 index 000000000..ef7a5e7cb --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/network/MovingAverage.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.network; + +@Deprecated +public class MovingAverage { + + private long[] samples; + private long sum; + private int count, index; + + public MovingAverage(int numSamples){ + samples = new long[numSamples]; + } + + public void add(long sample){ + sum = sum - samples[index] + sample; + samples[index++] = sample; + if (index > count){ + count = index; + } + if (index >= samples.length){ + index = 0; + } + } + + public long getAverage(){ + if (count == 0) + return 0; + else + return (long) ((float) sum / (float) count); + } + +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java new file mode 100644 index 000000000..fddb047c4 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2011 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.network; + +import com.jme3.network.Client; +import com.jme3.network.Message; +import com.jme3.network.MessageListener; +import com.jme3.network.Network; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.io.IOException; +import javax.swing.*; +import jme3test.network.TestChatServer.ChatMessage; + +/** + * A simple test chat server. When SM implements a set + * of standard chat classes this can become a lot simpler. + * + * @version $Revision$ + * @author Paul Speed + */ +public class TestChatClient extends JFrame { + + private Client client; + private JEditorPane chatLog; + private StringBuilder chatMessages = new StringBuilder(); + private JTextField nameField; + private JTextField messageField; + + public TestChatClient(String host) throws IOException { + super("jME3 Test Chat Client - to:" + host); + + // Build out the UI + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setSize(800, 600); + + chatLog = new JEditorPane(); + chatLog.setEditable(false); + chatLog.setContentType("text/html"); + chatLog.setText(""); + + getContentPane().add(new JScrollPane(chatLog), "Center"); + + // A crude form + JPanel p = new JPanel(); + p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); + p.add(new JLabel("Name:")); + nameField = new JTextField(System.getProperty("user.name", "yourname")); + Dimension d = nameField.getPreferredSize(); + nameField.setMaximumSize(new Dimension(120, d.height + 6)); + p.add(nameField); + p.add(new JLabel(" Message:")); + messageField = new JTextField(); + p.add(messageField); + p.add(new JButton(new SendAction(true))); + p.add(new JButton(new SendAction(false))); + + getContentPane().add(p, "South"); + + client = Network.connectToServer(TestChatServer.NAME, TestChatServer.VERSION, + host, TestChatServer.PORT, TestChatServer.UDP_PORT); + client.addMessageListener(new ChatHandler(), ChatMessage.class); + client.start(); + } + + public static String getString(Component owner, String title, String message, String initialValue) { + return (String) JOptionPane.showInputDialog(owner, message, title, JOptionPane.PLAIN_MESSAGE, + null, null, initialValue); + } + + public static void main(String... args) throws Exception { + TestChatServer.initializeClasses(); + + // Grab a host string from the user + String s = getString(null, "Host Info", "Enter chat host:", "localhost"); + if (s == null) { + System.out.println("User cancelled."); + return; + } + + TestChatClient test = new TestChatClient(s); + test.setVisible(true); + } + + private class ChatHandler implements MessageListener { + + public void messageReceived(Client source, Message m) { + ChatMessage chat = (ChatMessage) m; + + System.out.println("Received:" + chat); + + // One of the least efficient ways to add text to a + // JEditorPane + chatMessages.append("" + (m.isReliable() ? "TCP" : "UDP") + ""); + chatMessages.append(" -- " + chat.getName() + " : "); + chatMessages.append(chat.getMessage()); + chatMessages.append("
      "); + String s = "" + chatMessages + ""; + chatLog.setText(s); + + // Set selection to the end so that the scroll panel will scroll + // down. + chatLog.select(s.length(), s.length()); + } + } + + private class SendAction extends AbstractAction { + + private boolean reliable; + + public SendAction(boolean reliable) { + super(reliable ? "TCP" : "UDP"); + this.reliable = reliable; + } + + public void actionPerformed(ActionEvent evt) { + String name = nameField.getText(); + String message = messageField.getText(); + + ChatMessage chat = new ChatMessage(name, message); + chat.setReliable(reliable); + System.out.println("Sending:" + chat); + client.send(chat); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java new file mode 100644 index 000000000..a2aa2ee1c --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2011 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.network; + +import com.jme3.network.*; +import com.jme3.network.serializing.Serializable; +import com.jme3.network.serializing.Serializer; + +/** + * A simple test chat server. When SM implements a set + * of standard chat classes this can become a lot simpler. + * + * @version $Revision$ + * @author Paul Speed + */ +public class TestChatServer { + // Normally these and the initialized method would + // be in shared constants or something. + + public static final String NAME = "Test Chat Server"; + public static final int VERSION = 1; + public static final int PORT = 5110; + public static final int UDP_PORT = 5110; + + public static void initializeClasses() { + // Doing it here means that the client code only needs to + // call our initialize. + Serializer.registerClass(ChatMessage.class); + } + + public static void main(String... args) throws Exception { + initializeClasses(); + + // Use this to test the client/server name version check + Server server = Network.createServer(NAME, VERSION, PORT, UDP_PORT); + server.start(); + + ChatHandler handler = new ChatHandler(); + server.addMessageListener(handler, ChatMessage.class); + + // Keep running basically forever + synchronized (NAME) { + NAME.wait(); + } + } + + private static class ChatHandler implements MessageListener { + + public ChatHandler() { + } + + public void messageReceived(HostedConnection source, Message m) { + if (m instanceof ChatMessage) { + // Keep track of the name just in case we + // want to know it for some other reason later and it's + // a good example of session data + source.setAttribute("name", ((ChatMessage) m).getName()); + + System.out.println("Broadcasting:" + m + " reliable:" + m.isReliable()); + + // Just rebroadcast... the reliable flag will stay the + // same so if it came in on UDP it will go out on that too + source.getServer().broadcast(m); + } else { + System.err.println("Received odd message:" + m); + } + } + } + + @Serializable + public static class ChatMessage extends AbstractMessage { + + private String name; + private String message; + + public ChatMessage() { + } + + public ChatMessage(String name, String message) { + setName(name); + setMessage(message); + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setMessage(String s) { + this.message = s; + } + + public String getMessage() { + return message; + } + + public String toString() { + return name + ":" + message; + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/network/TestLatency.java b/jme3-examples/src/main/java/jme3test/network/TestLatency.java new file mode 100644 index 000000000..6f49a003b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/network/TestLatency.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.network; + +import com.jme3.network.*; +import com.jme3.network.serializing.Serializable; +import com.jme3.network.serializing.Serializer; +import java.io.IOException; + +public class TestLatency { + + private static long startTime; + private static Client client; + private static MovingAverage average = new MovingAverage(100); + + static { + startTime = System.currentTimeMillis(); + } + + private static long getTime(){ + return System.currentTimeMillis() - startTime; + } + + @Serializable + public static class TimestampMessage extends AbstractMessage { + + long timeSent = 0; + long timeReceived = 0; + + public TimestampMessage(){ + setReliable(false); + } + + public TimestampMessage(long timeSent, long timeReceived){ + setReliable(false); + this.timeSent = timeSent; + this.timeReceived = timeReceived; + } + + } + + public static void main(String[] args) throws IOException, InterruptedException{ + Serializer.registerClass(TimestampMessage.class); + + Server server = Network.createServer(5110); + server.start(); + + client = Network.connectToServer("localhost", 5110); + client.start(); + + client.addMessageListener(new MessageListener(){ + public void messageReceived(Client source, Message m) { + TimestampMessage timeMsg = (TimestampMessage) m; + + long curTime = getTime(); + //System.out.println("Time sent: " + timeMsg.timeSent); + //System.out.println("Time received by server: " + timeMsg.timeReceived); + //System.out.println("Time recieved by client: " + curTime); + + long latency = (curTime - timeMsg.timeSent); + System.out.println("Latency: " + (latency) + " ms"); + //long timeOffset = ((timeMsg.timeSent + curTime) / 2) - timeMsg.timeReceived; + //System.out.println("Approximate timeoffset: "+ (timeOffset) + " ms"); + + average.add(latency); + System.out.println("Average latency: " + average.getAverage()); + + long latencyOffset = latency - average.getAverage(); + System.out.println("Latency offset: " + latencyOffset); + + client.send(new TimestampMessage(getTime(), 0)); + } + }, TimestampMessage.class); + + server.addMessageListener(new MessageListener(){ + public void messageReceived(HostedConnection source, Message m) { + TimestampMessage timeMsg = (TimestampMessage) m; + TimestampMessage outMsg = new TimestampMessage(timeMsg.timeSent, getTime()); + source.send(outMsg); + } + }, TimestampMessage.class); + + Thread.sleep(1); + + client.send(new TimestampMessage(getTime(), 0)); + + Object obj = new Object(); + synchronized(obj){ + obj.wait(); + } + } + +} diff --git a/jme3-examples/src/main/java/jme3test/network/TestMessages.java b/jme3-examples/src/main/java/jme3test/network/TestMessages.java new file mode 100644 index 000000000..361f097ec --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/network/TestMessages.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.network; + +import com.jme3.network.*; +import com.jme3.network.serializing.Serializable; +import com.jme3.network.serializing.Serializer; +import java.io.IOException; + +public class TestMessages { + + @Serializable + public static class PingMessage extends AbstractMessage { + } + + @Serializable + public static class PongMessage extends AbstractMessage { + } + + private static class ServerPingResponder implements MessageListener { + public void messageReceived(HostedConnection source, com.jme3.network.Message message) { + if (message instanceof PingMessage){ + System.out.println("Server: Received ping message!"); + source.send(new PongMessage()); + } + } + } + + private static class ClientPingResponder implements MessageListener { + public void messageReceived(Client source, com.jme3.network.Message message) { + if (message instanceof PongMessage){ + System.out.println("Client: Received pong message!"); + } + } + } + + public static void main(String[] args) throws IOException, InterruptedException{ + Serializer.registerClass(PingMessage.class); + Serializer.registerClass(PongMessage.class); + + Server server = Network.createServer(5110); + server.start(); + + Client client = Network.connectToServer("localhost", 5110); + client.start(); + + server.addMessageListener(new ServerPingResponder(), PingMessage.class); + client.addMessageListener(new ClientPingResponder(), PongMessage.class); + + System.out.println("Client: Sending ping message.."); + client.send(new PingMessage()); + + Object obj = new Object(); + synchronized (obj){ + obj.wait(); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/network/TestNetworkStress.java b/jme3-examples/src/main/java/jme3test/network/TestNetworkStress.java new file mode 100644 index 000000000..dab33c597 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/network/TestNetworkStress.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.network; + +import com.jme3.network.*; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TestNetworkStress implements ConnectionListener { + + public void connectionAdded(Server server, HostedConnection conn) { + System.out.println("Client Connected: "+conn.getId()); + //conn.close("goodbye"); + } + + public void connectionRemoved(Server server, HostedConnection conn) { + } + + public static void main(String[] args) throws IOException, InterruptedException{ + Logger.getLogger("").getHandlers()[0].setLevel(Level.OFF); + + Server server = Network.createServer(5110); + server.start(); + server.addConnectionListener(new TestNetworkStress()); + + for (int i = 0; i < 1000; i++){ + Client client = Network.connectToServer("localhost", 5110); + client.start(); + + Thread.sleep(10); + + client.close(); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/network/TestRemoteCall.java b/jme3-examples/src/main/java/jme3test/network/TestRemoteCall.java new file mode 100644 index 000000000..4335bb325 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/network/TestRemoteCall.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.network; + +import com.jme3.app.SimpleApplication; +import com.jme3.export.Savable; +import com.jme3.network.Client; +import com.jme3.network.Network; +import com.jme3.network.Server; +import com.jme3.network.rmi.ObjectStore; +import com.jme3.network.serializing.Serializer; +import com.jme3.network.serializing.serializers.SavableSerializer; +import com.jme3.scene.Spatial; +import java.io.IOException; +import java.util.concurrent.Callable; + +public class TestRemoteCall { + + private static SimpleApplication serverApp; + + /** + * Interface implemented by the server, exposing + * RMI calls that clients can use. + */ + public static interface ServerAccess { + /** + * Attaches the model with the given name to the server's scene. + * + * @param model The model name + * + * @return True if the model was attached. + * + * @throws RuntimeException If some error occurs. + */ + public boolean attachChild(String model); + } + + public static class ServerAccessImpl implements ServerAccess { + public boolean attachChild(String model) { + if (model == null) + throw new RuntimeException("Cannot be null. .. etc"); + + final String finalModel = model; + serverApp.enqueue(new Callable() { + public Void call() throws Exception { + Spatial spatial = serverApp.getAssetManager().loadModel(finalModel); + serverApp.getRootNode().attachChild(spatial); + return null; + } + }); + return true; + } + } + + public static void createServer(){ + serverApp = new SimpleApplication() { + @Override + public void simpleInitApp() { + } + }; + serverApp.start(); + + try { + Server server = Network.createServer(5110); + server.start(); + + ObjectStore store = new ObjectStore(server); + store.exposeObject("access", new ServerAccessImpl()); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + public static void main(String[] args) throws IOException, InterruptedException{ + Serializer.registerClass(Savable.class, new SavableSerializer()); + + createServer(); + + Client client = Network.connectToServer("localhost", 5110); + client.start(); + + ObjectStore store = new ObjectStore(client); + ServerAccess access = store.getExposedObject("access", ServerAccess.class, true); + boolean result = access.attachChild("Models/Oto/Oto.mesh.xml"); + System.out.println(result); + } +} diff --git a/jme3-examples/src/main/java/jme3test/network/TestSerialization.java b/jme3-examples/src/main/java/jme3test/network/TestSerialization.java new file mode 100644 index 000000000..cbdeb6697 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/network/TestSerialization.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.network; + +import com.jme3.network.*; +import com.jme3.network.serializing.Serializable; +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.util.*; + +public class TestSerialization implements MessageListener { + + @Serializable + public static class SomeObject { + + private int val; + + public SomeObject(){ + } + + public SomeObject(int val){ + this.val = val; + } + + public int getVal(){ + return val; + } + + @Override + public String toString(){ + return "SomeObject[val="+val+"]"; + } + } + + public enum Status { + High, + Middle, + Low; + } + + @Serializable + public static class TestSerializationMessage extends AbstractMessage { + + boolean z; + byte b; + char c; + short s; + int i; + float f; + long l; + double d; + + int[] ia; + List 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()); + } + } + } + + public void messageReceived(HostedConnection source, Message m) { + TestSerializationMessage cm = (TestSerializationMessage) m; + System.out.println(cm.z); + System.out.println(cm.b); + System.out.println(cm.c); + System.out.println(cm.s); + System.out.println(cm.i); + System.out.println(cm.f); + System.out.println(cm.l); + System.out.println(cm.d); + System.out.println(Arrays.toString(cm.ia)); + System.out.println(cm.ls); + System.out.println(cm.mp); + System.out.println(cm.status1); + System.out.println(cm.status2); + System.out.println(cm.date); + } + + public static void main(String[] args) throws IOException, InterruptedException{ + Serializer.registerClass(SomeObject.class); + Serializer.registerClass(TestSerializationMessage.class); + + Server server = Network.createServer( 5110 ); + server.start(); + + Client client = Network.connectToServer( "localhost", 5110 ); + client.start(); + + server.addMessageListener(new TestSerialization(), TestSerializationMessage.class); + client.send(new TestSerializationMessage(true)); + + Thread.sleep(10000); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/network/TestThroughput.java b/jme3-examples/src/main/java/jme3test/network/TestThroughput.java new file mode 100644 index 000000000..c261ec0b9 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/network/TestThroughput.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2011 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.network; + +import com.jme3.network.*; +import com.jme3.network.serializing.Serializable; +import com.jme3.network.serializing.Serializer; +import java.io.IOException; + +public class TestThroughput implements MessageListener { //extends MessageAdapter { + + private static long lastTime = -1; + private static long counter = 0; + private static long total = 0; + // Change this flag to test UDP instead of TCP + private static boolean testReliable = false; + private boolean isOnServer; + + public TestThroughput(boolean isOnServer) { + this.isOnServer = isOnServer; + } + + @Serializable + public static class TestMessage extends AbstractMessage { + + public TestMessage() { + setReliable(testReliable); + } + } + + @Override + public void messageReceived(MessageConnection source, Message msg) { + + if (!isOnServer) { + // It's local to the client so we got it back + counter++; + total++; + long time = System.currentTimeMillis(); +//System.out.println( "total:" + total + " counter:" + counter + " lastTime:" + lastTime + " time:" + time ); + if (lastTime < 0) { + lastTime = time; + } else if (time - lastTime > 1000) { + long delta = time - lastTime; + double scale = delta / 1000.0; + double pps = counter / scale; + System.out.println("messages per second:" + pps + " total messages:" + total); + counter = 0; + lastTime = time; + } + } else { + if (source == null) { + System.out.println("Received a message from a not fully connected source, msg:" + msg); + } else { +//System.out.println( "sending:" + msg + " back to client:" + source ); + // The 'reliable' flag is transient and the server doesn't + // (yet) reset this value for us. + ((com.jme3.network.Message) msg).setReliable(testReliable); + source.send(msg); + } + } + } + + public static void main(String[] args) throws IOException, InterruptedException { + + Serializer.registerClass(TestMessage.class); + + // Use this to test the client/server name version check + //Server server = Network.createServer( "bad name", 42, 5110, 5110 ); + Server server = Network.createServer(5110, 5110); + server.start(); + + Client client = Network.connectToServer("localhost", 5110); + client.start(); + + client.addMessageListener(new TestThroughput(false), TestMessage.class); + server.addMessageListener(new TestThroughput(true), TestMessage.class); + + Thread.sleep(1); + + TestMessage test = new TestMessage(); +// for( int i = 0; i < 10; i++ ) { + while (true) { +//System.out.println( "sending." ); + client.send(test); + } + + //Thread.sleep(5000); + } +} diff --git a/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyExamples.java b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyExamples.java new file mode 100644 index 000000000..19246e4f5 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyExamples.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.niftygui; + +import com.jme3.app.SimpleApplication; +import com.jme3.niftygui.NiftyJmeDisplay; +import de.lessvoid.nifty.Nifty; + +public class TestNiftyExamples extends SimpleApplication { + + private Nifty nifty; + + public static void main(String[] args){ + TestNiftyExamples app = new TestNiftyExamples(); + app.setPauseOnLostFocus(false); + app.start(); + } + + public void simpleInitApp() { + NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager, + inputManager, + audioRenderer, + guiViewPort); + nifty = niftyDisplay.getNifty(); + + nifty.fromXml("all/intro.xml", "start"); + + // attach the nifty display to the gui view port as a processor + guiViewPort.addProcessor(niftyDisplay); + + // disable the fly cam + flyCam.setEnabled(false); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyGui.java b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyGui.java new file mode 100644 index 000000000..8d3532844 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyGui.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.niftygui; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.niftygui.NiftyJmeDisplay; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.screen.Screen; +import de.lessvoid.nifty.screen.ScreenController; + +public class TestNiftyGui extends SimpleApplication implements ScreenController { + + private Nifty nifty; + + public static void main(String[] args){ + TestNiftyGui app = new TestNiftyGui(); + app.setPauseOnLostFocus(false); + app.start(); + } + + public void simpleInitApp() { + Box b = new Box(Vector3f.ZERO, 1, 1, 1); + Geometry geom = new Geometry("Box", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + geom.setMaterial(mat); + rootNode.attachChild(geom); + + NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager, + inputManager, + audioRenderer, + guiViewPort); + nifty = niftyDisplay.getNifty(); + nifty.fromXml("Interface/Nifty/HelloJme.xml", "start", this); + + // attach the nifty display to the gui view port as a processor + guiViewPort.addProcessor(niftyDisplay); + + // disable the fly cam +// flyCam.setEnabled(false); +// flyCam.setDragToRotate(true); + inputManager.setCursorVisible(true); + } + + public void bind(Nifty nifty, Screen screen) { + System.out.println("bind( " + screen.getScreenId() + ")"); + } + + public void onStartScreen() { + System.out.println("onStartScreen"); + } + + public void onEndScreen() { + System.out.println("onEndScreen"); + } + + public void quit(){ + nifty.gotoScreen("end"); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyToMesh.java b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyToMesh.java new file mode 100644 index 000000000..ef82f263d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyToMesh.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.niftygui; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.niftygui.NiftyJmeDisplay; +import com.jme3.renderer.Camera; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.Texture2D; +import de.lessvoid.nifty.Nifty; + +public class TestNiftyToMesh extends SimpleApplication{ + + private Nifty nifty; + + public static void main(String[] args){ + TestNiftyToMesh app = new TestNiftyToMesh(); + app.start(); + } + + public void simpleInitApp() { + ViewPort niftyView = renderManager.createPreView("NiftyView", new Camera(1024, 768)); + niftyView.setClearFlags(true, true, true); + NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager, + inputManager, + audioRenderer, + niftyView); + nifty = niftyDisplay.getNifty(); + nifty.fromXml("all/intro.xml", "start"); + niftyView.addProcessor(niftyDisplay); + + Texture2D depthTex = new Texture2D(1024, 768, Format.Depth); + FrameBuffer fb = new FrameBuffer(1024, 768, 1); + fb.setDepthTexture(depthTex); + + Texture2D tex = new Texture2D(1024, 768, Format.RGBA8); + tex.setMinFilter(MinFilter.Trilinear); + tex.setMagFilter(MagFilter.Bilinear); + + fb.setColorTexture(tex); + niftyView.setClearFlags(true, true, true); + niftyView.setOutputFrameBuffer(fb); + + Box b = new Box(Vector3f.ZERO, 1, 1, 1); + Geometry geom = new Geometry("Box", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", tex); + geom.setMaterial(mat); + rootNode.attachChild(geom); + } +} diff --git a/jme3-examples/src/main/java/jme3test/post/BloomUI.java b/jme3-examples/src/main/java/jme3test/post/BloomUI.java new file mode 100644 index 000000000..f4878c197 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/BloomUI.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.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/jme3-examples/src/main/java/jme3test/post/LightScatteringUI.java b/jme3-examples/src/main/java/jme3test/post/LightScatteringUI.java new file mode 100644 index 000000000..20369918b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/LightScatteringUI.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/post/SSAOUI.java b/jme3-examples/src/main/java/jme3test/post/SSAOUI.java new file mode 100644 index 000000000..a5b7ba8e8 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/SSAOUI.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.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/jme3-examples/src/main/java/jme3test/post/TestBloom.java b/jme3-examples/src/main/java/jme3test/post/TestBloom.java new file mode 100644 index 000000000..b73d234d7 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestBloom.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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); + int numSamples = getContext().getSettings().getSamples(); + if( numSamples > 0 ) { + fpp.setNumSamples(numSamples); + } + + 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/jme3-examples/src/main/java/jme3test/post/TestCartoonEdge.java b/jme3-examples/src/main/java/jme3test/post/TestCartoonEdge.java new file mode 100644 index 000000000..1ca328a8a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestCartoonEdge.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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); + int numSamples = getContext().getSettings().getSamples(); + if( numSamples > 0 ) { + fpp.setNumSamples(numSamples); + } + 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/jme3-examples/src/main/java/jme3test/post/TestCrossHatch.java b/jme3-examples/src/main/java/jme3test/post/TestCrossHatch.java new file mode 100644 index 000000000..2164ec229 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestCrossHatch.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.CrossHatchFilter; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.WireFrustum; +import com.jme3.scene.shape.Box; +import com.jme3.util.SkyFactory; + +public class TestCrossHatch extends SimpleApplication { + + float angle; + Spatial lightMdl; + Spatial teapot; + Geometry frustumMdl; + WireFrustum frustum; + boolean active=true; + FilterPostProcessor fpp; + + public static void main(String[] args){ + TestCrossHatch app = new TestCrossHatch(); + app.start(); + } + + @Override + public void simpleInitApp() { + // put the camera in a bad position + cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -7.139601f)); + cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f)); + //cam.setFrustumFar(1000); + + + Material mat = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md"); + mat.setFloat("Shininess", 15f); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f)); + mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f)); + mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f)); + + + + + Material matSoil = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md"); + matSoil.setFloat("Shininess", 15f); + matSoil.setBoolean("UseMaterialColors", true); + matSoil.setColor("Ambient", ColorRGBA.Gray); + matSoil.setColor("Diffuse", ColorRGBA.Black); + matSoil.setColor("Specular", ColorRGBA.Gray); + + + + teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + teapot.setLocalTranslation(0,0,10); + + teapot.setMaterial(mat); + teapot.setShadowMode(ShadowMode.CastAndReceive); + teapot.setLocalScale(10.0f); + rootNode.attachChild(teapot); + + + + Geometry soil=new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700)); + soil.setMaterial(matSoil); + soil.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(soil); + + DirectionalLight light=new DirectionalLight(); + light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + light.setColor(ColorRGBA.White.mult(1.5f)); + rootNode.addLight(light); + + // load sky + Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false); + sky.setCullHint(Spatial.CullHint.Never); + rootNode.attachChild(sky); + + fpp=new FilterPostProcessor(assetManager); + + int numSamples = getContext().getSettings().getSamples(); + if( numSamples > 0 ) { + fpp.setNumSamples(numSamples); + } + + CrossHatchFilter chf=new CrossHatchFilter(); + + + + viewPort.addProcessor(fpp); + fpp.addFilter(chf); + initInputs(); + + } + + private void initInputs() { + inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); + + ActionListener acl = new ActionListener() { + + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("toggle") && keyPressed) { + if(active){ + active=false; + viewPort.removeProcessor(fpp); + }else{ + active=true; + viewPort.addProcessor(fpp); + } + } + } + }; + + inputManager.addListener(acl, "toggle"); + + } + + + +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestDepthOfField.java b/jme3-examples/src/main/java/jme3test/post/TestDepthOfField.java new file mode 100644 index 000000000..9966adc61 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestDepthOfField.java @@ -0,0 +1,204 @@ +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.DepthOfFieldFilter; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; +import java.util.ArrayList; +import java.util.List; + +/** + * test + * @author Nehon + */ +public class TestDepthOfField extends SimpleApplication { + + private FilterPostProcessor fpp; + private Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f); + TerrainQuad terrain; + Material matRock; + DepthOfFieldFilter dofFilter; + + public static void main(String[] args) { + TestDepthOfField app = new TestDepthOfField(); + app.start(); + } + + @Override + public void simpleInitApp() { + + + Node mainScene = new Node("Main Scene"); + rootNode.attachChild(mainScene); + + createTerrain(mainScene); + DirectionalLight sun = new DirectionalLight(); + sun.setDirection(lightDir); + sun.setColor(ColorRGBA.White.clone().multLocal(1.7f)); + mainScene.addLight(sun); + + DirectionalLight l = new DirectionalLight(); + l.setDirection(Vector3f.UNIT_Y.mult(-1)); + l.setColor(ColorRGBA.White.clone().multLocal(0.3f)); + mainScene.addLight(l); + + flyCam.setMoveSpeed(50); + cam.setFrustumFar(3000); + cam.setLocation(new Vector3f(-700, 100, 300)); + cam.setRotation(new Quaternion().fromAngles(new float[]{FastMath.PI * 0.06f, FastMath.PI * 0.65f, 0})); + + + Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false); + sky.setLocalScale(350); + mainScene.attachChild(sky); + + + + fpp = new FilterPostProcessor(assetManager); + // fpp.setNumSamples(4); + int numSamples = getContext().getSettings().getSamples(); + if( numSamples > 0 ) { + fpp.setNumSamples(numSamples); + } + + dofFilter = new DepthOfFieldFilter(); + dofFilter.setFocusDistance(0); + dofFilter.setFocusRange(50); + dofFilter.setBlurScale(1.4f); + fpp.addFilter(dofFilter); + viewPort.addProcessor(fpp); + + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + if (name.equals("toggle")) { + dofFilter.setEnabled(!dofFilter.isEnabled()); + } + + + } + } + }, "toggle"); + inputManager.addListener(new AnalogListener() { + + public void onAnalog(String name, float value, float tpf) { + if (name.equals("blurScaleUp")) { + dofFilter.setBlurScale(dofFilter.getBlurScale() + 0.01f); + System.out.println("blurScale : " + dofFilter.getBlurScale()); + } + if (name.equals("blurScaleDown")) { + dofFilter.setBlurScale(dofFilter.getBlurScale() - 0.01f); + System.out.println("blurScale : " + dofFilter.getBlurScale()); + } + if (name.equals("focusRangeUp")) { + dofFilter.setFocusRange(dofFilter.getFocusRange() + 1f); + System.out.println("focusRange : " + dofFilter.getFocusRange()); + } + if (name.equals("focusRangeDown")) { + dofFilter.setFocusRange(dofFilter.getFocusRange() - 1f); + System.out.println("focusRange : " + dofFilter.getFocusRange()); + } + if (name.equals("focusDistanceUp")) { + dofFilter.setFocusDistance(dofFilter.getFocusDistance() + 1f); + System.out.println("focusDistance : " + dofFilter.getFocusDistance()); + } + if (name.equals("focusDistanceDown")) { + dofFilter.setFocusDistance(dofFilter.getFocusDistance() - 1f); + System.out.println("focusDistance : " + dofFilter.getFocusDistance()); + } + + } + }, "blurScaleUp", "blurScaleDown", "focusRangeUp", "focusRangeDown", "focusDistanceUp", "focusDistanceDown"); + + + inputManager.addMapping("toggle", new KeyTrigger(keyInput.KEY_SPACE)); + inputManager.addMapping("blurScaleUp", new KeyTrigger(keyInput.KEY_U)); + inputManager.addMapping("blurScaleDown", new KeyTrigger(keyInput.KEY_J)); + inputManager.addMapping("focusRangeUp", new KeyTrigger(keyInput.KEY_I)); + inputManager.addMapping("focusRangeDown", new KeyTrigger(keyInput.KEY_K)); + inputManager.addMapping("focusDistanceUp", new KeyTrigger(keyInput.KEY_O)); + inputManager.addMapping("focusDistanceDown", new KeyTrigger(keyInput.KEY_L)); + + } + + private void createTerrain(Node rootNode) { + matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matRock.setBoolean("useTriPlanarMapping", false); + matRock.setBoolean("WardIso", true); + matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap", grass); + matRock.setFloat("DiffuseMap_0_scale", 64); + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_1", dirt); + matRock.setFloat("DiffuseMap_1_scale", 16); + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_2", rock); + matRock.setFloat("DiffuseMap_2_scale", 128); + Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); + normalMap0.setWrap(WrapMode.Repeat); + Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); + normalMap1.setWrap(WrapMode.Repeat); + Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); + normalMap2.setWrap(WrapMode.Repeat); + matRock.setTexture("NormalMap", normalMap0); + matRock.setTexture("NormalMap_1", normalMap2); + matRock.setTexture("NormalMap_2", normalMap2); + + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f); + heightmap.load(); + } catch (Exception e) { + e.printStackTrace(); + } + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList(); + cameras.add(getCamera()); + terrain.setMaterial(matRock); + terrain.setLocalScale(new Vector3f(5, 5, 5)); + terrain.setLocalTranslation(new Vector3f(0, -30, 0)); + terrain.setLocked(false); // unlock it so we can edit the height + + terrain.setShadowMode(ShadowMode.Receive); + rootNode.attachChild(terrain); + + } + + @Override + public void simpleUpdate(float tpf) { + Vector3f origin = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.0f); + Vector3f direction = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.3f); + direction.subtractLocal(origin).normalizeLocal(); + Ray ray = new Ray(origin, direction); + CollisionResults results = new CollisionResults(); + int numCollisions = terrain.collideWith(ray, results); + if (numCollisions > 0) { + CollisionResult hit = results.getClosestCollision(); + fpsText.setText(""+hit.getDistance()); + dofFilter.setFocusDistance(hit.getDistance()/10.0f); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestFBOPassthrough.java b/jme3-examples/src/main/java/jme3test/post/TestFBOPassthrough.java new file mode 100644 index 000000000..47b74f472 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestFBOPassthrough.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package 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/jme3-examples/src/main/java/jme3test/post/TestFog.java b/jme3-examples/src/main/java/jme3test/post/TestFog.java new file mode 100644 index 000000000..729ebdf18 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestFog.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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); + } else { + assetManager.registerLocator("wildhouse.zip", ZipLocator.class); + } + 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); + int numSamples = getContext().getSettings().getSamples(); + if( numSamples > 0 ) { + fpp.setNumSamples(numSamples); + } + 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/jme3-examples/src/main/java/jme3test/post/TestHDR.java b/jme3-examples/src/main/java/jme3test/post/TestHDR.java new file mode 100644 index 000000000..7a44bc904 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestHDR.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.post.HDRRenderer; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.ui.Picture; + +public class TestHDR extends SimpleApplication { + + private HDRRenderer hdrRender; + private Picture dispQuad; + + public static void main(String[] args){ + TestHDR app = new TestHDR(); + app.start(); + } + + public Geometry createHDRBox(){ + Box boxMesh = new Box(Vector3f.ZERO, 1, 1, 1); + Geometry box = new Geometry("Box", boxMesh); + +// Material mat = assetManager.loadMaterial("Textures/HdrTest/Memorial.j3m"); +// box.setMaterial(mat); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Textures/HdrTest/Memorial.hdr")); + box.setMaterial(mat); + + return box; + } + +// private Material disp; + + @Override + public void simpleInitApp() { + hdrRender = new HDRRenderer(assetManager, renderer); + hdrRender.setSamples(0); + hdrRender.setMaxIterations(20); + hdrRender.setExposure(0.87f); + hdrRender.setThrottle(0.33f); + + viewPort.addProcessor(hdrRender); + +// config.setVisible(true); + + rootNode.attachChild(createHDRBox()); + } + + public void simpleUpdate(float tpf){ + if (hdrRender.isInitialized() && dispQuad == null){ + dispQuad = hdrRender.createDisplayQuad(); + dispQuad.setWidth(128); + dispQuad.setHeight(128); + dispQuad.setPosition(30, cam.getHeight() - 128 - 30); + guiNode.attachChild(dispQuad); + } + } + +// public void displayAvg(Renderer r){ +// r.setFrameBuffer(null); +// disp = prepare(-1, -1, settings.getWidth(), settings.getHeight(), 3, -1, scene64, disp); +// r.clearBuffers(true, true, true); +// r.renderGeometry(pic); +// } + +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestLightScattering.java b/jme3-examples/src/main/java/jme3test/post/TestLightScattering.java new file mode 100644 index 000000000..ad7f555c0 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestLightScattering.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.LightScatteringFilter; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.PssmShadowRenderer; +import com.jme3.util.SkyFactory; +import com.jme3.util.TangentBinormalGenerator; + +public class TestLightScattering extends SimpleApplication { + + public static void main(String[] args) { + TestLightScattering app = new TestLightScattering(); + + app.start(); + } + + @Override + public void simpleInitApp() { + // put the camera in a bad position + cam.setLocation(new Vector3f(55.35316f, -0.27061665f, 27.092093f)); + cam.setRotation(new Quaternion(0.010414706f, 0.9874893f, 0.13880467f, -0.07409228f)); +// cam.setDirection(new Vector3f(0,-0.5f,1.0f)); +// cam.setLocation(new Vector3f(0, 300, -500)); + //cam.setFrustumFar(1000); + flyCam.setMoveSpeed(10); + Material mat = assetManager.loadMaterial("Textures/Terrain/Rocky/Rocky.j3m"); + Spatial scene = assetManager.loadModel("Models/Terrain/Terrain.mesh.xml"); + TangentBinormalGenerator.generate(((Geometry)((Node)scene).getChild(0)).getMesh()); + scene.setMaterial(mat); + scene.setShadowMode(ShadowMode.CastAndReceive); + scene.setLocalScale(400); + scene.setLocalTranslation(0, -10, -120); + + rootNode.attachChild(scene); + + // load sky + rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false)); + + DirectionalLight sun = new DirectionalLight(); + Vector3f lightDir = new Vector3f(-0.12f, -0.3729129f, 0.74847335f); + sun.setDirection(lightDir); + sun.setColor(ColorRGBA.White.clone().multLocal(2)); + scene.addLight(sun); + + PssmShadowRenderer pssmRenderer = new PssmShadowRenderer(assetManager,1024,4); + pssmRenderer.setDirection(lightDir); + pssmRenderer.setShadowIntensity(0.55f); + // viewPort.addProcessor(pssmRenderer); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); +// SSAOFilter ssaoFilter= new SSAOFilter(viewPort, new SSAOConfig(0.36f,1.8f,0.84f,0.16f,false,true)); +// fpp.addFilter(ssaoFilter); + + +// Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); +// mat2.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); +// +// Sphere lite=new Sphere(8, 8, 10.0f); +// Geometry lightSphere=new Geometry("lightsphere", lite); +// lightSphere.setMaterial(mat2); + Vector3f lightPos = lightDir.multLocal(-3000); +// lightSphere.setLocalTranslation(lightPos); + // rootNode.attachChild(lightSphere); + LightScatteringFilter filter = new LightScatteringFilter(lightPos); + LightScatteringUI ui = new LightScatteringUI(inputManager, filter); + fpp.addFilter(filter); +//fpp.setNumSamples(4); + //fpp.addFilter(new RadialBlurFilter(0.3f,15.0f)); + // SSAOUI ui=new SSAOUI(inputManager, ssaoFilter.getConfig()); + + viewPort.addProcessor(fpp); + } + + @Override + public void simpleUpdate(float tpf) { + } +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestMultiRenderTarget.java b/jme3-examples/src/main/java/jme3test/post/TestMultiRenderTarget.java new file mode 100644 index 000000000..5ccbfa248 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestMultiRenderTarget.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture2D; +import com.jme3.ui.Picture; + +public class TestMultiRenderTarget extends SimpleApplication implements SceneProcessor { + + private FrameBuffer fb; + private Texture2D diffuseData, normalData, specularData, depthData; + private Geometry sphere; + private Picture display1, display2, display3, display4; + + private Picture display; + private Material mat; + + public static void main(String[] args){ + TestMultiRenderTarget app = new TestMultiRenderTarget(); + app.start(); + } + + @Override + public void simpleInitApp() { + viewPort.addProcessor(this); + renderManager.setForcedTechnique("GBuf"); + +// flyCam.setEnabled(false); + cam.setLocation(new Vector3f(4.8037705f, 4.851632f, 10.789033f)); + cam.setRotation(new Quaternion(-0.05143692f, 0.9483723f, -0.21131563f, -0.230846f)); + + Node tank = (Node) assetManager.loadModel("Models/HoverTank/Tank2.mesh.xml"); + + //tankMesh.getMaterial().setColor("Specular", ColorRGBA.Black); + rootNode.attachChild(tank); + + display1 = new Picture("Picture"); + display1.move(0, 0, -1); // make it appear behind stats view + display2 = (Picture) display1.clone(); + display3 = (Picture) display1.clone(); + display4 = (Picture) display1.clone(); + display = (Picture) display1.clone(); + + ColorRGBA[] colors = new ColorRGBA[]{ + ColorRGBA.White, + ColorRGBA.Blue, + ColorRGBA.Cyan, + ColorRGBA.DarkGray, + ColorRGBA.Green, + ColorRGBA.Magenta, + ColorRGBA.Orange, + ColorRGBA.Pink, + ColorRGBA.Red, + ColorRGBA.Yellow + }; + + for (int i = 0; i < 3; i++){ + PointLight pl = new PointLight(); + float angle = 0.314159265f * i; + pl.setPosition( new Vector3f(FastMath.cos(angle)*2f, 0, + FastMath.sin(angle)*2f)); + pl.setColor(colors[i]); + pl.setRadius(5); + rootNode.addLight(pl); + display.addLight(pl); + } + } + + public void initialize(RenderManager rm, ViewPort vp) { + reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight()); + viewPort.setOutputFrameBuffer(fb); + guiViewPort.setClearFlags(true, true, true); + guiNode.attachChild(display); +// guiNode.attachChild(display1); +// guiNode.attachChild(display2); +// guiNode.attachChild(display3); +// guiNode.attachChild(display4); + guiNode.updateGeometricState(); + } + + public void reshape(ViewPort vp, int w, int h) { + diffuseData = new Texture2D(w, h, Format.RGBA8); + normalData = new Texture2D(w, h, Format.RGBA8); + specularData = new Texture2D(w, h, Format.RGBA8); + depthData = new Texture2D(w, h, Format.Depth); + + mat = new Material(assetManager, "Common/MatDefs/Light/Deferred.j3md"); + mat.setTexture("DiffuseData", diffuseData); + mat.setTexture("SpecularData", specularData); + mat.setTexture("NormalData", normalData); + mat.setTexture("DepthData", depthData); + + display.setMaterial(mat); + display.setPosition(0, 0); + display.setWidth(w); + display.setHeight(h); + + display1.setTexture(assetManager, diffuseData, false); + display2.setTexture(assetManager, normalData, false); + display3.setTexture(assetManager, specularData, false); + display4.setTexture(assetManager, depthData, false); + + display1.setPosition(0, 0); + display2.setPosition(w/2, 0); + display3.setPosition(0, h/2); + display4.setPosition(w/2, h/2); + + display1.setWidth(w/2); + display1.setHeight(h/2); + + display2.setWidth(w/2); + display2.setHeight(h/2); + + display3.setWidth(w/2); + display3.setHeight(h/2); + + display4.setWidth(w/2); + display4.setHeight(h/2); + + guiNode.updateGeometricState(); + + fb = new FrameBuffer(w, h, 1); + fb.setDepthTexture(depthData); + fb.addColorTexture(diffuseData); + fb.addColorTexture(normalData); + fb.addColorTexture(specularData); + fb.setMultiTarget(true); + + /* + * Marks pixels in front of the far light boundary + Render back-faces of light volume + Depth test GREATER-EQUAL + Write to stencil on depth pass + Skipped for very small distant lights + */ + + /* + * Find amount of lit pixels inside the volume + Start pixel query + Render front faces of light volume + Depth test LESS-EQUAL + Don’t write anything – only EQUAL stencil test + */ + + /* + * Enable conditional rendering + Based on query results from previous stage + GPU skips rendering for invisible lights + */ + + /* + * Render front-faces of light volume + Depth test - LESS-EQUAL + Stencil test - EQUAL + Runs only on marked pixels inside light + */ + } + + public boolean isInitialized() { + return diffuseData != null; + } + + public void preFrame(float tpf) { + Matrix4f inverseViewProj = cam.getViewProjectionMatrix().invert(); + mat.setMatrix4("ViewProjectionMatrixInverse", inverseViewProj); + } + + public void postQueue(RenderQueue rq) { + } + + public void postFrame(FrameBuffer out) { + } + + public void cleanup() { + } + +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestMultiViewsFilters.java b/jme3-examples/src/main/java/jme3test/post/TestMultiViewsFilters.java new file mode 100644 index 000000000..477dd96c7 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestMultiViewsFilters.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.*; +import com.jme3.post.ssao.SSAOFilter; +import com.jme3.renderer.Camera; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.util.SkyFactory; + +public class TestMultiViewsFilters extends SimpleApplication { + + public static void main(String[] args) { + TestMultiViewsFilters app = new TestMultiViewsFilters(); + app.start(); + } + private boolean filterEnabled = true; + + public void simpleInitApp() { + // create the geometry and attach it + Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); + teaGeom.scale(3); + teaGeom.getMaterial().setColor("GlowColor", ColorRGBA.Green); + + DirectionalLight dl = new DirectionalLight(); + dl.setColor(ColorRGBA.White); + dl.setDirection(Vector3f.UNIT_XYZ.negate()); + + rootNode.addLight(dl); + rootNode.attachChild(teaGeom); + + // Setup first view + cam.setViewPort(.5f, 1f, 0f, 0.5f); + cam.setLocation(new Vector3f(3.3212643f, 4.484704f, 4.2812433f)); + cam.setRotation(new Quaternion(-0.07680723f, 0.92299235f, -0.2564353f, -0.27645364f)); + + // Setup second view + Camera cam2 = cam.clone(); + cam2.setViewPort(0f, 0.5f, 0f, 0.5f); + cam2.setLocation(new Vector3f(-0.10947256f, 1.5760219f, 4.81758f)); + cam2.setRotation(new Quaternion(0.0010108891f, 0.99857414f, -0.04928594f, 0.020481428f)); + + final ViewPort view2 = renderManager.createMainView("Bottom Left", cam2); + view2.setClearFlags(true, true, true); + view2.attachScene(rootNode); + + // Setup third view + Camera cam3 = cam.clone(); + cam3.setName("cam3"); + cam3.setViewPort(0f, .5f, .5f, 1f); + cam3.setLocation(new Vector3f(0.2846221f, 6.4271426f, 0.23380789f)); + cam3.setRotation(new Quaternion(0.004381671f, 0.72363687f, -0.69015175f, 0.0045953835f)); + + final ViewPort view3 = renderManager.createMainView("Top Left", cam3); + view3.setClearFlags(true, true, true); + view3.attachScene(rootNode); + + + // Setup fourth view + Camera cam4 = cam.clone(); + cam4.setName("cam4"); + cam4.setViewPort(.5f, 1f, .5f, 1f); + + cam4.setLocation(new Vector3f(4.775564f, 1.4548365f, 0.11491505f)); + cam4.setRotation(new Quaternion(0.02356979f, -0.74957186f, 0.026729556f, 0.66096294f)); + + final ViewPort view4 = renderManager.createMainView("Top Right", cam4); + view4.setClearFlags(true, true, true); + view4.attachScene(rootNode); + +// Camera cam5 = new Camera(200, 200); +// cam5.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f); +// cam5.setName("cam5"); +// cam5.setViewPort(5.23f, 6.33f, 0.56f, 1.66f); +// this.setViewPortAreas(5.23f, 6.33f, 0.56f, 1.66f); +// this.setViewPortCamSize(200, 200); +// 1046,1266,112,332 + Camera cam5 = cam.clone(); + cam5.setName("cam5"); + cam5.setViewPort(1046f/settings.getWidth(), 1266f/settings.getWidth(), 112f/settings.getHeight(), 332f/settings.getHeight()); + cam5.setLocation(new Vector3f(0.2846221f, 6.4271426f, 0.23380789f)); + cam5.setRotation(new Quaternion(0.004381671f, 0.72363687f, -0.69015175f, 0.0045953835f)); + + final ViewPort view5 = renderManager.createMainView("center", cam5); + view5.setClearFlags(true, true, true); + view5.attachScene(rootNode); + + + + rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false)); + + final FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + final FilterPostProcessor fpp2 = new FilterPostProcessor(assetManager); + final FilterPostProcessor fpp3 = new FilterPostProcessor(assetManager); + final FilterPostProcessor fpp4 = new FilterPostProcessor(assetManager); + final FilterPostProcessor fpp5 = new FilterPostProcessor(assetManager); + + + // fpp.addFilter(new WaterFilter(rootNode, Vector3f.UNIT_Y.mult(-1))); + fpp3.addFilter(new CartoonEdgeFilter()); + + fpp2.addFilter(new CrossHatchFilter()); + final FogFilter ff = new FogFilter(ColorRGBA.Yellow, 0.7f, 2); + fpp.addFilter(ff); + + final RadialBlurFilter rbf = new RadialBlurFilter(1, 10); + // rbf.setEnabled(false); + fpp.addFilter(rbf); + + + SSAOFilter f = new SSAOFilter(1.8899765f, 20.490374f, 0.4699998f, 0.1f);; + fpp4.addFilter(f); + SSAOUI ui = new SSAOUI(inputManager, f); + + fpp5.addFilter(new BloomFilter(BloomFilter.GlowMode.Objects)); + + viewPort.addProcessor(fpp); + view2.addProcessor(fpp2); + view3.addProcessor(fpp3); + view4.addProcessor(fpp4); + view5.addProcessor(fpp5); + + + + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("press") && isPressed) { + if (filterEnabled) { + viewPort.removeProcessor(fpp); + view2.removeProcessor(fpp2); + view3.removeProcessor(fpp3); + view4.removeProcessor(fpp4); + view5.removeProcessor(fpp5); + } else { + viewPort.addProcessor(fpp); + view2.addProcessor(fpp2); + view3.addProcessor(fpp3); + view4.addProcessor(fpp4); + view5.addProcessor(fpp5); + } + filterEnabled = !filterEnabled; + } + if (name.equals("filter") && isPressed) { + ff.setEnabled(!ff.isEnabled()); + rbf.setEnabled(!rbf.isEnabled()); + } + } + }, "press", "filter"); + + inputManager.addMapping("press", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("filter", new KeyTrigger(KeyInput.KEY_F)); + + } +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestMultiplesFilters.java b/jme3-examples/src/main/java/jme3test/post/TestMultiplesFilters.java new file mode 100644 index 000000000..010651379 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestMultiplesFilters.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.HttpZipLocator; +import com.jme3.asset.plugins.ZipLocator; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.post.filters.ColorOverlayFilter; +import com.jme3.post.ssao.SSAOFilter; +import com.jme3.scene.Spatial; +import com.jme3.util.SkyFactory; +import com.jme3.water.WaterFilter; +import java.io.File; + +public class TestMultiplesFilters extends SimpleApplication { + + private static boolean useHttp = false; + + public static void main(String[] args) { + File file = new File("wildhouse.zip"); + if (!file.exists()) { + useHttp = true; + } + TestMultiplesFilters app = new TestMultiplesFilters(); + app.start(); + } + SSAOFilter ssaoFilter; + FilterPostProcessor fpp; + boolean en = true; + + public void simpleInitApp() { + this.flyCam.setMoveSpeed(10); + cam.setLocation(new Vector3f(6.0344796f, 1.5054002f, 55.572033f)); + cam.setRotation(new Quaternion(0.0016069f, 0.9810479f, -0.008143323f, 0.19358753f)); + + // load sky + rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false)); + + // create the geometry and attach it + // load the level from zip or http zip + if (useHttp) { + assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip", HttpZipLocator.class); + } else { + assetManager.registerLocator("wildhouse.zip", ZipLocator.class); + } + Spatial scene = assetManager.loadModel("main.scene"); + + + DirectionalLight sun = new DirectionalLight(); + sun.setDirection(new Vector3f(-0.4790551f, -0.39247334f, -0.7851566f)); + sun.setColor(ColorRGBA.White.clone().multLocal(2)); + scene.addLight(sun); + + fpp = new FilterPostProcessor(assetManager); + // fpp.setNumSamples(4); + ssaoFilter = new SSAOFilter(0.92f, 2.2f, 0.46f, 0.2f); + final WaterFilter water=new WaterFilter(rootNode,new Vector3f(-0.4790551f, -0.39247334f, -0.7851566f)); + water.setWaterHeight(-20); + SSAOUI ui=new SSAOUI(inputManager,ssaoFilter); + final BloomFilter bloom = new BloomFilter(); + final ColorOverlayFilter overlay = new ColorOverlayFilter(ColorRGBA.LightGray); + + + fpp.addFilter(ssaoFilter); + + fpp.addFilter(water); + + fpp.addFilter(bloom); + + fpp.addFilter(overlay); + + viewPort.addProcessor(fpp); + + rootNode.attachChild(scene); + + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if ("toggleSSAO".equals(name) && isPressed) { + if (ssaoFilter.isEnabled()) { + ssaoFilter.setEnabled(false); + } else { + ssaoFilter.setEnabled(true); + } + } + if ("toggleWater".equals(name) && isPressed) { + if (water.isEnabled()) { + water.setEnabled(false); + } else { + water.setEnabled(true); + } + } + if ("toggleBloom".equals(name) && isPressed) { + if (bloom.isEnabled()) { + bloom.setEnabled(false); + } else { + bloom.setEnabled(true); + } + } + if ("toggleOverlay".equals(name) && isPressed) { + if (overlay.isEnabled()) { + overlay.setEnabled(false); + } else { + overlay.setEnabled(true); + } + } + } + }, "toggleSSAO", "toggleBloom", "toggleWater","toggleOverlay"); + inputManager.addMapping("toggleSSAO", new KeyTrigger(KeyInput.KEY_1)); + inputManager.addMapping("toggleWater", new KeyTrigger(KeyInput.KEY_2)); + inputManager.addMapping("toggleBloom", new KeyTrigger(KeyInput.KEY_3)); + inputManager.addMapping("toggleOverlay", new KeyTrigger(KeyInput.KEY_4)); + + } +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java b/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java new file mode 100644 index 000000000..4ad419668 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.ColorOverlayFilter; +import com.jme3.post.filters.FadeFilter; +import com.jme3.post.filters.RadialBlurFilter; +import com.jme3.renderer.Caps; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.scene.shape.Box; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; +import com.jme3.util.TangentBinormalGenerator; + +public class TestPostFilters extends SimpleApplication implements ActionListener { + + private FilterPostProcessor fpp; + private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); + FadeFilter fade; + + public static void main(String[] args) { + TestPostFilters app = new TestPostFilters(); + app.start(); + } + + public void setupFilters() { + if (renderer.getCaps().contains(Caps.GLSL100)) { + fpp = new FilterPostProcessor(assetManager); + // fpp.setNumSamples(4); + fpp.addFilter(new ColorOverlayFilter(ColorRGBA.LightGray)); + fpp.addFilter(new RadialBlurFilter()); + //fade=new FadeFilter(1.0f); + //fpp.addFilter(fade); + + + viewPort.addProcessor(fpp); + } + } + + public void setupSkyBox() { + Texture envMap; + if (renderer.getCaps().contains(Caps.FloatTexture)) { + envMap = assetManager.loadTexture("Textures/Sky/St Peters/StPeters.hdr"); + } else { + envMap = assetManager.loadTexture("Textures/Sky/St Peters/StPeters.jpg"); + } + rootNode.attachChild(SkyFactory.createSky(assetManager, envMap, new Vector3f(-1, -1, -1), true)); + } + + public void setupLighting() { + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(lightDir); + + dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1)); + + rootNode.addLight(dl); + + dl = new DirectionalLight(); + dl.setDirection(new Vector3f(1, 0, -1).normalizeLocal()); + + dl.setColor(new ColorRGBA(.4f, .4f, .4f, 1)); + + rootNode.addLight(dl); + } + + public void setupFloor() { + Material mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"); + mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat); + mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat); + mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat); + Box floor = new Box(Vector3f.ZERO, 50, 1f, 50); + TangentBinormalGenerator.generate(floor); + floor.scaleTextureCoordinates(new Vector2f(5, 5)); + Geometry floorGeom = new Geometry("Floor", floor); + floorGeom.setMaterial(mat); + floorGeom.setShadowMode(ShadowMode.Receive); + rootNode.attachChild(floorGeom); + } + + public void setupSignpost() { + Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml"); + Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); + signpost.setMaterial(mat); + signpost.rotate(0, FastMath.HALF_PI, 0); + signpost.setLocalTranslation(12, 3.5f, 30); + signpost.setLocalScale(4); + signpost.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(signpost); + } + + @Override + public void simpleInitApp() { + cam.setLocation(new Vector3f(-32.295086f, 54.80136f, 79.59805f)); + cam.setRotation(new Quaternion(0.074364014f, 0.92519957f, -0.24794696f, 0.27748522f)); + cam.update(); + + cam.setFrustumFar(300); + flyCam.setMoveSpeed(30); + + rootNode.setCullHint(CullHint.Never); + + setupLighting(); + setupSkyBox(); + + + setupFloor(); + + setupSignpost(); + + setupFilters(); + + initInput(); + + } + + protected void initInput() { + flyCam.setMoveSpeed(3); + //init input + inputManager.addMapping("fadein", new KeyTrigger(KeyInput.KEY_I)); + inputManager.addListener(this, "fadein"); + inputManager.addMapping("fadeout", new KeyTrigger(KeyInput.KEY_O)); + inputManager.addListener(this, "fadeout"); + + } + + public void onAction(String name, boolean value, float tpf) { + if (name.equals("fadein") && value) { + fade.fadeIn(); + System.out.println("fade in"); + + } + if (name.equals("fadeout") && value) { + fade.fadeOut(); + System.out.println("fade out"); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestPostFiltersCompositing.java b/jme3-examples/src/main/java/jme3test/post/TestPostFiltersCompositing.java new file mode 100644 index 000000000..f05276a81 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestPostFiltersCompositing.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.ZipLocator; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.post.filters.ColorOverlayFilter; +import com.jme3.post.filters.ComposeFilter; +import com.jme3.scene.Spatial; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture2D; +import com.jme3.util.SkyFactory; + +/** + * This test showcases the possibility to compose the post filtered outputs of several viewports. + * The usual use case is when you want to apply some post process to the main viewport and then other post process to the gui viewport + * @author Nehon + */ +public class TestPostFiltersCompositing extends SimpleApplication { + + public static void main(String[] args) { + TestPostFiltersCompositing app = new TestPostFiltersCompositing(); + app.start(); + } + + 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)); + + makeScene(); + + //Creating the main view port post processor + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + fpp.addFilter(new ColorOverlayFilter(ColorRGBA.Blue)); + viewPort.addProcessor(fpp); + + //creating a frame buffer for the mainviewport + FrameBuffer mainVPFrameBuffer = new FrameBuffer(cam.getWidth(), cam.getHeight(), 1); + Texture2D mainVPTexture = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); + mainVPFrameBuffer.addColorTexture(mainVPTexture); + mainVPFrameBuffer.setDepthBuffer(Image.Format.Depth); + viewPort.setOutputFrameBuffer(mainVPFrameBuffer); + + //creating the post processor for the gui viewport + final FilterPostProcessor guifpp = new FilterPostProcessor(assetManager); + guifpp.addFilter(new ColorOverlayFilter(ColorRGBA.Red)); + //this will compose the main viewport texture with the guiviewport back buffer. + //Note that you can swich the order of the filters so that guiviewport filters are applied or not to the main viewport texture + guifpp.addFilter(new ComposeFilter(mainVPTexture)); + + guiViewPort.addProcessor(guifpp); + + //compositing is done my mixing texture depending on the alpha channel, + //it's important that the guiviewport clear color alpha value is set to 0 + guiViewPort.setBackgroundColor(new ColorRGBA(0, 0, 0, 0)); + + + } + + private void makeScene() { + // load sky + rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false)); + assetManager.registerLocator("wildhouse.zip", ZipLocator.class); + 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); + rootNode.attachChild(scene); + } +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestPosterization.java b/jme3-examples/src/main/java/jme3test/post/TestPosterization.java new file mode 100644 index 000000000..4200ad463 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestPosterization.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.PosterizationFilter; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.WireFrustum; +import com.jme3.scene.shape.Box; +import com.jme3.util.SkyFactory; + +public class TestPosterization extends SimpleApplication { + + float angle; + Spatial lightMdl; + Spatial teapot; + Geometry frustumMdl; + WireFrustum frustum; + boolean active=true; + FilterPostProcessor fpp; + + public static void main(String[] args){ + TestPosterization app = new TestPosterization(); + app.start(); + } + + @Override + public void simpleInitApp() { + // put the camera in a bad position + cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -7.139601f)); + cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f)); + //cam.setFrustumFar(1000); + + + Material mat = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md"); + mat.setFloat("Shininess", 15f); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f)); + mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f)); + mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f)); + + + + + Material matSoil = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md"); + matSoil.setFloat("Shininess", 15f); + matSoil.setBoolean("UseMaterialColors", true); + matSoil.setColor("Ambient", ColorRGBA.Gray); + matSoil.setColor("Diffuse", ColorRGBA.Black); + matSoil.setColor("Specular", ColorRGBA.Gray); + + + + teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + teapot.setLocalTranslation(0,0,10); + + teapot.setMaterial(mat); + teapot.setShadowMode(ShadowMode.CastAndReceive); + teapot.setLocalScale(10.0f); + rootNode.attachChild(teapot); + + + + Geometry soil=new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700)); + soil.setMaterial(matSoil); + soil.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(soil); + + DirectionalLight light=new DirectionalLight(); + light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + light.setColor(ColorRGBA.White.mult(1.5f)); + rootNode.addLight(light); + + // load sky + Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false); + sky.setCullHint(Spatial.CullHint.Never); + rootNode.attachChild(sky); + + fpp=new FilterPostProcessor(assetManager); + PosterizationFilter pf=new PosterizationFilter(); + + + + viewPort.addProcessor(fpp); + fpp.addFilter(pf); + initInputs(); + + } + + private void initInputs() { + inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); + + ActionListener acl = new ActionListener() { + + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("toggle") && keyPressed) { + if(active){ + active=false; + viewPort.removeProcessor(fpp); + }else{ + active=true; + viewPort.addProcessor(fpp); + } + } + } + }; + + inputManager.addListener(acl, "toggle"); + + } + + + +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestRenderToCubemap.java b/jme3-examples/src/main/java/jme3test/post/TestRenderToCubemap.java new file mode 100644 index 000000000..5eced134e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestRenderToCubemap.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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.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.TextureCubeMap; +import com.jme3.util.SkyFactory; + +/** + * Renders a rotating box to a cubemap texture, then applies the cubemap + * texture as a sky. + */ +public class TestRenderToCubemap extends SimpleApplication { + + private Geometry offBox; + private float angle = 0; + private ViewPort offView; + + public static void main(String[] args){ + TestRenderToCubemap app = new TestRenderToCubemap(); + app.start(); + } + + public Texture setupOffscreenView(){ + Camera offCamera = new Camera(512, 512); + + offView = renderManager.createPreView("Offscreen View", offCamera); + offView.setClearFlags(true, true, true); + offView.setBackgroundColor(ColorRGBA.DarkGray); + + // create offscreen framebuffer + FrameBuffer offBuffer = new FrameBuffer(512, 512, 1); + + //setup framebuffer's cam + offCamera.setFrustumPerspective(45f, 1f, 1f, 1000f); + offCamera.setLocation(new Vector3f(0f, 0f, -5f)); + offCamera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y); + + //setup framebuffer's texture + TextureCubeMap offTex = new TextureCubeMap(512, 512, Format.RGBA8); + offTex.setMinFilter(Texture.MinFilter.Trilinear); + offTex.setMagFilter(Texture.MagFilter.Bilinear); + + //setup framebuffer to use texture + offBuffer.setDepthBuffer(Format.Depth); + offBuffer.setMultiTarget(true); + offBuffer.addColorTexture(offTex, TextureCubeMap.Face.NegativeX); + offBuffer.addColorTexture(offTex, TextureCubeMap.Face.PositiveX); + offBuffer.addColorTexture(offTex, TextureCubeMap.Face.NegativeY); + offBuffer.addColorTexture(offTex, TextureCubeMap.Face.PositiveY); + offBuffer.addColorTexture(offTex, TextureCubeMap.Face.NegativeZ); + offBuffer.addColorTexture(offTex, TextureCubeMap.Face.PositiveZ); + + //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); + + Texture offTex = setupOffscreenView(); + rootNode.attachChild(SkyFactory.createSky(assetManager, offTex, false)); + } + + @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(); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java b/jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java new file mode 100644 index 000000000..550a68949 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext.Type; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; +import com.jme3.util.Screenshots; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +/** + * This test renders a scene to an offscreen framebuffer, then copies + * the contents to a Swing JFrame. Note that some parts are done inefficently, + * this is done to make the code more readable. + */ +public class TestRenderToMemory extends SimpleApplication implements SceneProcessor { + + private Geometry offBox; + private float angle = 0; + + private FrameBuffer offBuffer; + private ViewPort offView; + private Texture2D offTex; + private Camera offCamera; + private ImageDisplay display; + + private static final int width = 800, height = 600; + + private final ByteBuffer cpuBuf = BufferUtils.createByteBuffer(width * height * 4); + private final byte[] cpuArray = new byte[width * height * 4]; + private final BufferedImage image = new BufferedImage(width, height, + BufferedImage.TYPE_4BYTE_ABGR); + + private class ImageDisplay extends JPanel { + + private long t; + private long total; + private int frames; + private int fps; + + @Override + public void paintComponent(Graphics gfx) { + super.paintComponent(gfx); + Graphics2D g2d = (Graphics2D) gfx; + + if (t == 0) + t = timer.getTime(); + +// g2d.setBackground(Color.BLACK); +// g2d.clearRect(0,0,width,height); + + synchronized (image){ + g2d.drawImage(image, null, 0, 0); + } + + long t2 = timer.getTime(); + long dt = t2 - t; + total += dt; + frames ++; + t = t2; + + if (total > 1000){ + fps = frames; + total = 0; + frames = 0; + } + + g2d.setColor(Color.white); + g2d.drawString("FPS: "+fps, 0, getHeight() - 100); + } + } + + public static void main(String[] args){ + TestRenderToMemory app = new TestRenderToMemory(); + app.setPauseOnLostFocus(false); + AppSettings settings = new AppSettings(true); + settings.setResolution(1, 1); + app.setSettings(settings); + app.start(Type.OffscreenSurface); + } + + public void createDisplayFrame(){ + SwingUtilities.invokeLater(new Runnable(){ + public void run(){ + JFrame frame = new JFrame("Render Display"); + display = new ImageDisplay(); + display.setPreferredSize(new Dimension(width, height)); + frame.getContentPane().add(display); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.addWindowListener(new WindowAdapter(){ + public void windowClosed(WindowEvent e){ + stop(); + } + }); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setResizable(false); + frame.setVisible(true); + } + }); + } + + public void updateImageContents(){ + cpuBuf.clear(); + renderer.readFrameBuffer(offBuffer, cpuBuf); + + synchronized (image) { + Screenshots.convertScreenShot(cpuBuf, image); + } + + if (display != null) + display.repaint(); + } + + public void setupOffscreenView(){ + offCamera = new Camera(width, height); + + // create a pre-view. a view that is rendered before the main view + offView = renderManager.createPreView("Offscreen View", offCamera); + offView.setBackgroundColor(ColorRGBA.DarkGray); + offView.setClearFlags(true, true, true); + + // this will let us know when the scene has been rendered to the + // frame buffer + offView.addProcessor(this); + + // create offscreen framebuffer + offBuffer = new FrameBuffer(width, height, 1); + + //setup framebuffer's cam + offCamera.setFrustumPerspective(45f, 1f, 1f, 1000f); + offCamera.setLocation(new Vector3f(0f, 0f, -5f)); + offCamera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y); + + //setup framebuffer's texture +// offTex = new Texture2D(width, height, Format.RGBA8); + + //setup framebuffer to use renderbuffer + // this is faster for gpu -> cpu copies + offBuffer.setDepthBuffer(Format.Depth); + offBuffer.setColorBuffer(Format.RGBA8); +// offBuffer.setColorTexture(offTex); + + //set viewport to render to offscreen framebuffer + offView.setOutputFrameBuffer(offBuffer); + + // setup framebuffer's scene + Box boxMesh = new Box(Vector3f.ZERO, 1,1,1); + Material material = assetManager.loadMaterial("Interface/Logo/Logo.j3m"); + offBox = new Geometry("box", boxMesh); + offBox.setMaterial(material); + + // attach the scene to the viewport to be rendered + offView.attachScene(offBox); + } + + @Override + public void simpleInitApp() { + setupOffscreenView(); + createDisplayFrame(); + } + + @Override + public void simpleUpdate(float tpf){ + Quaternion q = new Quaternion(); + angle += tpf; + angle %= FastMath.TWO_PI; + q.fromAngles(angle, 0, angle); + + offBox.setLocalRotation(q); + offBox.updateLogicalState(tpf); + offBox.updateGeometricState(); + } + + public void initialize(RenderManager rm, ViewPort vp) { + } + + public void reshape(ViewPort vp, int w, int h) { + } + + public boolean isInitialized() { + return true; + } + + public void preFrame(float tpf) { + } + + public void postQueue(RenderQueue rq) { + } + + /** + * Update the CPU image's contents after the scene has + * been rendered to the framebuffer. + */ + public void postFrame(FrameBuffer out) { + updateImageContents(); + } + + public void cleanup() { + } + + +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestRenderToTexture.java b/jme3-examples/src/main/java/jme3test/post/TestRenderToTexture.java new file mode 100644 index 000000000..ba872831a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestRenderToTexture.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; + +/** + * This test renders a scene to a texture, then displays the texture on a cube. + */ +public class TestRenderToTexture extends SimpleApplication implements ActionListener { + + private static final String TOGGLE_UPDATE = "Toggle Update"; + private Geometry offBox; + private float angle = 0; + private ViewPort offView; + + public static void main(String[] args){ + TestRenderToTexture app = new TestRenderToTexture(); + app.start(); + } + + public Texture setupOffscreenView(){ + Camera offCamera = new Camera(512, 512); + + offView = renderManager.createPreView("Offscreen View", offCamera); + offView.setClearFlags(true, true, true); + offView.setBackgroundColor(ColorRGBA.DarkGray); + + // create offscreen framebuffer + FrameBuffer offBuffer = new FrameBuffer(512, 512, 1); + + //setup framebuffer's cam + offCamera.setFrustumPerspective(45f, 1f, 1f, 1000f); + offCamera.setLocation(new Vector3f(0f, 0f, -5f)); + offCamera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y); + + //setup framebuffer's texture + Texture2D offTex = new Texture2D(512, 512, Format.RGBA8); + offTex.setMinFilter(Texture.MinFilter.Trilinear); + offTex.setMagFilter(Texture.MagFilter.Bilinear); + + //setup framebuffer to use texture + offBuffer.setDepthBuffer(Format.Depth); + offBuffer.setColorTexture(offTex); + + //set viewport to render to offscreen framebuffer + offView.setOutputFrameBuffer(offBuffer); + + // setup framebuffer's scene + Box boxMesh = new Box(Vector3f.ZERO, 1,1,1); + Material material = assetManager.loadMaterial("Interface/Logo/Logo.j3m"); + offBox = new Geometry("box", boxMesh); + offBox.setMaterial(material); + + // attach the scene to the viewport to be rendered + offView.attachScene(offBox); + + return offTex; + } + + @Override + public void simpleInitApp() { + cam.setLocation(new Vector3f(3, 3, 3)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + + //setup main scene + Geometry quad = new Geometry("box", new Box(Vector3f.ZERO, 1,1,1)); + + Texture offTex = setupOffscreenView(); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", offTex); + quad.setMaterial(mat); + rootNode.attachChild(quad); + inputManager.addMapping(TOGGLE_UPDATE, new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(this, TOGGLE_UPDATE); + } + + @Override + public void simpleUpdate(float tpf){ + Quaternion q = new Quaternion(); + + if (offView.isEnabled()) { + angle += tpf; + angle %= FastMath.TWO_PI; + q.fromAngles(angle, 0, angle); + + offBox.setLocalRotation(q); + offBox.updateLogicalState(tpf); + offBox.updateGeometricState(); + } + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals(TOGGLE_UPDATE) && isPressed) { + offView.setEnabled(!offView.isEnabled()); + } + } + + +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestSSAO.java b/jme3-examples/src/main/java/jme3test/post/TestSSAO.java new file mode 100644 index 000000000..3cd508a5d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestSSAO.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.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/jme3-examples/src/main/java/jme3test/post/TestTransparentCartoonEdge.java b/jme3-examples/src/main/java/jme3test/post/TestTransparentCartoonEdge.java new file mode 100644 index 000000000..46ba0ac49 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestTransparentCartoonEdge.java @@ -0,0 +1,97 @@ +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.CartoonEdgeFilter; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture; + +public class TestTransparentCartoonEdge extends SimpleApplication { + + public static void main(String[] args){ + TestTransparentCartoonEdge app = new TestTransparentCartoonEdge(); + app.start(); + } + + public void simpleInitApp() { + renderManager.setAlphaToCoverage(true); + cam.setLocation(new Vector3f(0.14914267f, 0.58147097f, 4.7686534f)); + cam.setRotation(new Quaternion(-0.0044764364f, 0.9767943f, 0.21314798f, 0.020512417f)); + +// cam.setLocation(new Vector3f(2.0606942f, 3.20342f, 6.7860126f)); +// cam.setRotation(new Quaternion(-0.017481906f, 0.98241085f, -0.12393151f, -0.13857932f)); + + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + + Quad q = new Quad(20, 20); + q.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(5)); + Geometry geom = new Geometry("floor", q); + Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); + geom.setMaterial(mat); + + geom.rotate(-FastMath.HALF_PI, 0, 0); + geom.center(); + geom.setShadowMode(ShadowMode.Receive); + rootNode.attachChild(geom); + + // create the geometry and attach it + Spatial teaGeom = assetManager.loadModel("Models/Tree/Tree.mesh.j3o"); + teaGeom.setQueueBucket(Bucket.Transparent); + teaGeom.setShadowMode(ShadowMode.Cast); + makeToonish(teaGeom); + + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(2)); + rootNode.addLight(al); + + DirectionalLight dl1 = new DirectionalLight(); + dl1.setDirection(new Vector3f(1, -1, 1).normalizeLocal()); + dl1.setColor(new ColorRGBA(0.965f, 0.949f, 0.772f, 1f).mult(0.7f)); + rootNode.addLight(dl1); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + dl.setColor(new ColorRGBA(0.965f, 0.949f, 0.772f, 1f).mult(0.7f)); + rootNode.addLight(dl); + + rootNode.attachChild(teaGeom); + + FilterPostProcessor fpp=new FilterPostProcessor(assetManager); + CartoonEdgeFilter toon=new CartoonEdgeFilter(); + toon.setEdgeWidth(0.5f); + toon.setEdgeIntensity(1.0f); + toon.setNormalThreshold(0.8f); + fpp.addFilter(toon); + viewPort.addProcessor(fpp); + } + + public void makeToonish(Spatial spatial){ + if (spatial instanceof Node){ + Node n = (Node) spatial; + for (Spatial child : n.getChildren()) + makeToonish(child); + }else if (spatial instanceof Geometry){ + Geometry g = (Geometry) spatial; + Material m = g.getMaterial(); + if (m.getMaterialDef().getName().equals("Phong Lighting")){ + Texture t = assetManager.loadTexture("Textures/ColorRamp/toon.png"); +// t.setMinFilter(Texture.MinFilter.NearestNoMipMaps); +// t.setMagFilter(Texture.MagFilter.Nearest); + m.setTexture("ColorRamp", t); + m.setBoolean("UseMaterialColors", true); + m.setColor("Specular", ColorRGBA.Black); + m.setColor("Diffuse", ColorRGBA.White); + m.setBoolean("VertexLighting", true); + } + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestTransparentSSAO.java b/jme3-examples/src/main/java/jme3test/post/TestTransparentSSAO.java new file mode 100644 index 000000000..310cc2de1 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestTransparentSSAO.java @@ -0,0 +1,76 @@ +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.ssao.SSAOFilter; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.util.TangentBinormalGenerator; + +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); + TangentBinormalGenerator.generate(geom); + rootNode.attachChild(geom); + + // create the geometry and attach it + Spatial teaGeom = assetManager.loadModel("Models/Tree/Tree.mesh.j3o"); + teaGeom.setQueueBucket(Bucket.Transparent); + teaGeom.setShadowMode(ShadowMode.Cast); + + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(2)); + rootNode.addLight(al); + + DirectionalLight dl1 = new DirectionalLight(); + dl1.setDirection(new Vector3f(1, -1, 1).normalizeLocal()); + dl1.setColor(new ColorRGBA(0.965f, 0.949f, 0.772f, 1f).mult(0.7f)); + rootNode.addLight(dl1); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + dl.setColor(new ColorRGBA(0.965f, 0.949f, 0.772f, 1f).mult(0.7f)); + rootNode.addLight(dl); + + rootNode.attachChild(teaGeom); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + + SSAOFilter ssao = new SSAOFilter();//0.49997783f, 42.598858f, 35.999966f, 0.39299846f + fpp.addFilter(ssao); + + SSAOUI ui = new SSAOUI(inputManager, ssao); + + viewPort.addProcessor(fpp); + } +} diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestDepthFuncChange.java b/jme3-examples/src/main/java/jme3test/renderer/TestDepthFuncChange.java new file mode 100644 index 000000000..1bacf782b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/TestDepthFuncChange.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.renderer; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; + +public class TestDepthFuncChange extends SimpleApplication { + + public static void main(String[] args) { + TestDepthFuncChange app = new TestDepthFuncChange(); + app.start(); + } + + public void simpleInitApp() { + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + flyCam.setMoveSpeed(20); + + + //top of the screen + //default depth func (less or equal) rendering. + //2 cubes, a blue and a red. the red cube is offset by 0.2 WU to the right + //the red cube is put in the transparent bucket to be sure it's rendered after the blue one (but there is no transparency involved). + //You should see a small part of the blue cube on the left and the whole red cube + Box boxshape1 = new Box(1f, 1f, 1f); + Geometry cube1 = new Geometry("box", boxshape1); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Blue); + + cube1.setMaterial(mat); + rootNode.attachChild(cube1); + cube1.move(0, 1.5f, 0); + + Geometry cube2 = cube1.clone(true); + cube2.move(0.2f, 0 , 0); + cube2.setQueueBucket(RenderQueue.Bucket.Transparent); + cube2.getMaterial().setColor("Color", ColorRGBA.Red); + rootNode.attachChild(cube2); + + //Bottom of the screen + //here the 2 cubes are clonned and the depthFunc for the red cube's material is set to Less + //You should see the whole bleu cube and a small part of the red cube on the right + Geometry cube3 = cube1.clone(); + Geometry cube4 = cube2.clone(true); + cube4.getMaterial().getAdditionalRenderState().setDepthFunc(RenderState.TestFunction.Less); + cube3.move(0,-3,0); + cube4.move(0,-3,0); + rootNode.attachChild(cube3); + rootNode.attachChild(cube4); + + //Note that if you move the camera z fighting will occur but that's expected. + + + } +} diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestMultiViews.java b/jme3-examples/src/main/java/jme3test/renderer/TestMultiViews.java new file mode 100644 index 000000000..b098eaddd --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/TestMultiViews.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.renderer; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; + +public class TestMultiViews extends SimpleApplication { + + public static void main(String[] args) { + TestMultiViews app = new TestMultiViews(); + app.start(); + } + + public void simpleInitApp() { + // create the geometry and attach it + Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); + teaGeom.scale(3); + + DirectionalLight dl = new DirectionalLight(); + dl.setColor(ColorRGBA.White); + dl.setDirection(Vector3f.UNIT_XYZ.negate()); + + rootNode.addLight(dl); + rootNode.attachChild(teaGeom); + + // Setup first view + viewPort.setBackgroundColor(ColorRGBA.Blue); + cam.setViewPort(.5f, 1f, 0f, 0.5f); + cam.setLocation(new Vector3f(3.3212643f, 4.484704f, 4.2812433f)); + cam.setRotation(new Quaternion(-0.07680723f, 0.92299235f, -0.2564353f, -0.27645364f)); + + // Setup second view + Camera cam2 = cam.clone(); + cam2.setViewPort(0f, 0.5f, 0f, 0.5f); + cam2.setLocation(new Vector3f(-0.10947256f, 1.5760219f, 4.81758f)); + cam2.setRotation(new Quaternion(0.0010108891f, 0.99857414f, -0.04928594f, 0.020481428f)); + + ViewPort view2 = renderManager.createMainView("Bottom Left", cam2); + view2.setClearFlags(true, true, true); + view2.attachScene(rootNode); + + // Setup third view + Camera cam3 = cam.clone(); + cam3.setViewPort(0f, .5f, .5f, 1f); + cam3.setLocation(new Vector3f(0.2846221f, 6.4271426f, 0.23380789f)); + cam3.setRotation(new Quaternion(0.004381671f, 0.72363687f, -0.69015175f, 0.0045953835f)); + + ViewPort view3 = renderManager.createMainView("Top Left", cam3); + view3.setClearFlags(true, true, true); + view3.attachScene(rootNode); + + // Setup fourth view + Camera cam4 = cam.clone(); + cam4.setViewPort(.5f, 1f, .5f, 1f); + cam4.setLocation(new Vector3f(4.775564f, 1.4548365f, 0.11491505f)); + cam4.setRotation(new Quaternion(0.02356979f, -0.74957186f, 0.026729556f, 0.66096294f)); + + ViewPort view4 = renderManager.createMainView("Top Right", cam4); + view4.setClearFlags(true, true, true); + view4.attachScene(rootNode); + + //test multiview for gui + guiViewPort.getCamera().setViewPort(.5f, 1f, .5f, 1f); + + // Setup second gui view + Camera guiCam2 = guiViewPort.getCamera().clone(); + guiCam2.setViewPort(0f, 0.5f, 0f, 0.5f); + ViewPort guiViewPort2 = renderManager.createPostView("Gui 2", guiCam2); + guiViewPort2.setClearFlags(false, false, false); + guiViewPort2.attachScene(guiViewPort.getScenes().get(0)); + + } +} diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestParallelProjection.java b/jme3-examples/src/main/java/jme3test/renderer/TestParallelProjection.java new file mode 100644 index 000000000..5fc7ab099 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/TestParallelProjection.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.renderer; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; + +public class TestParallelProjection extends SimpleApplication implements AnalogListener { + + private float frustumSize = 1; + + public static void main(String[] args){ + TestParallelProjection app = new TestParallelProjection(); + app.start(); + } + + public void simpleInitApp() { + Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); + + DirectionalLight dl = new DirectionalLight(); + dl.setColor(ColorRGBA.White); + dl.setDirection(Vector3f.UNIT_XYZ.negate()); + + rootNode.addLight(dl); + rootNode.attachChild(teaGeom); + + // Setup first view + cam.setParallelProjection(true); + float aspect = (float) cam.getWidth() / cam.getHeight(); + cam.setFrustum(-1000, 1000, -aspect * frustumSize, aspect * frustumSize, frustumSize, -frustumSize); + + inputManager.addListener(this, "Size+", "Size-"); + inputManager.addMapping("Size+", new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping("Size-", new KeyTrigger(KeyInput.KEY_S)); + } + + public void onAnalog(String name, float value, float tpf) { + // Instead of moving closer/farther to object, we zoom in/out. + if (name.equals("Size-")) + frustumSize += 0.3f * tpf; + else + frustumSize -= 0.3f * tpf; + + float aspect = (float) cam.getWidth() / cam.getHeight(); + cam.setFrustum(-1000, 1000, -aspect * frustumSize, aspect * frustumSize, frustumSize, -frustumSize); + } +} diff --git a/jme3-examples/src/main/java/jme3test/scene/TestSceneLoading.java b/jme3-examples/src/main/java/jme3test/scene/TestSceneLoading.java new file mode 100644 index 000000000..c2af968d2 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/TestSceneLoading.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.scene; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.HttpZipLocator; +import com.jme3.asset.plugins.ZipLocator; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.SkyFactory; +import java.io.File; + +public class TestSceneLoading extends SimpleApplication { + + private Sphere sphereMesh = new Sphere(32, 32, 10, false, true); + private Geometry sphere = new Geometry("Sky", sphereMesh); + private static boolean useHttp = false; + + public static void main(String[] args) { + + TestSceneLoading app = new TestSceneLoading(); + app.start(); + } + + @Override + public void simpleUpdate(float tpf){ + sphere.setLocalTranslation(cam.getLocation()); + } + + public void simpleInitApp() { + this.flyCam.setMoveSpeed(10); + + // load sky + rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false)); + + File file = new File("wildhouse.zip"); + if (!file.exists()) { + useHttp = true; + } + // create the geometry and attach it + // load the level from zip or http zip + if (useHttp) { + assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip", HttpZipLocator.class); + } else { + assetManager.registerLocator("wildhouse.zip", ZipLocator.class); + } + 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/jme3-examples/src/main/java/jme3test/scene/TestUserData.java b/jme3-examples/src/main/java/jme3test/scene/TestUserData.java new file mode 100644 index 000000000..59cf6d374 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/TestUserData.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.scene; + +import com.jme3.app.SimpleApplication; +import com.jme3.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/jme3-examples/src/main/java/jme3test/stress/TestBatchLod.java b/jme3-examples/src/main/java/jme3test/stress/TestBatchLod.java new file mode 100644 index 000000000..08ef772a3 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/stress/TestBatchLod.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.stress; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.control.LodControl; +import jme3tools.optimize.GeometryBatchFactory; + +public class TestBatchLod extends SimpleApplication { + + private boolean lod = false; + + public static void main(String[] args) { + TestBatchLod app = new TestBatchLod(); + app.start(); + } + + public void simpleInitApp() { +// inputManager.registerKeyBinding("USELOD", KeyInput.KEY_L); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + rootNode.addLight(dl); + + Node teapotNode = (Node) assetManager.loadModel("Models/Teapot/Teapot.mesh.xml"); + Geometry teapot = (Geometry) teapotNode.getChild(0); + + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setFloat("Shininess", 16f); + mat.setBoolean("VertexLighting", true); + teapot.setMaterial(mat); + + // show normals as material + //Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); + flyCam.setMoveSpeed(5); + for (int y = -5; y < 5; y++) { + for (int x = -5; x < 5; x++) { + Geometry clonePot = teapot.clone(); + + //clonePot.setMaterial(mat); + clonePot.setLocalTranslation(x * .5f, 0, y * .5f); + clonePot.setLocalScale(.15f); + clonePot.setMaterial(mat); + rootNode.attachChild(clonePot); + } + } + GeometryBatchFactory.optimize(rootNode, true); + LodControl control = new LodControl(); + rootNode.getChild(0).addControl(control); + cam.setLocation(new Vector3f(-1.0748308f, 1.35778f, -1.5380064f)); + cam.setRotation(new Quaternion(0.18343268f, 0.34531063f, -0.069015436f, 0.9177962f)); + + } +} diff --git a/jme3-examples/src/main/java/jme3test/stress/TestLeakingGL.java b/jme3-examples/src/main/java/jme3test/stress/TestLeakingGL.java new file mode 100644 index 000000000..bd2bd485b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/stress/TestLeakingGL.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.stress; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.NativeObjectManager; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Generates 400 new meshes every frame then leaks them. + * Notice how memory usage stays constant and OpenGL objects + * are properly destroyed. + */ +public class TestLeakingGL extends SimpleApplication { + + private Material solidColor; + private Sphere original; + + public static void main(String[] args){ + TestLeakingGL app = new TestLeakingGL(); + app.start(); + } + + public void simpleInitApp() { + original = new Sphere(4, 4, 1); + original.setStatic(); + //original.setInterleaved(); + + // this will make sure all spheres are rendered always + rootNode.setCullHint(CullHint.Never); + solidColor = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); + cam.setLocation(new Vector3f(0, 5, 0)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + + Logger.getLogger(Node.class.getName()).setLevel(Level.WARNING); + Logger.getLogger(NativeObjectManager.class.getName()).setLevel(Level.WARNING); + } + + @Override + public void simpleUpdate(float tpf){ + rootNode.detachAllChildren(); + for (int y = -15; y < 15; y++){ + for (int x = -15; x < 15; x++){ + Mesh sphMesh = original.deepClone(); + Geometry sphere = new Geometry("sphere", sphMesh); + + sphere.setMaterial(solidColor); + sphere.setLocalTranslation(x * 1.5f, 0, y * 1.5f); + rootNode.attachChild(sphere); + } + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java b/jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java new file mode 100644 index 000000000..cf8dbb64b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.stress; + +import com.jme3.animation.AnimChannel; +import com.jme3.animation.AnimControl; +import com.jme3.animation.SkeletonControl; +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +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.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import jme3tools.optimize.LodGenerator; + +public class TestLodGeneration extends SimpleApplication { + + public static void main(String[] args) { + TestLodGeneration app = new TestLodGeneration(); + app.start(); + } + boolean wireFrame = false; + float reductionvalue = 0.0f; + private int lodLevel = 0; + private Node model; + private BitmapText hudText; + private List listGeoms = new ArrayList(); + private ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(5); + private AnimChannel ch; + + public void simpleInitApp() { + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + rootNode.addLight(dl); + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(0.6f)); + rootNode.addLight(al); + + // model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); + model = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o"); + BoundingBox b = ((BoundingBox) model.getWorldBound()); + model.setLocalScale(1.2f / (b.getYExtent() * 2)); + // model.setLocalTranslation(0,-(b.getCenter().y - b.getYExtent())* model.getLocalScale().y, 0); + for (Spatial spatial : model.getChildren()) { + if (spatial instanceof Geometry) { + listGeoms.add((Geometry) spatial); + } + } + ChaseCamera chaseCam = new ChaseCamera(cam, inputManager); + model.addControl(chaseCam); + chaseCam.setLookAtOffset(b.getCenter()); + chaseCam.setDefaultDistance(5); + chaseCam.setMinVerticalRotation(-FastMath.HALF_PI + 0.01f); + chaseCam.setZoomSensitivity(0.5f); + + + +// ch = model.getControl(AnimControl.class).createChannel(); +// ch.setAnim("Wave"); + SkeletonControl c = model.getControl(SkeletonControl.class); + if (c != null) { + c.setEnabled(false); + } + + + reductionvalue = 0.80f; + lodLevel = 1; + for (final Geometry geometry : listGeoms) { + LodGenerator lodGenerator = new LodGenerator(geometry); + lodGenerator.bakeLods(LodGenerator.TriangleReductionMethod.PROPORTIONAL, reductionvalue); + geometry.setLodLevel(lodLevel); + + } + + rootNode.attachChild(model); + flyCam.setEnabled(false); + + + + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + hudText = new BitmapText(guiFont, false); + hudText.setSize(guiFont.getCharSet().getRenderedSize()); + hudText.setText(computeNbTri() + " tris"); + hudText.setLocalTranslation(cam.getWidth() / 2, hudText.getLineHeight(), 0); + guiNode.attachChild(hudText); + + inputManager.addListener(new ActionListener() { + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + if (name.equals("plus")) { +// lodLevel++; +// for (Geometry geometry : listGeoms) { +// if (geometry.getMesh().getNumLodLevels() <= lodLevel) { +// lodLevel = 0; +// } +// geometry.setLodLevel(lodLevel); +// } +// jaimeText.setText(computeNbTri() + " tris"); + + + + reductionvalue += 0.05f; + updateLod(); + + + + } + if (name.equals("minus")) { +// lodLevel--; +// for (Geometry geometry : listGeoms) { +// if (lodLevel < 0) { +// lodLevel = geometry.getMesh().getNumLodLevels() - 1; +// } +// geometry.setLodLevel(lodLevel); +// } +// jaimeText.setText(computeNbTri() + " tris"); + + + + reductionvalue -= 0.05f; + updateLod(); + + + } + if (name.equals("wireFrame")) { + wireFrame = !wireFrame; + for (Geometry geometry : listGeoms) { + geometry.getMaterial().getAdditionalRenderState().setWireframe(wireFrame); + } + } + + } + + } + + private void updateLod() { + reductionvalue = FastMath.clamp(reductionvalue, 0.0f, 1.0f); + makeLod(LodGenerator.TriangleReductionMethod.PROPORTIONAL, reductionvalue, 1); + } + }, "plus", "minus", "wireFrame"); + + inputManager.addMapping("plus", new KeyTrigger(KeyInput.KEY_ADD)); + inputManager.addMapping("minus", new KeyTrigger(KeyInput.KEY_SUBTRACT)); + inputManager.addMapping("wireFrame", new KeyTrigger(KeyInput.KEY_SPACE)); + + + + } + + @Override + public void simpleUpdate(float tpf) { + // model.rotate(0, tpf, 0); + } + + private int computeNbTri() { + int nbTri = 0; + for (Geometry geometry : listGeoms) { + if (geometry.getMesh().getNumLodLevels() > 0) { + nbTri += geometry.getMesh().getLodLevel(lodLevel).getNumElements(); + } else { + nbTri += geometry.getMesh().getTriangleCount(); + } + } + return nbTri; + } + + @Override + public void destroy() { + super.destroy(); + exec.shutdown(); + } + + private void makeLod(final LodGenerator.TriangleReductionMethod method, final float value, final int ll) { + exec.execute(new Runnable() { + public void run() { + for (final Geometry geometry : listGeoms) { + LodGenerator lODGenerator = new LodGenerator(geometry); + final VertexBuffer[] lods = lODGenerator.computeLods(method, value); + + enqueue(new Callable() { + public Void call() throws Exception { + geometry.getMesh().setLodLevels(lods); + lodLevel = 0; + if (geometry.getMesh().getNumLodLevels() > ll) { + lodLevel = ll; + } + geometry.setLodLevel(lodLevel); + hudText.setText(computeNbTri() + " tris"); + return null; + } + }); + } + } + }); + + } +} diff --git a/jme3-examples/src/main/java/jme3test/stress/TestLodStress.java b/jme3-examples/src/main/java/jme3test/stress/TestLodStress.java new file mode 100644 index 000000000..21b8e9d53 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/stress/TestLodStress.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.stress; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.control.LodControl; + +public class TestLodStress extends SimpleApplication { + + public static void main(String[] args){ + TestLodStress app = new TestLodStress(); + app.setShowSettings(false); + app.setPauseOnLostFocus(false); + app.start(); + } + + public void simpleInitApp() { + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1,-1,-1).normalizeLocal()); + rootNode.addLight(dl); + + Node teapotNode = (Node) assetManager.loadModel("Models/Teapot/Teapot.mesh.xml"); + Geometry teapot = (Geometry) teapotNode.getChild(0); + +// Sphere sph = new Sphere(16, 16, 4); +// Geometry teapot = new Geometry("teapot", sph); + + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setFloat("Shininess", 16f); + mat.setBoolean("VertexLighting", true); + teapot.setMaterial(mat); + + // show normals as material + //Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); + + for (int y = -10; y < 10; y++){ + for (int x = -10; x < 10; x++){ + Geometry clonePot = teapot.clone(); + + //clonePot.setMaterial(mat); + clonePot.setLocalTranslation(x * .5f, 0, y * .5f); + clonePot.setLocalScale(.15f); + + LodControl control = new LodControl(); + clonePot.addControl(control); + rootNode.attachChild(clonePot); + } + } + + cam.setLocation(new Vector3f(8.378951f, 5.4324f, 8.795956f)); + cam.setRotation(new Quaternion(-0.083419204f, 0.90370524f, -0.20599906f, -0.36595422f)); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainFractalGridTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainFractalGridTest.java new file mode 100644 index 000000000..0ab647d62 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainFractalGridTest.java @@ -0,0 +1,148 @@ +package jme3test.terrain; + +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.ScreenshotAppState; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.terrain.geomipmap.TerrainGrid; +import com.jme3.terrain.geomipmap.TerrainGridLodControl; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.grid.FractalTileLoader; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.noise.ShaderUtils; +import com.jme3.terrain.noise.basis.FilteredBasis; +import com.jme3.terrain.noise.filter.IterativeFilter; +import com.jme3.terrain.noise.filter.OptimizedErode; +import com.jme3.terrain.noise.filter.PerturbFilter; +import com.jme3.terrain.noise.filter.SmoothFilter; +import com.jme3.terrain.noise.fractal.FractalSum; +import com.jme3.terrain.noise.modulator.NoiseModulator; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; + +public class TerrainFractalGridTest extends SimpleApplication { + + private Material mat_terrain; + private TerrainGrid terrain; + private float grassScale = 64; + private float dirtScale = 16; + private float rockScale = 128; + + public static void main(final String[] args) { + TerrainFractalGridTest app = new TerrainFractalGridTest(); + app.start(); + } + private CharacterControl player3; + private FractalSum base; + private PerturbFilter perturb; + private OptimizedErode therm; + private SmoothFilter smooth; + private IterativeFilter iterate; + + @Override + public void simpleInitApp() { + this.flyCam.setMoveSpeed(100f); + ScreenshotAppState state = new ScreenshotAppState(); + this.stateManager.attach(state); + + // TERRAIN TEXTURE material + this.mat_terrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md"); + + // Parameters to material: + // regionXColorMap: X = 1..4 the texture that should be appliad to state X + // regionX: a Vector3f containing the following information: + // regionX.x: the start height of the region + // regionX.y: the end height of the region + // regionX.z: the texture scale for the region + // it might not be the most elegant way for storing these 3 values, but it packs the data nicely :) + // slopeColorMap: the texture to be used for cliffs, and steep mountain sites + // slopeTileFactor: the texture scale for slopes + // terrainSize: the total size of the terrain (used for scaling the texture) + // GRASS texture + Texture grass = this.assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + this.mat_terrain.setTexture("region1ColorMap", grass); + this.mat_terrain.setVector3("region1", new Vector3f(15, 200, this.grassScale)); + + // DIRT texture + Texture dirt = this.assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + this.mat_terrain.setTexture("region2ColorMap", dirt); + this.mat_terrain.setVector3("region2", new Vector3f(0, 20, this.dirtScale)); + + // ROCK texture + Texture rock = this.assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg"); + rock.setWrap(WrapMode.Repeat); + this.mat_terrain.setTexture("region3ColorMap", rock); + this.mat_terrain.setVector3("region3", new Vector3f(198, 260, this.rockScale)); + + this.mat_terrain.setTexture("region4ColorMap", rock); + this.mat_terrain.setVector3("region4", new Vector3f(198, 260, this.rockScale)); + + this.mat_terrain.setTexture("slopeColorMap", rock); + this.mat_terrain.setFloat("slopeTileFactor", 32); + + this.mat_terrain.setFloat("terrainSize", 513); + + this.base = new FractalSum(); + this.base.setRoughness(0.7f); + this.base.setFrequency(1.0f); + this.base.setAmplitude(1.0f); + this.base.setLacunarity(2.12f); + this.base.setOctaves(8); + this.base.setScale(0.02125f); + this.base.addModulator(new NoiseModulator() { + + @Override + public float value(float... in) { + return ShaderUtils.clamp(in[0] * 0.5f + 0.5f, 0, 1); + } + }); + + FilteredBasis ground = new FilteredBasis(this.base); + + this.perturb = new PerturbFilter(); + this.perturb.setMagnitude(0.119f); + + this.therm = new OptimizedErode(); + this.therm.setRadius(5); + this.therm.setTalus(0.011f); + + this.smooth = new SmoothFilter(); + this.smooth.setRadius(1); + this.smooth.setEffect(0.7f); + + this.iterate = new IterativeFilter(); + this.iterate.addPreFilter(this.perturb); + this.iterate.addPostFilter(this.smooth); + this.iterate.setFilter(this.therm); + this.iterate.setIterations(1); + + ground.addPreFilter(this.iterate); + + this.terrain = new TerrainGrid("terrain", 33, 129, new FractalTileLoader(ground, 256f)); + + this.terrain.setMaterial(this.mat_terrain); + this.terrain.setLocalTranslation(0, 0, 0); + this.terrain.setLocalScale(2f, 1f, 2f); + this.rootNode.attachChild(this.terrain); + + TerrainLodControl control = new TerrainGridLodControl(this.terrain, this.getCamera()); + control.setLodCalculator(new DistanceLodCalculator(33, 2.7f)); // patch size, and a multiplier + this.terrain.addControl(control); + + + + this.getCamera().setLocation(new Vector3f(0, 300, 0)); + + this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)); + + + } + + @Override + public void simpleUpdate(final float tpf) { + } +} diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridAlphaMapTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridAlphaMapTest.java new file mode 100644 index 000000000..6a2167bcd --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridAlphaMapTest.java @@ -0,0 +1,357 @@ +package jme3test.terrain; + +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.ScreenshotAppState; +import com.jme3.asset.plugins.HttpZipLocator; +import com.jme3.asset.plugins.ZipLocator; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.Arrow; +import com.jme3.terrain.geomipmap.TerrainGrid; +import com.jme3.terrain.geomipmap.TerrainGridListener; +import com.jme3.terrain.geomipmap.TerrainGridLodControl; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.grid.FractalTileLoader; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.noise.ShaderUtils; +import com.jme3.terrain.noise.basis.FilteredBasis; +import com.jme3.terrain.noise.filter.IterativeFilter; +import com.jme3.terrain.noise.filter.OptimizedErode; +import com.jme3.terrain.noise.filter.PerturbFilter; +import com.jme3.terrain.noise.filter.SmoothFilter; +import com.jme3.terrain.noise.fractal.FractalSum; +import com.jme3.terrain.noise.modulator.NoiseModulator; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import java.io.File; + +public class TerrainGridAlphaMapTest extends SimpleApplication { + + private TerrainGrid terrain; + private float grassScale = 64; + private float dirtScale = 16; + private float rockScale = 128; + private boolean usePhysics = false; + + public static void main(final String[] args) { + TerrainGridAlphaMapTest app = new TerrainGridAlphaMapTest(); + app.start(); + } + private CharacterControl player3; + private FractalSum base; + private PerturbFilter perturb; + private OptimizedErode therm; + private SmoothFilter smooth; + private IterativeFilter iterate; + private Material material; + private Material matWire; + + @Override + public void simpleInitApp() { + DirectionalLight sun = new DirectionalLight(); + sun.setColor(ColorRGBA.White); + sun.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + rootNode.addLight(sun); + + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(1.3f)); + rootNode.addLight(al); + + File file = new File("TerrainGridTestData.zip"); + if (!file.exists()) { + assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip", HttpZipLocator.class); + } else { + assetManager.registerLocator("TerrainGridTestData.zip", ZipLocator.class); + } + + this.flyCam.setMoveSpeed(100f); + ScreenshotAppState state = new ScreenshotAppState(); + this.stateManager.attach(state); + + // TERRAIN TEXTURE material + material = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + material.setBoolean("useTriPlanarMapping", false); + //material.setBoolean("isTerrainGrid", true); + material.setFloat("Shininess", 0.0f); + + // GRASS texture + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + material.setTexture("DiffuseMap", grass); + material.setFloat("DiffuseMap_0_scale", grassScale); + + // DIRT texture + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + material.setTexture("DiffuseMap_1", dirt); + material.setFloat("DiffuseMap_1_scale", dirtScale); + + // ROCK texture + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + material.setTexture("DiffuseMap_2", rock); + material.setFloat("DiffuseMap_2_scale", rockScale); + + // WIREFRAME material + matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matWire.getAdditionalRenderState().setWireframe(true); + matWire.setColor("Color", ColorRGBA.Green); + + this.base = new FractalSum(); + this.base.setRoughness(0.7f); + this.base.setFrequency(1.0f); + this.base.setAmplitude(1.0f); + this.base.setLacunarity(2.12f); + this.base.setOctaves(8); + this.base.setScale(0.02125f); + this.base.addModulator(new NoiseModulator() { + + @Override + public float value(float... in) { + return ShaderUtils.clamp(in[0] * 0.5f + 0.5f, 0, 1); + } + }); + + FilteredBasis ground = new FilteredBasis(this.base); + + this.perturb = new PerturbFilter(); + this.perturb.setMagnitude(0.119f); + + this.therm = new OptimizedErode(); + this.therm.setRadius(5); + this.therm.setTalus(0.011f); + + this.smooth = new SmoothFilter(); + this.smooth.setRadius(1); + this.smooth.setEffect(0.7f); + + this.iterate = new IterativeFilter(); + this.iterate.addPreFilter(this.perturb); + this.iterate.addPostFilter(this.smooth); + this.iterate.setFilter(this.therm); + this.iterate.setIterations(1); + + ground.addPreFilter(this.iterate); + + this.terrain = new TerrainGrid("terrain", 33, 257, new FractalTileLoader(ground, 256)); + this.terrain.setMaterial(this.material); + + this.terrain.setLocalTranslation(0, 0, 0); + this.terrain.setLocalScale(2f, 1f, 2f); + this.rootNode.attachChild(this.terrain); + + TerrainLodControl control = new TerrainGridLodControl(this.terrain, this.getCamera()); + control.setLodCalculator( new DistanceLodCalculator(33, 2.7f) ); // patch size, and a multiplier + this.terrain.addControl(control); + + final BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + + this.getCamera().setLocation(new Vector3f(0, 256, 0)); + + this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)); + + if (usePhysics) { + CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(0.5f, 1.8f, 1); + player3 = new CharacterControl(capsuleShape, 0.5f); + player3.setJumpSpeed(20); + player3.setFallSpeed(10); + player3.setGravity(10); + + player3.setPhysicsLocation(new Vector3f(cam.getLocation().x, 256, cam.getLocation().z)); + + bulletAppState.getPhysicsSpace().add(player3); + + } + terrain.addListener(new TerrainGridListener() { + + public void gridMoved(Vector3f newCenter) { + } + + public void tileAttached(Vector3f cell, TerrainQuad quad) { + Texture alpha = null; + try { + alpha = assetManager.loadTexture("TerrainAlphaTest/alpha_" + (int)cell.x+ "_" + (int)cell.z + ".png"); + } catch (Exception e) { + alpha = assetManager.loadTexture("TerrainAlphaTest/alpha_default.png"); + } + quad.getMaterial().setTexture("AlphaMap", alpha); + if (usePhysics) { + quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrain.getLocalScale()), 0)); + bulletAppState.getPhysicsSpace().add(quad); + } + updateMarkerElevations(); + } + + public void tileDetached(Vector3f cell, TerrainQuad quad) { + if (usePhysics) { + if (quad.getControl(RigidBodyControl.class) != null) { + bulletAppState.getPhysicsSpace().remove(quad); + quad.removeControl(RigidBodyControl.class); + } + } + updateMarkerElevations(); + } + }); + + this.initKeys(); + + markers = new Node(); + rootNode.attachChild(markers); + createMarkerPoints(1); + } + + Node markers; + + + private void createMarkerPoints(float count) { + Node center = createAxisMarker(10); + markers.attachChild(center); + + float xS = (count-1)*terrain.getTerrainSize() - (terrain.getTerrainSize()/2); + float zS = (count-1)*terrain.getTerrainSize() - (terrain.getTerrainSize()/2); + float xSi = xS; + float zSi = zS; + for (int x=0; x 0) { + CollisionResult hit = results.getClosestCollision(); + if (collisionMarker == null) { + createCollisionMarker(); + } + Vector2f loc = new Vector2f(hit.getContactPoint().x, hit.getContactPoint().z); + float height = terrain.getHeight(loc); + System.out.println("collide " + hit.getContactPoint() + ", height: " + height + ", distance: " + hit.getDistance()); + collisionMarker.setLocalTranslation(new Vector3f(hit.getContactPoint().x, height, hit.getContactPoint().z)); + } + } else if (binding.equals("cameraDown") && !keyPressed) { + getCamera().lookAtDirection(new Vector3f(0, -1, 0), Vector3f.UNIT_Y); + } else if (binding.equals("Lefts") && !keyPressed) { + Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone(); + selectedCollisionObject.move(-0.5f, 0, 0); + testCollision(oldLoc); + } else if (binding.equals("Rights") && !keyPressed) { + Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone(); + selectedCollisionObject.move(0.5f, 0, 0); + testCollision(oldLoc); + } else if (binding.equals("Forwards") && !keyPressed) { + Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone(); + selectedCollisionObject.move(0, 0, 0.5f); + testCollision(oldLoc); + } else if (binding.equals("Backs") && !keyPressed) { + Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone(); + selectedCollisionObject.move(0, 0, -0.5f); + testCollision(oldLoc); + } else if (binding.equals("Ups") && !keyPressed) { + Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone(); + selectedCollisionObject.move(0, 0.5f, 0); + testCollision(oldLoc); + } else if (binding.equals("Downs") && !keyPressed) { + Vector3f oldLoc = selectedCollisionObject.getLocalTranslation().clone(); + selectedCollisionObject.move(0, -0.5f, 0); + testCollision(oldLoc); + } + + } + }; + + private void testCollision(Vector3f oldLoc) { + if (terrain.collideWith(selectedCollisionObject.getWorldBound(), new CollisionResults()) > 0) { + selectedCollisionObject.setLocalTranslation(oldLoc); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestModifyHeight.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestModifyHeight.java new file mode 100644 index 000000000..b68761593 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestModifyHeight.java @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.terrain; + +import com.jme3.app.SimpleApplication; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Ray; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.debug.Arrow; +import com.jme3.scene.shape.Sphere; +import com.jme3.terrain.geomipmap.TerrainGrid; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.grid.FractalTileLoader; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.terrain.noise.ShaderUtils; +import com.jme3.terrain.noise.basis.FilteredBasis; +import com.jme3.terrain.noise.filter.IterativeFilter; +import com.jme3.terrain.noise.filter.OptimizedErode; +import com.jme3.terrain.noise.filter.PerturbFilter; +import com.jme3.terrain.noise.filter.SmoothFilter; +import com.jme3.terrain.noise.fractal.FractalSum; +import com.jme3.terrain.noise.modulator.NoiseModulator; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Brent Owens + */ +public class TerrainTestModifyHeight extends SimpleApplication { + + private TerrainQuad terrain; + Material matTerrain; + Material matWire; + boolean wireframe = true; + boolean triPlanar = false; + boolean wardiso = false; + boolean minnaert = false; + protected BitmapText hintText; + private float grassScale = 64; + private float dirtScale = 16; + private float rockScale = 128; + + private boolean raiseTerrain = false; + private boolean lowerTerrain = false; + + private Geometry marker; + private Geometry markerNormal; + + public static void main(String[] args) { + TerrainTestModifyHeight app = new TerrainTestModifyHeight(); + app.start(); + } + + @Override + public void simpleUpdate(float tpf){ + Vector3f intersection = getWorldIntersection(); + updateHintText(intersection); + + if (raiseTerrain){ + + if (intersection != null) { + adjustHeight(intersection, 64, tpf * 60); + } + }else if (lowerTerrain){ + if (intersection != null) { + adjustHeight(intersection, 64, -tpf * 60); + } + } + + if (terrain != null && intersection != null) { + float h = terrain.getHeight(new Vector2f(intersection.x, intersection.z)); + Vector3f tl = terrain.getWorldTranslation(); + marker.setLocalTranslation(tl.add(new Vector3f(intersection.x, h, intersection.z)) ); + markerNormal.setLocalTranslation(tl.add(new Vector3f(intersection.x, h, intersection.z)) ); + + Vector3f normal = terrain.getNormal(new Vector2f(intersection.x, intersection.z)); + ((Arrow)markerNormal.getMesh()).setArrowExtent(normal); + } + } + + @Override + public void simpleInitApp() { + loadHintText(); + initCrossHairs(); + setupKeys(); + + createMarker(); + + // WIREFRAME material + matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matWire.getAdditionalRenderState().setWireframe(true); + matWire.setColor("Color", ColorRGBA.Green); + + createTerrain(); + //createTerrainGrid(); + + DirectionalLight light = new DirectionalLight(); + light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize()); + rootNode.addLight(light); + + AmbientLight ambLight = new AmbientLight(); + ambLight.setColor(new ColorRGBA(1f, 1f, 0.8f, 0.2f)); + rootNode.addLight(ambLight); + + cam.setLocation(new Vector3f(0, 256, 0)); + cam.lookAtDirection(new Vector3f(0, -1f, 0).normalizeLocal(), Vector3f.UNIT_X); + } + + public void loadHintText() { + hintText = new BitmapText(guiFont, false); + hintText.setLocalTranslation(0, getCamera().getHeight(), 0); + hintText.setText("Hit 1 to raise terrain, hit 2 to lower terrain"); + guiNode.attachChild(hintText); + } + + public void updateHintText(Vector3f target) { + int x = (int) getCamera().getLocation().x; + int y = (int) getCamera().getLocation().y; + int z = (int) getCamera().getLocation().z; + String targetText = ""; + if (target!= null) + targetText = " intersect: "+target.toString(); + hintText.setText("Press left mouse button to raise terrain, press right mouse button to lower terrain. " + x + "," + y + "," + z+targetText); + } + + protected void initCrossHairs() { + BitmapText ch = new BitmapText(guiFont, false); + ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); + ch.setText("+"); // crosshairs + ch.setLocalTranslation( // center + settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2, + settings.getHeight() / 2 + ch.getLineHeight() / 2, 0); + guiNode.attachChild(ch); + } + + private void setupKeys() { + flyCam.setMoveSpeed(100); + inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addListener(actionListener, "wireframe"); + inputManager.addMapping("Raise", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(actionListener, "Raise"); + inputManager.addMapping("Lower", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); + inputManager.addListener(actionListener, "Lower"); + } + private ActionListener actionListener = new ActionListener() { + + public void onAction(String name, boolean pressed, float tpf) { + if (name.equals("wireframe") && !pressed) { + wireframe = !wireframe; + if (!wireframe) { + terrain.setMaterial(matWire); + } else { + terrain.setMaterial(matTerrain); + } + } else if (name.equals("Raise")) { + raiseTerrain = pressed; + } else if (name.equals("Lower")) { + lowerTerrain = pressed; + } + } + }; + + private void adjustHeight(Vector3f loc, float radius, float height) { + + // offset it by radius because in the loop we iterate through 2 radii + int radiusStepsX = (int) (radius / terrain.getLocalScale().x); + int radiusStepsZ = (int) (radius / terrain.getLocalScale().z); + + float xStepAmount = terrain.getLocalScale().x; + float zStepAmount = terrain.getLocalScale().z; + long start = System.currentTimeMillis(); + List locs = new ArrayList(); + List heights = new ArrayList(); + + for (int z = -radiusStepsZ; z < radiusStepsZ; z++) { + for (int x = -radiusStepsX; x < radiusStepsX; x++) { + + float locX = loc.x + (x * xStepAmount); + float locZ = loc.z + (z * zStepAmount); + + if (isInRadius(locX - loc.x, locZ - loc.z, radius)) { + // see if it is in the radius of the tool + float h = calculateHeight(radius, height, locX - loc.x, locZ - loc.z); + locs.add(new Vector2f(locX, locZ)); + heights.add(h); + } + } + } + + terrain.adjustHeight(locs, heights); + //System.out.println("Modified "+locs.size()+" points, took: " + (System.currentTimeMillis() - start)+" ms"); + terrain.updateModelBound(); + } + + private boolean isInRadius(float x, float y, float radius) { + Vector2f point = new Vector2f(x, y); + // return true if the distance is less than equal to the radius + return point.length() <= radius; + } + + private float calculateHeight(float radius, float heightFactor, float x, float z) { + // find percentage for each 'unit' in radius + Vector2f point = new Vector2f(x, z); + float val = point.length() / radius; + val = 1 - val; + if (val <= 0) { + val = 0; + } + return heightFactor * val; + } + + private Vector3f getWorldIntersection() { + Vector3f origin = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.0f); + Vector3f direction = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.3f); + direction.subtractLocal(origin).normalizeLocal(); + + Ray ray = new Ray(origin, direction); + CollisionResults results = new CollisionResults(); + int numCollisions = terrain.collideWith(ray, results); + if (numCollisions > 0) { + CollisionResult hit = results.getClosestCollision(); + return hit.getContactPoint(); + } + return null; + } + + private void createTerrain() { + // First, we load up our textures and the heightmap texture for the terrain + + // TERRAIN TEXTURE material + matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matTerrain.setBoolean("useTriPlanarMapping", false); + matTerrain.setBoolean("WardIso", true); + matTerrain.setFloat("Shininess", 0); + + // ALPHA map (for splat textures) + matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); + + // GRASS texture + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap", grass); + matTerrain.setFloat("DiffuseMap_0_scale", grassScale); + + // DIRT texture + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_1", dirt); + matTerrain.setFloat("DiffuseMap_1_scale", dirtScale); + + // ROCK texture + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_2", rock); + matTerrain.setFloat("DiffuseMap_2_scale", rockScale); + + // HEIGHTMAP image (for the terrain heightmap) + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.5f); + heightmap.load(); + heightmap.smooth(0.9f, 1); + + } catch (Exception e) { + e.printStackTrace(); + } + + // CREATE THE TERRAIN + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); + control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier + terrain.addControl(control); + terrain.setMaterial(matTerrain); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(2.5f, 0.5f, 2.5f); + rootNode.attachChild(terrain); + } + + private void createTerrainGrid() { + + // TERRAIN TEXTURE material + matTerrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md"); + + // Parameters to material: + // regionXColorMap: X = 1..4 the texture that should be appliad to state X + // regionX: a Vector3f containing the following information: + // regionX.x: the start height of the region + // regionX.y: the end height of the region + // regionX.z: the texture scale for the region + // it might not be the most elegant way for storing these 3 values, but it packs the data nicely :) + // slopeColorMap: the texture to be used for cliffs, and steep mountain sites + // slopeTileFactor: the texture scale for slopes + // terrainSize: the total size of the terrain (used for scaling the texture) + // GRASS texture + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matTerrain.setTexture("region1ColorMap", grass); + matTerrain.setVector3("region1", new Vector3f(88, 200, this.grassScale)); + + // DIRT texture + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matTerrain.setTexture("region2ColorMap", dirt); + matTerrain.setVector3("region2", new Vector3f(0, 90, this.dirtScale)); + + // ROCK texture + Texture rock = assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg"); + rock.setWrap(WrapMode.Repeat); + matTerrain.setTexture("region3ColorMap", rock); + matTerrain.setVector3("region3", new Vector3f(198, 260, this.rockScale)); + + matTerrain.setTexture("region4ColorMap", rock); + matTerrain.setVector3("region4", new Vector3f(198, 260, this.rockScale)); + + matTerrain.setTexture("slopeColorMap", rock); + matTerrain.setFloat("slopeTileFactor", 32); + + matTerrain.setFloat("terrainSize", 513); + + FractalSum base = new FractalSum(); + base.setRoughness(0.7f); + base.setFrequency(1.0f); + base.setAmplitude(1.0f); + base.setLacunarity(2.12f); + base.setOctaves(8); + base.setScale(0.02125f); + base.addModulator(new NoiseModulator() { + @Override + public float value(float... in) { + return ShaderUtils.clamp(in[0] * 0.5f + 0.5f, 0, 1); + } + }); + + FilteredBasis ground = new FilteredBasis(base); + + PerturbFilter perturb = new PerturbFilter(); + perturb.setMagnitude(0.119f); + + OptimizedErode therm = new OptimizedErode(); + therm.setRadius(5); + therm.setTalus(0.011f); + + SmoothFilter smooth = new SmoothFilter(); + smooth.setRadius(1); + smooth.setEffect(0.7f); + + IterativeFilter iterate = new IterativeFilter(); + iterate.addPreFilter(perturb); + iterate.addPostFilter(smooth); + iterate.setFilter(therm); + iterate.setIterations(1); + + ground.addPreFilter(iterate); + + this.terrain = new TerrainGrid("terrain", 65, 257, new FractalTileLoader(ground, 256f)); + + + terrain.setMaterial(matTerrain); + terrain.setLocalTranslation(0, 0, 0); + terrain.setLocalScale(2f, 1f, 2f); + + rootNode.attachChild(this.terrain); + + TerrainLodControl control = new TerrainLodControl(this.terrain, getCamera()); + this.terrain.addControl(control); + } + + private void createMarker() { + // collision marker + Sphere sphere = new Sphere(8, 8, 0.5f); + marker = new Geometry("Marker"); + marker.setMesh(sphere); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", new ColorRGBA(251f/255f, 130f/255f, 0f, 0.6f)); + mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + + marker.setMaterial(mat); + rootNode.attachChild(marker); + + + // surface normal marker + Arrow arrow = new Arrow(new Vector3f(0,1,0)); + markerNormal = new Geometry("MarkerNormal"); + markerNormal.setMesh(arrow); + markerNormal.setMaterial(mat); + rootNode.attachChild(markerNormal); + } +} diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestReadWrite.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestReadWrite.java new file mode 100644 index 000000000..b6bba1c82 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestReadWrite.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.terrain; + +import com.jme3.app.SimpleApplication; +import com.jme3.export.Savable; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.terrain.Terrain; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import java.io.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Saves and loads terrain. + * + * @author Brent Owens + */ +public class TerrainTestReadWrite extends SimpleApplication { + + private Terrain terrain; + protected BitmapText hintText; + private float grassScale = 64; + private float dirtScale = 16; + private float rockScale = 128; + private Material matTerrain; + private Material matWire; + + public static void main(String[] args) { + TerrainTestReadWrite app = new TerrainTestReadWrite(); + app.start(); + //testHeightmapBuilding(); + } + + @Override + public void initialize() { + super.initialize(); + + loadHintText(); + } + + @Override + public void simpleInitApp() { + + + createControls(); + createMap(); + } + + private void createMap() { + matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matTerrain.setBoolean("useTriPlanarMapping", false); + matTerrain.setBoolean("WardIso", true); + + // ALPHA map (for splat textures) + matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); + + // HEIGHTMAP image (for the terrain heightmap) + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); + + // GRASS texture + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap", grass); + matTerrain.setFloat("DiffuseMap_0_scale", grassScale); + + + // DIRT texture + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_1", dirt); + matTerrain.setFloat("DiffuseMap_1_scale", dirtScale); + + // ROCK texture + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_2", rock); + matTerrain.setFloat("DiffuseMap_2_scale", rockScale); + + + Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); + normalMap0.setWrap(WrapMode.Repeat); + Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); + normalMap1.setWrap(WrapMode.Repeat); + Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); + normalMap2.setWrap(WrapMode.Repeat); + matTerrain.setTexture("NormalMap", normalMap0); + matTerrain.setTexture("NormalMap_1", normalMap2); + matTerrain.setTexture("NormalMap_2", normalMap2); + + matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matWire.getAdditionalRenderState().setWireframe(true); + matWire.setColor("Color", ColorRGBA.Green); + + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f); + heightmap.load(); + + } catch (Exception e) { + e.printStackTrace(); + } + + if (new File("terrainsave.jme").exists()) { + loadTerrain(); + } else { + // create the terrain as normal, and give it a control for LOD management + TerrainQuad terrainQuad = new TerrainQuad("terrain", 65, 129, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations + TerrainLodControl control = new TerrainLodControl(terrainQuad, getCamera()); + control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier + terrainQuad.addControl(control); + terrainQuad.setMaterial(matTerrain); + terrainQuad.setLocalTranslation(0, -100, 0); + terrainQuad.setLocalScale(4f, 0.25f, 4f); + rootNode.attachChild(terrainQuad); + + this.terrain = terrainQuad; + } + + DirectionalLight light = new DirectionalLight(); + light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize()); + rootNode.addLight(light); + } + + /** + * Create the save and load actions and add them to the input listener + */ + private void createControls() { + flyCam.setMoveSpeed(50); + cam.setLocation(new Vector3f(0, 100, 0)); + + inputManager.addMapping("save", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addListener(saveActionListener, "save"); + + inputManager.addMapping("load", new KeyTrigger(KeyInput.KEY_Y)); + inputManager.addListener(loadActionListener, "load"); + + inputManager.addMapping("clone", new KeyTrigger(KeyInput.KEY_C)); + inputManager.addListener(cloneActionListener, "clone"); + } + + public void loadHintText() { + hintText = new BitmapText(guiFont, false); + hintText.setSize(guiFont.getCharSet().getRenderedSize()); + hintText.setLocalTranslation(0, getCamera().getHeight(), 0); + hintText.setText("Hit T to save, and Y to load"); + guiNode.attachChild(hintText); + } + private ActionListener saveActionListener = new ActionListener() { + + public void onAction(String name, boolean pressed, float tpf) { + if (name.equals("save") && !pressed) { + + FileOutputStream fos = null; + try { + long start = System.currentTimeMillis(); + fos = new FileOutputStream(new File("terrainsave.jme")); + + // we just use the exporter and pass in the terrain + BinaryExporter.getInstance().save((Savable)terrain, new BufferedOutputStream(fos)); + + fos.flush(); + float duration = (System.currentTimeMillis() - start) / 1000.0f; + System.out.println("Save took " + duration + " seconds"); + } catch (IOException ex) { + Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, ex); + } finally { + try { + if (fos != null) { + fos.close(); + } + } catch (IOException e) { + Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, e); + } + } + } + } + }; + + private void loadTerrain() { + FileInputStream fis = null; + try { + long start = System.currentTimeMillis(); + // remove the existing terrain and detach it from the root node. + if (terrain != null) { + Node existingTerrain = (Node)terrain; + existingTerrain.removeFromParent(); + existingTerrain.removeControl(TerrainLodControl.class); + existingTerrain.detachAllChildren(); + terrain = null; + } + + // import the saved terrain, and attach it back to the root node + File f = new File("terrainsave.jme"); + fis = new FileInputStream(f); + BinaryImporter imp = BinaryImporter.getInstance(); + imp.setAssetManager(assetManager); + terrain = (TerrainQuad) imp.load(new BufferedInputStream(fis)); + rootNode.attachChild((Node)terrain); + + float duration = (System.currentTimeMillis() - start) / 1000.0f; + System.out.println("Load took " + duration + " seconds"); + + // now we have to add back the camera to the LOD control + TerrainLodControl lodControl = ((Node)terrain).getControl(TerrainLodControl.class); + if (lodControl != null) + lodControl.setCamera(getCamera()); + + } catch (IOException ex) { + Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, ex); + } finally { + try { + if (fis != null) { + fis.close(); + } + } catch (IOException ex) { + Logger.getLogger(TerrainTestReadWrite.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + private ActionListener loadActionListener = new ActionListener() { + + public void onAction(String name, boolean pressed, float tpf) { + if (name.equals("load") && !pressed) { + loadTerrain(); + } + } + }; + private ActionListener cloneActionListener = new ActionListener() { + + public void onAction(String name, boolean pressed, float tpf) { + if (name.equals("clone") && !pressed) { + + Terrain clone = (Terrain) ((Node)terrain).clone(); + ((Node)terrain).removeFromParent(); + terrain = clone; + getRootNode().attachChild((Node)terrain); + } + } + }; + + // no junit tests, so this has to be hand-tested: + private static void testHeightmapBuilding() { + int s = 9; + int b = 3; + float[] hm = new float[s * s]; + for (int i = 0; i < s; i++) { + for (int j = 0; j < s; j++) { + hm[(i * s) + j] = i * j; + } + } + + for (int i = 0; i < s; i++) { + for (int j = 0; j < s; j++) { + System.out.print(hm[i * s + j] + " "); + } + System.out.println(""); + } + + TerrainQuad terrain = new TerrainQuad("terrain", b, s, hm); + float[] hm2 = terrain.getHeightMap(); + boolean failed = false; + for (int i = 0; i < s * s; i++) { + if (hm[i] != hm2[i]) { + failed = true; + } + } + + System.out.println(""); + if (failed) { + System.out.println("Terrain heightmap building FAILED!!!"); + for (int i = 0; i < s; i++) { + for (int j = 0; j < s; j++) { + System.out.print(hm2[i * s + j] + " "); + } + System.out.println(""); + } + } else { + System.out.println("Terrain heightmap building PASSED"); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestTile.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestTile.java new file mode 100644 index 000000000..4e093e80c --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestTile.java @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.terrain; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Sphere; +import com.jme3.terrain.ProgressMonitor; +import com.jme3.terrain.Terrain; +import com.jme3.terrain.geomipmap.MultiTerrainLodControl; +import com.jme3.terrain.geomipmap.NeighbourFinder; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import java.util.List; + +/** + * Demonstrates the NeighbourFinder interface for TerrainQuads, + * allowing you to tile terrains together without having to use + * TerrainGrid. It also introduces the MultiTerrainLodControl that + * will seam the edges of all the terrains supplied. + * + * @author sploreg + */ +public class TerrainTestTile extends SimpleApplication { + + private TiledTerrain terrain; + Material matTerrain; + Material matWire; + boolean wireframe = true; + boolean triPlanar = false; + boolean wardiso = false; + boolean minnaert = false; + protected BitmapText hintText; + private float grassScale = 256; + + + public static void main(String[] args) { + TerrainTestTile app = new TerrainTestTile(); + app.start(); + } + + + + @Override + public void simpleInitApp() { + loadHintText(); + setupKeys(); + + // WIREFRAME material + matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matWire.getAdditionalRenderState().setWireframe(true); + matWire.setColor("Color", ColorRGBA.Green); + + terrain = new TiledTerrain(); + 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); + + cam.setLocation(new Vector3f(0, 256, 0)); + cam.lookAtDirection(new Vector3f(0, -1, -1).normalizeLocal(), Vector3f.UNIT_Y); + + + Sphere s = new Sphere(12, 12, 3); + Geometry g = new Geometry("marker"); + g.setMesh(s); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Red); + g.setMaterial(mat); + g.setLocalTranslation(0, -100, 0); + rootNode.attachChild(g); + + Geometry g2 = new Geometry("marker"); + g2.setMesh(s); + mat.setColor("Color", ColorRGBA.Red); + g2.setMaterial(mat); + g2.setLocalTranslation(10, -100, 0); + rootNode.attachChild(g2); + + Geometry g3 = new Geometry("marker"); + g3.setMesh(s); + mat.setColor("Color", ColorRGBA.Red); + g3.setMaterial(mat); + g3.setLocalTranslation(0, -100, 10); + rootNode.attachChild(g3); + } + + public void loadHintText() { + hintText = new BitmapText(guiFont, false); + hintText.setLocalTranslation(0, getCamera().getHeight(), 0); + hintText.setText("Hit 'T' to toggle wireframe"); + guiNode.attachChild(hintText); + } + + + private void setupKeys() { + flyCam.setMoveSpeed(100); + inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addListener(actionListener, "wireframe"); + } + 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); + } + } + } + }; + + /** + * A sample class (node in this case) that demonstrates + * the use of NeighbourFinder. + * It just links up the left,right,top,bottom TerrainQuads + * so LOD can work. + * It does not implement many of the Terrain interface's methods, + * you will want to do that for your own implementations. + */ + private class TiledTerrain extends Node implements Terrain, NeighbourFinder { + + private TerrainQuad terrain1; + private TerrainQuad terrain2; + private TerrainQuad terrain3; + private TerrainQuad terrain4; + + TiledTerrain() { + // TERRAIN TEXTURE material + matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matTerrain.setBoolean("useTriPlanarMapping", false); + matTerrain.setBoolean("WardIso", true); + matTerrain.setFloat("Shininess", 0); + + // 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); + + // CREATE THE TERRAIN + terrain1 = new TerrainQuad("terrain 1", 65, 513, null); + terrain1.setMaterial(matTerrain); + terrain1.setLocalTranslation(-256, -100, -256); + terrain1.setLocalScale(1f, 1f, 1f); + this.attachChild(terrain1); + + terrain2 = new TerrainQuad("terrain 2", 65, 513, null); + terrain2.setMaterial(matTerrain); + terrain2.setLocalTranslation(-256, -100, 256); + terrain2.setLocalScale(1f, 1f, 1f); + this.attachChild(terrain2); + + terrain3 = new TerrainQuad("terrain 3", 65, 513, null); + terrain3.setMaterial(matTerrain); + terrain3.setLocalTranslation(256, -100, -256); + terrain3.setLocalScale(1f, 1f, 1f); + this.attachChild(terrain3); + + terrain4 = new TerrainQuad("terrain 4", 65, 513, null); + terrain4.setMaterial(matTerrain); + terrain4.setLocalTranslation(256, -100, 256); + terrain4.setLocalScale(1f, 1f, 1f); + this.attachChild(terrain4); + + terrain1.setNeighbourFinder(this); + terrain2.setNeighbourFinder(this); + terrain3.setNeighbourFinder(this); + terrain4.setNeighbourFinder(this); + + MultiTerrainLodControl lodControl = new MultiTerrainLodControl(getCamera()); + lodControl.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier + lodControl.addTerrain(terrain1); + lodControl.addTerrain(terrain2); + lodControl.addTerrain(terrain3);// order of these seems to matter + lodControl.addTerrain(terrain4); + this.addControl(lodControl); + + } + + /** + * 1 3 + * 2 4 + */ + public TerrainQuad getRightQuad(TerrainQuad center) { + //System.out.println("lookup neighbour"); + if (center == terrain1) + return terrain3; + if (center == terrain2) + return terrain4; + + return null; + } + + /** + * 1 3 + * 2 4 + */ + public TerrainQuad getLeftQuad(TerrainQuad center) { + //System.out.println("lookup neighbour"); + if (center == terrain3) + return terrain1; + if (center == terrain4) + return terrain2; + + return null; + } + + /** + * 1 3 + * 2 4 + */ + public TerrainQuad getTopQuad(TerrainQuad center) { + //System.out.println("lookup neighbour"); + if (center == terrain2) + return terrain1; + if (center == terrain4) + return terrain3; + + return null; + } + + /** + * 1 3 + * 2 4 + */ + public TerrainQuad getDownQuad(TerrainQuad center) { + //System.out.println("lookup neighbour"); + if (center == terrain1) + return terrain2; + if (center == terrain3) + return terrain4; + + return null; + } + + public float getHeight(Vector2f xz) { + // you will have to offset the coordinate for each terrain, to center on it + throw new UnsupportedOperationException("Not supported yet."); + } + + public Vector3f getNormal(Vector2f xz) { + // you will have to offset the coordinate for each terrain, to center on it + throw new UnsupportedOperationException("Not supported yet."); + } + + public float getHeightmapHeight(Vector2f xz) { + // you will have to offset the coordinate for each terrain, to center on it + throw new UnsupportedOperationException("Not supported yet."); + } + + public void setHeight(Vector2f xzCoordinate, float height) { + // you will have to offset the coordinate for each terrain, to center on it + throw new UnsupportedOperationException("Not supported yet."); + } + + public void setHeight(List xz, List height) { + // you will have to offset the coordinate for each terrain, to center on it + throw new UnsupportedOperationException("Not supported yet."); + } + + public void adjustHeight(Vector2f xzCoordinate, float delta) { + // you will have to offset the coordinate for each terrain, to center on it + throw new UnsupportedOperationException("Not supported yet."); + } + + public void adjustHeight(List xz, List height) { + // you will have to offset the coordinate for each terrain, to center on it + throw new UnsupportedOperationException("Not supported yet."); + } + + public float[] getHeightMap() { + throw new UnsupportedOperationException("Not supported yet."); + } + + public int getMaxLod() { + throw new UnsupportedOperationException("Not supported yet."); + } + + public void setLocked(boolean locked) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public void generateEntropy(ProgressMonitor monitor) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public Material getMaterial() { + throw new UnsupportedOperationException("Not supported yet."); + } + + public Material getMaterial(Vector3f worldLocation) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public int getTerrainSize() { + throw new UnsupportedOperationException("Not supported yet."); + } + + public int getNumMajorSubdivisions() { + throw new UnsupportedOperationException("Not supported yet."); + } + + + + } +} diff --git a/jme3-examples/src/main/java/jme3test/texture/TestImageRaster.java b/jme3-examples/src/main/java/jme3test/texture/TestImageRaster.java new file mode 100644 index 000000000..da27ddf40 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/texture/TestImageRaster.java @@ -0,0 +1,167 @@ +package jme3test.texture; + + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.font.Rectangle; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; +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.texture.image.ImageRaster; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; + +public class TestImageRaster extends SimpleApplication { + + private Image convertImage(Image image, Format newFormat) { + int width = image.getWidth(); + int height = image.getHeight(); + ByteBuffer data = BufferUtils.createByteBuffer( (int)Math.ceil(newFormat.getBitsPerPixel() / 8.0) * width * height); + Image convertedImage = new Image(newFormat, width, height, data); + + ImageRaster sourceReader = ImageRaster.create(image); + ImageRaster targetWriter = ImageRaster.create(convertedImage); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + ColorRGBA color = sourceReader.getPixel(x, y); + targetWriter.setPixel(x, y, color); + } + } + + return convertedImage; + } + + private void convertAndPutImage(Image image, float posX, float posY) { + Texture tex = new Texture2D(image); + tex.setMagFilter(MagFilter.Nearest); + tex.setMinFilter(MinFilter.NearestNoMipMaps); + tex.setAnisotropicFilter(16); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", tex); + + Quad q = new Quad(5, 5); + Geometry g = new Geometry("quad", q); + g.setLocalTranslation(posX, posY - 5, -0.0001f); + g.setMaterial(mat); + rootNode.attachChild(g); + + BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText txt = new BitmapText(fnt); + txt.setBox(new Rectangle(0, 0, 5, 5)); + txt.setQueueBucket(RenderQueue.Bucket.Transparent); + txt.setSize(0.5f); + txt.setText(image.getFormat().name()); + txt.setLocalTranslation(posX, posY, 0); + rootNode.attachChild(txt); + } + + private Image createTestImage() { + Image testImage = new Image(Format.BGR8, 4, 3, BufferUtils.createByteBuffer(4 * 4 * 3)); + + ImageRaster io = ImageRaster.create(testImage); + io.setPixel(0, 0, ColorRGBA.Black); + io.setPixel(1, 0, ColorRGBA.Gray); + io.setPixel(2, 0, ColorRGBA.White); + io.setPixel(3, 0, ColorRGBA.White.mult(4)); // HDR color + + io.setPixel(0, 1, ColorRGBA.Red); + io.setPixel(1, 1, ColorRGBA.Green); + io.setPixel(2, 1, ColorRGBA.Blue); + io.setPixel(3, 1, new ColorRGBA(0, 0, 0, 0)); + + io.setPixel(0, 2, ColorRGBA.Yellow); + io.setPixel(1, 2, ColorRGBA.Magenta); + io.setPixel(2, 2, ColorRGBA.Cyan); + io.setPixel(3, 2, new ColorRGBA(1, 1, 1, 0)); + + return testImage; + } + + @Override + public void simpleInitApp() { + cam.setLocation(new Vector3f(16, 6, 36)); + flyCam.setMoveSpeed(10); + + Texture tex = assetManager.loadTexture("com/jme3/app/Monkey.png"); +// Texture tex = assetManager.loadTexture("Textures/HdrTest/Memorial.hdr"); + Image originalImage = tex.getImage(); + + Image image = convertImage(originalImage, Format.RGBA32F); + convertAndPutImage(image, 0, 0); + + image = convertImage(image, Format.RGB32F); + convertAndPutImage(image, 5, 0); + + image = convertImage(image, Format.RGBA16F); + convertAndPutImage(image, 10, 0); + + image = convertImage(image, Format.RGB16F); + convertAndPutImage(image, 15, 0); + + image = convertImage(image, Format.RGB16F_to_RGB9E5); + convertAndPutImage(image, 20, 0); + + image = convertImage(image, Format.RGB16F_to_RGB111110F); + convertAndPutImage(image, 25, 0); + + image = convertImage(image, Format.RGBA16); + convertAndPutImage(image, 0, 5); + + image = convertImage(image, Format.RGB16); + convertAndPutImage(image, 5, 5); + + image = convertImage(image, Format.RGBA8); + convertAndPutImage(image, 10, 5); + + image = convertImage(image, Format.RGB8); + convertAndPutImage(image, 15, 5); + + image = convertImage(image, Format.ABGR8); + convertAndPutImage(image, 20, 5); + + image = convertImage(image, Format.BGR8); + convertAndPutImage(image, 25, 5); + + image = convertImage(image, Format.RGB5A1); + convertAndPutImage(image, 0, 10); + + image = convertImage(image, Format.ARGB4444); + convertAndPutImage(image, 5, 10); + + image = convertImage(image, Format.Luminance32F); + convertAndPutImage(image, 0, 15); + + image = convertImage(image, Format.Luminance16FAlpha16F); + convertAndPutImage(image, 5, 15); + + image = convertImage(image, Format.Luminance16F); + convertAndPutImage(image, 10, 15); + + image = convertImage(image, Format.Luminance16Alpha16); + convertAndPutImage(image, 15, 15); + + image = convertImage(image, Format.Luminance16); + convertAndPutImage(image, 20, 15); + + image = convertImage(image, Format.Luminance8Alpha8); + convertAndPutImage(image, 25, 15); + + image = convertImage(image, Format.Luminance8); + convertAndPutImage(image, 30, 15); + } + + public static void main(String[] args) { + TestImageRaster app = new TestImageRaster(); + app.start(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/texture/TestSkyLoading.java b/jme3-examples/src/main/java/jme3test/texture/TestSkyLoading.java new file mode 100644 index 000000000..7991bd067 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/texture/TestSkyLoading.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/java/jme3test/texture/TestTexture3D.java b/jme3-examples/src/main/java/jme3test/texture/TestTexture3D.java new file mode 100644 index 000000000..7b0ffc104 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/texture/TestTexture3D.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.texture; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.shape.Sphere; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture3D; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; + +public class TestTexture3D extends SimpleApplication { + + public static void main(String[] args) { + TestTexture3D app = new TestTexture3D(); + app.start(); + } + + @Override + public void simpleInitApp() { + //mouseInput.setCursorVisible(true); + flyCam.setMoveSpeed(10); + //creating a sphere + Sphere sphere = new Sphere(32, 32, 1); + //getting the boundingbox + sphere.updateBound(); + BoundingBox bb = (BoundingBox) sphere.getBound(); + Vector3f min = bb.getMin(null); + float[] ext = new float[]{bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2}; + //we need to change the UV coordinates (the sphere is assumet to be inside the 3D image box) + sphere.clearBuffer(Type.TexCoord); + VertexBuffer vb = sphere.getBuffer(Type.Position); + FloatBuffer fb = (FloatBuffer) vb.getData(); + float[] uvCoordinates = BufferUtils.getFloatArray(fb); + //now transform the coordinates so that they are in the range of <0; 1> + for (int i = 0; i < uvCoordinates.length; i += 3) { + uvCoordinates[i] = (uvCoordinates[i] - min.x) / ext[0]; + uvCoordinates[i + 1] = (uvCoordinates[i + 1] - min.y) / ext[1]; + uvCoordinates[i + 2] = (uvCoordinates[i + 2] - min.z) / ext[2]; + } + //apply new texture coordinates + VertexBuffer uvCoordsBuffer = new VertexBuffer(Type.TexCoord); + uvCoordsBuffer.setupData(Usage.Static, 3, com.jme3.scene.VertexBuffer.Format.Float, + BufferUtils.createFloatBuffer(uvCoordinates)); + sphere.setBuffer(uvCoordsBuffer); + //create geometry, and apply material and our 3D texture + Geometry g = new Geometry("sphere", sphere); + Material material = new Material(assetManager, "jme3test/texture/tex3D.j3md"); + try { + Texture texture = this.getTexture(); + material.setTexture("Texture", texture); + } catch (IOException e) { + e.printStackTrace(); + } + g.setMaterial(material); + rootNode.attachChild(g); + //add some light so that it is visible + PointLight light = new PointLight(); + light.setColor(ColorRGBA.White); + light.setPosition(new Vector3f(5, 5, 5)); + light.setRadius(20); + rootNode.addLight(light); + light = new PointLight(); + light.setColor(ColorRGBA.White); + light.setPosition(new Vector3f(-5, -5, -5)); + light.setRadius(20); + rootNode.addLight(light); + } + + /** + * This method creates a RGB8 texture with the sizes of 10x10x10 pixels. + */ + private Texture getTexture() throws IOException { + ArrayList data = new ArrayList(1); + ByteBuffer bb = BufferUtils.createByteBuffer(10 * 10 * 10 * 3);//all data must be inside one buffer + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 10 * 10; ++j) { + bb.put((byte) (255f*i/10f)); + bb.put((byte) (255f*i/10f)); + bb.put((byte) (255f)); + } + } + bb.rewind(); + data.add(bb); + return new Texture3D(new Image(Format.RGB8, 10, 10, 10, data)); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/texture/TestTexture3DLoading.java b/jme3-examples/src/main/java/jme3test/texture/TestTexture3DLoading.java new file mode 100644 index 000000000..4d18a3bde --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/texture/TestTexture3DLoading.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.texture; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture; + +public class TestTexture3DLoading extends SimpleApplication { + + public static void main(String[] args) { + TestTexture3DLoading app = new TestTexture3DLoading(); + app.start(); + } + + @Override + public void simpleInitApp() { + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + flyCam.setEnabled(false); + + + Quad q = new Quad(10, 10); + + Geometry geom = new Geometry("Quad", q); + Material material = new Material(assetManager, "jme3test/texture/tex3DThumb.j3md"); + TextureKey key = new TextureKey("Textures/3D/flame.dds"); + key.setGenerateMips(true); + key.setAsTexture3D(true); + + Texture t = assetManager.loadTexture(key); + + int rows = 4;//4 * 4 + + q.scaleTextureCoordinates(new Vector2f(rows, rows)); + + //The image only have 8 pictures and we have 16 thumbs, the data will be interpolated by the GPU + material.setFloat("InvDepth", 1f / 16f); + material.setInt("Rows", rows); + material.setTexture("Texture", t); + geom.setMaterial(material); + + rootNode.attachChild(geom); + + cam.setLocation(new Vector3f(4.7444625f, 5.160054f, 13.1939f)); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/texture/TestTextureArray.java b/jme3-examples/src/main/java/jme3test/texture/TestTextureArray.java new file mode 100644 index 000000000..823fd7881 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/texture/TestTextureArray.java @@ -0,0 +1,86 @@ +package jme3test.texture; + + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Caps; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.TextureArray; +import com.jme3.util.BufferUtils; +import java.util.ArrayList; +import java.util.List; + +public class TestTextureArray extends SimpleApplication +{ + + @Override + public void simpleInitApp() + { + Material mat = new Material(assetManager, "jme3test/texture/UnshadedArray.j3md"); + + for (Caps caps : renderManager.getRenderer().getCaps()) { + System.out.println(caps.name()); + } + if(!renderManager.getRenderer().getCaps().contains(Caps.TextureArray)){ + throw new UnsupportedOperationException("Your hardware does not support TextureArray"); + } + + + Texture tex1 = assetManager.loadTexture( "Textures/Terrain/Pond/Pond.jpg"); + Texture tex2 = assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg"); + List images = new ArrayList(); + images.add(tex1.getImage()); + images.add(tex2.getImage()); + TextureArray tex3 = new TextureArray(images); + mat.setTexture("ColorMap", tex3); + + Mesh m = new Mesh(); + Vector3f[] vertices = new Vector3f[8]; + vertices[0] = new Vector3f(0, 0, 0); + vertices[1] = new Vector3f(3, 0, 0); + vertices[2] = new Vector3f(0, 3, 0); + vertices[3] = new Vector3f(3, 3, 0); + + vertices[4] = new Vector3f(3, 0, 0); + vertices[5] = new Vector3f(6, 0, 0); + vertices[6] = new Vector3f(3, 3, 0); + vertices[7] = new Vector3f(6, 3, 0); + + Vector3f[] texCoord = new Vector3f[8]; + texCoord[0] = new Vector3f(0, 0, 0); + texCoord[1] = new Vector3f(1, 0, 0); + texCoord[2] = new Vector3f(0, 1, 0); + texCoord[3] = new Vector3f(1, 1, 0); + + texCoord[4] = new Vector3f(0, 0, 1); + texCoord[5] = new Vector3f(1, 0, 1); + texCoord[6] = new Vector3f(0, 1, 1); + texCoord[7] = new Vector3f(1, 1, 1); + + int[] indexes = { 2, 0, 1, 1, 3, 2 , 6, 4, 5, 5, 7, 6}; + + m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices)); + m.setBuffer(Type.TexCoord, 3, BufferUtils.createFloatBuffer(texCoord)); + m.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(indexes)); + m.updateBound(); + + Geometry geom = new Geometry("Mesh", m); + geom.setMaterial(mat); + rootNode.attachChild(geom); + } + + /** + * @param args + */ + public static void main(String[] args) + { + TestTextureArray app = new TestTextureArray(); + app.start(); + } + +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/tools/TestOctree.java b/jme3-examples/src/main/java/jme3test/tools/TestOctree.java new file mode 100644 index 000000000..40db009dd --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/tools/TestOctree.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.tools; + +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.MaterialList; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.WireBox; +import com.jme3.scene.plugins.ogre.MeshLoader; +import com.jme3.scene.plugins.ogre.OgreMeshKey; +import com.jme3.texture.FrameBuffer; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import jme3tools.optimize.FastOctnode; +import jme3tools.optimize.Octree; + + +public class TestOctree extends SimpleApplication implements SceneProcessor { + + private Octree tree; + private FastOctnode fastRoot; + private Geometry[] globalGeoms; + private BoundingBox octBox; + + private Set 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); +// 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.class); + 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); + tree.construct(50, 0.01f, 50); + + 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/jme3-examples/src/main/java/jme3test/tools/TestSaveGame.java b/jme3-examples/src/main/java/jme3test/tools/TestSaveGame.java new file mode 100644 index 000000000..d9ef51db1 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/tools/TestSaveGame.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.tools; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import jme3tools.savegame.SaveGame; + +public class TestSaveGame extends SimpleApplication { + + public static void main(String[] args) { + + TestSaveGame app = new TestSaveGame(); + app.start(); + } + + @Override + public void simpleUpdate(float tpf) { + } + + public void simpleInitApp() { + + //node that is used to store player data + Node myPlayer = new Node(); + myPlayer.setName("PlayerNode"); + myPlayer.setUserData("name", "Mario"); + myPlayer.setUserData("health", 100.0f); + myPlayer.setUserData("points", 0); + + //the actual model would be attached to this node + Spatial model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + myPlayer.attachChild(model); + + //before saving the game, the model should be detached so its not saved along with the node + myPlayer.detachAllChildren(); + SaveGame.saveGame("mycompany/mygame", "savegame_001", myPlayer); + + //later the game is loaded again + Node player = (Node) SaveGame.loadGame("mycompany/mygame", "savegame_001"); + player.attachChild(model); + rootNode.attachChild(player); + + //and the data is available + System.out.println("Name: " + player.getUserData("name")); + System.out.println("Health: " + player.getUserData("health")); + System.out.println("Points: " + player.getUserData("points")); + + AmbientLight al = new AmbientLight(); + rootNode.addLight(al); + + //note you can also implement your own classes that implement the Savable interface. + } +} diff --git a/jme3-examples/src/main/java/jme3test/tools/TestTextureAtlas.java b/jme3-examples/src/main/java/jme3test/tools/TestTextureAtlas.java new file mode 100644 index 000000000..5c3d6c98c --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/tools/TestTextureAtlas.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.tools; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import jme3tools.optimize.TextureAtlas; + +public class TestTextureAtlas extends SimpleApplication { + + public static void main(String[] args) { + TestTextureAtlas app = new TestTextureAtlas(); + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(50); + Node scene = new Node("Scene"); + Spatial obj1 = assetManager.loadModel("Models/Ferrari/Car.scene"); + obj1.setLocalTranslation(-4, 0, 0); + Spatial obj2 = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + obj2.setLocalTranslation(-2, 0, 0); + Spatial obj3 = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml"); + obj3.setLocalTranslation(-0, 0, 0); + Spatial obj4 = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); + obj4.setLocalTranslation(2, 0, 0); + Spatial obj5 = assetManager.loadModel("Models/Tree/Tree.mesh.j3o"); + obj5.setLocalTranslation(4, 0, 0); + scene.attachChild(obj1); + scene.attachChild(obj2); + scene.attachChild(obj3); + scene.attachChild(obj4); + scene.attachChild(obj5); + + Geometry geom = TextureAtlas.makeAtlasBatch(scene, assetManager, 2048); + + AmbientLight al = new AmbientLight(); + rootNode.addLight(al); + + DirectionalLight sun = new DirectionalLight(); + sun.setDirection(new Vector3f(0.69077975f, -0.6277887f, -0.35875428f).normalizeLocal()); + sun.setColor(ColorRGBA.White.clone().multLocal(2)); + rootNode.addLight(sun); + + rootNode.attachChild(geom); + + //quad to display material + Geometry box = new Geometry("displayquad", new Quad(4, 4)); + box.setMaterial(geom.getMaterial()); + box.setLocalTranslation(0, 1, 3); + rootNode.attachChild(box); + } +} diff --git a/jme3-examples/src/main/java/jme3test/water/TestMultiPostWater.java b/jme3-examples/src/main/java/jme3test/water/TestMultiPostWater.java new file mode 100644 index 000000000..2821b818c --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/water/TestMultiPostWater.java @@ -0,0 +1,187 @@ +package jme3test.water; + +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.renderer.Camera; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.util.SkyFactory; +import com.jme3.water.WaterFilter; +import java.util.ArrayList; +import java.util.List; + +/** + * test + * + * @author normenhansen + */ +public class TestMultiPostWater extends SimpleApplication { + + private Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f); + private WaterFilter water; + private TerrainQuad terrain; + private Material matRock; + private static float WATER_HEIGHT = 90; + + public static void main(String[] args) { + TestMultiPostWater app = new TestMultiPostWater(); + AppSettings s = new AppSettings(true); + s.setRenderer(AppSettings.LWJGL_OPENGL2); + s.setAudioRenderer(AppSettings.LWJGL_OPENAL); +// +// s.setRenderer("JOGL"); +// s.setAudioRenderer("JOAL"); + app.setSettings(s); + + app.start(); + } + + @Override + public void simpleInitApp() { + +// setDisplayFps(false); +// setDisplayStatView(false); + + Node mainScene = new Node("Main Scene"); + rootNode.attachChild(mainScene); + + createTerrain(mainScene); + DirectionalLight sun = new DirectionalLight(); + sun.setDirection(lightDir); + sun.setColor(ColorRGBA.White.clone().multLocal(1.7f)); + mainScene.addLight(sun); + + flyCam.setMoveSpeed(100); + + //cam.setLocation(new Vector3f(-700, 100, 300)); + //cam.setRotation(new Quaternion().fromAngleAxis(0.5f, Vector3f.UNIT_Z)); + cam.setLocation(new Vector3f(-327.21957f, 251.6459f, 126.884346f)); + 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); + + + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + + water = new WaterFilter(rootNode, lightDir); + water.setCenter(new Vector3f(9.628218f, -15.830074f, 199.23595f)); + water.setRadius(260); + 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.setRefractionStrength(0.2f); + water.setWaterHeight(WATER_HEIGHT); + fpp.addFilter(water); + + WaterFilter water2 = new WaterFilter(rootNode, lightDir); + water2.setCenter(new Vector3f(-280.46027f, -24.971727f, -271.71976f)); + water2.setRadius(260); + water2.setWaterHeight(WATER_HEIGHT); + water2.setUseFoam(false); + water2.setUseRipples(false); + water2.setDeepWaterColor(ColorRGBA.Brown); + water2.setWaterColor(ColorRGBA.Brown.mult(2.0f)); + water2.setWaterTransparency(0.2f); + water2.setMaxAmplitude(0.3f); + water2.setWaveScale(0.008f); + water2.setSpeed(0.7f); + water2.setShoreHardness(1.0f); + water2.setRefractionConstant(0.2f); + water2.setShininess(0.3f); + water2.setSunScale(1.0f); + water2.setColorExtinction(new Vector3f(10.0f, 20.0f, 30.0f)); + fpp.addFilter(water2); + + + WaterFilter water3 = new WaterFilter(rootNode, lightDir); + water3.setCenter(new Vector3f(319.6663f, -18.367947f, -236.67674f)); + water3.setRadius(260); + water3.setWaterHeight(WATER_HEIGHT); + water3.setWaveScale(0.003f); + water3.setMaxAmplitude(2f); + water3.setFoamExistence(new Vector3f(1f, 4, 0.5f)); + water3.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg")); + water3.setRefractionStrength(0.2f); + water3.setDeepWaterColor(ColorRGBA.Red); + water3.setWaterColor(ColorRGBA.Red.mult(2.0f)); + water3.setLightColor(ColorRGBA.Red); + fpp.addFilter(water3); + + viewPort.addProcessor(fpp); + + //fpp.setNumSamples(4); + } + + 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/pools.png"); + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap", grass); + matRock.setFloat("DiffuseMap_0_scale", 64); + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_1", dirt); + matRock.setFloat("DiffuseMap_1_scale", 16); + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_2", rock); + matRock.setFloat("DiffuseMap_2_scale", 128); + Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); + normalMap0.setWrap(WrapMode.Repeat); + Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); + normalMap1.setWrap(WrapMode.Repeat); + Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); + normalMap2.setWrap(WrapMode.Repeat); + matRock.setTexture("NormalMap", normalMap0); + matRock.setTexture("NormalMap_1", normalMap2); + matRock.setTexture("NormalMap_2", normalMap2); + + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f); + heightmap.load(); + } catch (Exception e) { + e.printStackTrace(); + } + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList(); + cameras.add(getCamera()); + terrain.setMaterial(matRock); + terrain.setLocalScale(new Vector3f(5, 5, 5)); + terrain.setLocalTranslation(new Vector3f(0, -30, 0)); + terrain.setLocked(false); // unlock it so we can edit the height + + terrain.setShadowMode(ShadowMode.Receive); + rootNode.attachChild(terrain); + + } + + @Override + public void simpleUpdate(float tpf) { + } +} diff --git a/jme3-examples/src/main/java/jme3test/water/TestPostWater.java b/jme3-examples/src/main/java/jme3test/water/TestPostWater.java new file mode 100644 index 000000000..bdd6a717f --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/water/TestPostWater.java @@ -0,0 +1,313 @@ +package jme3test.water; + +import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioNode; +import com.jme3.audio.LowPassFilter; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh; +import com.jme3.input.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.material.RenderState.BlendMode; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.post.filters.DepthOfFieldFilter; +import com.jme3.post.filters.LightScatteringFilter; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.util.SkyFactory; +import com.jme3.water.WaterFilter; +import java.util.ArrayList; +import java.util.List; + +/** + * test + * + * @author normenhansen + */ +public class TestPostWater extends SimpleApplication { + + private Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f); + private WaterFilter water; + TerrainQuad terrain; + Material matRock; + AudioNode waves; + LowPassFilter underWaterAudioFilter = new LowPassFilter(0.5f, 0.1f); + LowPassFilter underWaterReverbFilter = new LowPassFilter(0.5f, 0.1f); + LowPassFilter aboveWaterAudioFilter = new LowPassFilter(1, 1); + + public static void main(String[] args) { + TestPostWater app = new TestPostWater(); + app.start(); + } + + @Override + public void simpleInitApp() { + + setDisplayFps(false); + setDisplayStatView(false); + + Node mainScene = new Node("Main Scene"); + rootNode.attachChild(mainScene); + + createTerrain(mainScene); + DirectionalLight sun = new DirectionalLight(); + sun.setDirection(lightDir); + sun.setColor(ColorRGBA.White.clone().multLocal(1.7f)); + mainScene.addLight(sun); + + DirectionalLight l = new DirectionalLight(); + l.setDirection(Vector3f.UNIT_Y.mult(-1)); + l.setColor(ColorRGBA.White.clone().multLocal(0.3f)); +// mainScene.addLight(l); + + flyCam.setMoveSpeed(50); + + //cam.setLocation(new Vector3f(-700, 100, 300)); + //cam.setRotation(new Quaternion().fromAngleAxis(0.5f, Vector3f.UNIT_Z)); + cam.setLocation(new Vector3f(-327.21957f, 61.6459f, 126.884346f)); + cam.setRotation(new Quaternion(0.052168474f, 0.9443102f, -0.18395276f, 0.2678024f)); + + + cam.setRotation(new Quaternion().fromAngles(new float[]{FastMath.PI * 0.06f, FastMath.PI * 0.65f, 0})); + + + Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false); + sky.setLocalScale(350); + + mainScene.attachChild(sky); + cam.setFrustumFar(4000); + //cam.setFrustumNear(100); + + + + //private FilterPostProcessor fpp; + + + water = new WaterFilter(rootNode, lightDir); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + + fpp.addFilter(water); + BloomFilter bloom = new BloomFilter(); + //bloom.getE + bloom.setExposurePower(55); + bloom.setBloomIntensity(1.0f); + fpp.addFilter(bloom); + LightScatteringFilter lsf = new LightScatteringFilter(lightDir.mult(-300)); + lsf.setLightDensity(1.0f); + fpp.addFilter(lsf); + DepthOfFieldFilter dof = new DepthOfFieldFilter(); + dof.setFocusDistance(0); + dof.setFocusRange(100); + fpp.addFilter(dof); +// + + // fpp.addFilter(new TranslucentBucketFilter()); + // + + // fpp.setNumSamples(4); + + + water.setWaveScale(0.003f); + water.setMaxAmplitude(2f); + water.setFoamExistence(new Vector3f(1f, 4, 0.5f)); + water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg")); + //water.setNormalScale(0.5f); + + //water.setRefractionConstant(0.25f); + water.setRefractionStrength(0.2f); + //water.setFoamHardness(0.6f); + + water.setWaterHeight(initialWaterHeight); + uw = cam.getLocation().y < waterHeight; + + waves = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", false); + waves.setLooping(true); + waves.setReverbEnabled(true); + if (uw) { + waves.setDryFilter(new LowPassFilter(0.5f, 0.1f)); + } else { + waves.setDryFilter(aboveWaterAudioFilter); + } + audioRenderer.playSource(waves); + // + viewPort.addProcessor(fpp); + + inputManager.addListener(new ActionListener() { + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + if (name.equals("foam1")) { + water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam.jpg")); + } + if (name.equals("foam2")) { + water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg")); + } + if (name.equals("foam3")) { + water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam3.jpg")); + } + + if (name.equals("upRM")) { + water.setReflectionMapSize(Math.min(water.getReflectionMapSize() * 2, 4096)); + System.out.println("Reflection map size : " + water.getReflectionMapSize()); + } + if (name.equals("downRM")) { + water.setReflectionMapSize(Math.max(water.getReflectionMapSize() / 2, 32)); + System.out.println("Reflection map size : " + water.getReflectionMapSize()); + } + } + } + }, "foam1", "foam2", "foam3", "upRM", "downRM"); + inputManager.addMapping("foam1", new KeyTrigger(KeyInput.KEY_1)); + inputManager.addMapping("foam2", new KeyTrigger(KeyInput.KEY_2)); + inputManager.addMapping("foam3", new KeyTrigger(KeyInput.KEY_3)); + inputManager.addMapping("upRM", new KeyTrigger(KeyInput.KEY_PGUP)); + inputManager.addMapping("downRM", new KeyTrigger(KeyInput.KEY_PGDN)); +// createBox(); + // createFire(); + } + Geometry box; + + private void createBox() { + //creating a transluscent box + box = new Geometry("box", new Box(new Vector3f(0, 0, 0), 50, 50, 50)); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", new ColorRGBA(1.0f, 0, 0, 0.3f)); + mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + //mat.getAdditionalRenderState().setDepthWrite(false); + //mat.getAdditionalRenderState().setDepthTest(false); + box.setMaterial(mat); + box.setQueueBucket(Bucket.Translucent); + + + //creating a post view port +// ViewPort post=renderManager.createPostView("transpPost", cam); +// post.setClearFlags(false, true, true); + + + box.setLocalTranslation(-600, 0, 300); + + //attaching the box to the post viewport + //Don't forget to updateGeometricState() the box in the simpleUpdate + // post.attachScene(box); + + rootNode.attachChild(box); + } + + private void createFire() { + /** + * Uses Texture from jme3-test-data library! + */ + ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30); + Material mat_red = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat_red.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png")); + + fire.setMaterial(mat_red); + fire.setImagesX(2); + fire.setImagesY(2); // 2x2 texture animation + fire.setEndColor(new ColorRGBA(1f, 0f, 0f, 1f)); // red + fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow + fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 2, 0)); + fire.setStartSize(10f); + fire.setEndSize(1f); + fire.setGravity(0, 0, 0); + fire.setLowLife(0.5f); + fire.setHighLife(1.5f); + fire.getParticleInfluencer().setVelocityVariation(0.3f); + fire.setLocalTranslation(-350, 40, 430); + + fire.setQueueBucket(Bucket.Transparent); + rootNode.attachChild(fire); + } + + private void createTerrain(Node rootNode) { + matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matRock.setBoolean("useTriPlanarMapping", false); + matRock.setBoolean("WardIso", true); + matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap", grass); + matRock.setFloat("DiffuseMap_0_scale", 64); + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_1", dirt); + matRock.setFloat("DiffuseMap_1_scale", 16); + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_2", rock); + matRock.setFloat("DiffuseMap_2_scale", 128); + Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); + normalMap0.setWrap(WrapMode.Repeat); + Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); + normalMap1.setWrap(WrapMode.Repeat); + Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); + normalMap2.setWrap(WrapMode.Repeat); + matRock.setTexture("NormalMap", normalMap0); + matRock.setTexture("NormalMap_1", normalMap2); + matRock.setTexture("NormalMap_2", normalMap2); + + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f); + heightmap.load(); + } catch (Exception e) { + e.printStackTrace(); + } + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList(); + cameras.add(getCamera()); + terrain.setMaterial(matRock); + terrain.setLocalScale(new Vector3f(5, 5, 5)); + terrain.setLocalTranslation(new Vector3f(0, -30, 0)); + terrain.setLocked(false); // unlock it so we can edit the height + + terrain.setShadowMode(ShadowMode.Receive); + rootNode.attachChild(terrain); + + } + //This part is to emulate tides, slightly varrying the height of the water plane + private float time = 0.0f; + private float waterHeight = 0.0f; + private float initialWaterHeight = 90f;//0.8f; + private boolean uw = false; + + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + // box.updateGeometricState(); + time += tpf; + waterHeight = (float) Math.cos(((time * 0.6f) % FastMath.TWO_PI)) * 1.5f; + water.setWaterHeight(initialWaterHeight + waterHeight); + if (water.isUnderWater() && !uw) { + + waves.setDryFilter(new LowPassFilter(0.5f, 0.1f)); + uw = true; + } + if (!water.isUnderWater() && uw) { + uw = false; + //waves.setReverbEnabled(false); + waves.setDryFilter(new LowPassFilter(1, 1f)); + //waves.setDryFilter(new LowPassFilter(1,1f)); + + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/water/TestPostWaterLake.java b/jme3-examples/src/main/java/jme3test/water/TestPostWaterLake.java new file mode 100644 index 000000000..e178c12ac --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/water/TestPostWaterLake.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.water; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.HttpZipLocator; +import com.jme3.asset.plugins.ZipLocator; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.scene.Spatial; +import com.jme3.util.SkyFactory; +import com.jme3.water.WaterFilter; +import java.io.File; + +public class TestPostWaterLake extends SimpleApplication { + + // set default for applets + private static boolean useHttp = true; + + public static void main(String[] args) { + + TestPostWaterLake app = new TestPostWaterLake(); + app.start(); + } + + public void simpleInitApp() { + this.flyCam.setMoveSpeed(10); + cam.setLocation(new Vector3f(-27.0f, 1.0f, 75.0f)); + // cam.setRotation(new Quaternion(0.03f, 0.9f, 0f, 0.4f)); + + // load sky + rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false)); + + File file = new File("wildhouse.zip"); + + if (file.exists()) { + useHttp = false; + } + // create the geometry and attach it + // load the level from zip or http zip + if (useHttp) { + assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip", HttpZipLocator.class); + } else { + assetManager.registerLocator("wildhouse.zip", ZipLocator.class); + } + 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/jme3-examples/src/main/java/jme3test/water/TestSceneWater.java b/jme3-examples/src/main/java/jme3test/water/TestSceneWater.java new file mode 100644 index 000000000..f0a6302a1 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/water/TestSceneWater.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.water; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.HttpZipLocator; +import com.jme3.asset.plugins.ZipLocator; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.SkyFactory; +import com.jme3.water.SimpleWaterProcessor; +import java.io.File; + +public class TestSceneWater extends SimpleApplication { + + // set default for applets + private static boolean useHttp = true; + + public static void main(String[] args) { + + TestSceneWater app = new TestSceneWater(); + app.start(); + } + + public void simpleInitApp() { + this.flyCam.setMoveSpeed(10); + Node mainScene=new Node(); + cam.setLocation(new Vector3f(-27.0f, 1.0f, 75.0f)); + cam.setRotation(new Quaternion(0.03f, 0.9f, 0f, 0.4f)); + + // load sky + mainScene.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false)); + + + File file = new File("wildhouse.zip"); + if (file.exists()) { + useHttp = false; + } + // create the geometry and attach it + // load the level from zip or http zip + if (useHttp) { + assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip", HttpZipLocator.class); + } else { + assetManager.registerLocator("wildhouse.zip", ZipLocator.class); + } + Spatial scene = assetManager.loadModel("main.scene"); + + DirectionalLight sun = new DirectionalLight(); + Vector3f lightDir=new Vector3f(-0.37352666f, -0.50444174f, -0.7784704f); + sun.setDirection(lightDir); + sun.setColor(ColorRGBA.White.clone().multLocal(2)); + scene.addLight(sun); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + //add lightPos Geometry + Sphere lite=new Sphere(8, 8, 3.0f); + Geometry lightSphere=new Geometry("lightsphere", lite); + lightSphere.setMaterial(mat); + Vector3f lightPos=lightDir.multLocal(-400); + lightSphere.setLocalTranslation(lightPos); + rootNode.attachChild(lightSphere); + + + SimpleWaterProcessor waterProcessor = new SimpleWaterProcessor(assetManager); + waterProcessor.setReflectionScene(mainScene); + waterProcessor.setDebug(false); + waterProcessor.setLightPosition(lightPos); + waterProcessor.setRefractionClippingOffset(1.0f); + + + //setting the water plane + Vector3f waterLocation=new Vector3f(0,-20,0); + waterProcessor.setPlane(new Plane(Vector3f.UNIT_Y, waterLocation.dot(Vector3f.UNIT_Y))); + WaterUI waterUi=new WaterUI(inputManager, waterProcessor); + waterProcessor.setWaterColor(ColorRGBA.Brown); + waterProcessor.setDebug(true); + //lower render size for higher performance +// waterProcessor.setRenderSize(128,128); + //raise depth to see through water +// waterProcessor.setWaterDepth(20); + //lower the distortion scale if the waves appear too strong +// waterProcessor.setDistortionScale(0.1f); + //lower the speed of the waves if they are too fast +// waterProcessor.setWaveSpeed(0.01f); + + Quad quad = new Quad(400,400); + + //the texture coordinates define the general size of the waves + quad.scaleTextureCoordinates(new Vector2f(6f,6f)); + + Geometry water=new Geometry("water", quad); + water.setShadowMode(ShadowMode.Receive); + water.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); + water.setMaterial(waterProcessor.getMaterial()); + water.setLocalTranslation(-200, -20, 250); + + rootNode.attachChild(water); + + viewPort.addProcessor(waterProcessor); + + mainScene.attachChild(scene); + rootNode.attachChild(mainScene); + } +} diff --git a/jme3-examples/src/main/java/jme3test/water/TestSimpleWater.java b/jme3-examples/src/main/java/jme3test/water/TestSimpleWater.java new file mode 100644 index 000000000..4a8855df6 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/water/TestSimpleWater.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.water; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.SkyFactory; +import com.jme3.water.SimpleWaterProcessor; + +/** + * + * @author normenhansen + */ +public class TestSimpleWater extends SimpleApplication implements ActionListener { + + Material mat; + Spatial waterPlane; + Geometry lightSphere; + SimpleWaterProcessor waterProcessor; + Node sceneNode; + boolean useWater = true; + private Vector3f lightPos = new Vector3f(33,12,-29); + + + public static void main(String[] args) { + TestSimpleWater app = new TestSimpleWater(); + app.start(); + } + + @Override + public void simpleInitApp() { + initInput(); + initScene(); + + //create processor + waterProcessor = new SimpleWaterProcessor(assetManager); + waterProcessor.setReflectionScene(sceneNode); + waterProcessor.setDebug(true); + viewPort.addProcessor(waterProcessor); + + waterProcessor.setLightPosition(lightPos); + + //create water quad + //waterPlane = waterProcessor.createWaterGeometry(100, 100); + waterPlane=(Spatial) assetManager.loadModel("Models/WaterTest/WaterTest.mesh.xml"); + waterPlane.setMaterial(waterProcessor.getMaterial()); + waterPlane.setLocalScale(40); + waterPlane.setLocalTranslation(-5, 0, 5); + + rootNode.attachChild(waterPlane); + } + + private void initScene() { + //init cam location + cam.setLocation(new Vector3f(0, 10, 10)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + //init scene + sceneNode = new Node("Scene"); + mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + Box b = new Box(1, 1, 1); + Geometry geom = new Geometry("Box", b); + geom.setMaterial(mat); + sceneNode.attachChild(geom); + + // load sky + sceneNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", false)); + rootNode.attachChild(sceneNode); + + //add lightPos Geometry + Sphere lite=new Sphere(8, 8, 3.0f); + lightSphere=new Geometry("lightsphere", lite); + lightSphere.setMaterial(mat); + lightSphere.setLocalTranslation(lightPos); + rootNode.attachChild(lightSphere); + } + + protected void initInput() { + flyCam.setMoveSpeed(3); + //init input + inputManager.addMapping("use_water", new KeyTrigger(KeyInput.KEY_O)); + inputManager.addListener(this, "use_water"); + inputManager.addMapping("lightup", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addListener(this, "lightup"); + inputManager.addMapping("lightdown", new KeyTrigger(KeyInput.KEY_G)); + inputManager.addListener(this, "lightdown"); + inputManager.addMapping("lightleft", new KeyTrigger(KeyInput.KEY_H)); + inputManager.addListener(this, "lightleft"); + inputManager.addMapping("lightright", new KeyTrigger(KeyInput.KEY_K)); + inputManager.addListener(this, "lightright"); + inputManager.addMapping("lightforward", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addListener(this, "lightforward"); + inputManager.addMapping("lightback", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addListener(this, "lightback"); + } + + @Override + public void simpleUpdate(float tpf) { + fpsText.setText("Light Position: "+lightPos.toString()+" Change Light position with [U], [H], [J], [K] and [T], [G] Turn off water with [O]"); + lightSphere.setLocalTranslation(lightPos); + waterProcessor.setLightPosition(lightPos); + } + + public void onAction(String name, boolean value, float tpf) { + if (name.equals("use_water") && value) { + if (!useWater) { + useWater = true; + waterPlane.setMaterial(waterProcessor.getMaterial()); + } else { + useWater = false; + waterPlane.setMaterial(mat); + } + } else if (name.equals("lightup") && value) { + lightPos.y++; + } else if (name.equals("lightdown") && value) { + lightPos.y--; + } else if (name.equals("lightleft") && value) { + lightPos.x--; + } else if (name.equals("lightright") && value) { + lightPos.x++; + } else if (name.equals("lightforward") && value) { + lightPos.z--; + } else if (name.equals("lightback") && value) { + lightPos.z++; + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/water/WaterUI.java b/jme3-examples/src/main/java/jme3test/water/WaterUI.java new file mode 100644 index 000000000..c09732089 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/water/WaterUI.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.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/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.frag b/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.frag new file mode 100644 index 000000000..4cd92cff9 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.frag @@ -0,0 +1,57 @@ +#extension GL_EXT_texture_array : enable +#extension GL_EXT_gpu_shader4 : enable + +uniform vec4 m_Color; + +#if defined(HAS_GLOWMAP) || defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD)) + #define NEED_TEXCOORD1 +#endif + +#ifdef HAS_COLORMAP + #if !defined(GL_EXT_texture_array) && !defined(GL_EXT_gpu_shader4) + #error Texture arrays are not supported, but required for this shader. + #endif + + uniform sampler2DArray m_ColorMap; +#endif + +#ifdef NEED_TEXCOORD1 + varying vec3 texCoord1; +#endif + +#ifdef HAS_LIGHTMAP + uniform sampler2D m_LightMap; + #ifdef SEPERATE_TEXCOORD + varying vec3 texCoord2; + #endif +#endif + +#ifdef HAS_VERTEXCOLOR + varying vec4 vertColor; +#endif + +void main(){ + vec4 color = vec4(1.0); + + #ifdef HAS_COLORMAP + color *= texture2DArray(m_ColorMap, texCoord1); + #endif + + #ifdef HAS_VERTEXCOLOR + color *= vertColor; + #endif + + #ifdef HAS_COLOR + color *= m_Color; + #endif + + #ifdef HAS_LIGHTMAP + #ifdef SEPARATE_TEXCOORD + color.rgb *= texture2D(m_LightMap, texCoord2).rgb; + #else + color.rgb *= texture2D(m_LightMap, texCoord1).rgb; + #endif + #endif + + gl_FragColor = color; +} \ No newline at end of file diff --git a/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.j3md b/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.j3md new file mode 100644 index 000000000..6a3219e60 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.j3md @@ -0,0 +1,66 @@ +MaterialDef Unshaded { + + MaterialParameters { + TextureArray ColorMap + Texture2D LightMap + Color Color (Color) + Boolean VertexColor + Boolean SeparateTexCoord + + // Texture of the glowing parts of the material + Texture2D GlowMap + // The glow color of the object + Color GlowColor + } + + Technique { + VertexShader GLSL100: jme3test/texture/UnshadedArray.vert + FragmentShader GLSL100: jme3test/texture/UnshadedArray.frag + + WorldParameters { + WorldViewProjectionMatrix + } + + Defines { + SEPARATE_TEXCOORD : SeparateTexCoord + HAS_COLORMAP : ColorMap + HAS_LIGHTMAP : LightMap + HAS_VERTEXCOLOR : VertexColor + HAS_COLOR : Color + } + } + + Technique PreNormalPass { + + VertexShader GLSL100 : Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + NormalMatrix + } + + RenderState { + + } + + } + + + Technique Glow { + + VertexShader GLSL100: Cjme3test/texture/UnshadedArray.vert + FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag + + WorldParameters { + WorldViewProjectionMatrix + } + + Defines { + HAS_GLOWMAP : GlowMap + HAS_GLOWCOLOR : GlowColor + HAS_COLORMAP // Must be passed so that Unshaded.vert exports texCoord. + } + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.vert b/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.vert new file mode 100644 index 000000000..9d1153c3d --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/texture/UnshadedArray.vert @@ -0,0 +1,38 @@ +uniform mat4 g_WorldViewProjectionMatrix; +attribute vec3 inPosition; + +#if defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD)) + #define NEED_TEXCOORD1 +#endif + +#ifdef NEED_TEXCOORD1 + attribute vec3 inTexCoord; + varying vec3 texCoord1; +#endif + +#ifdef SEPARATE_TEXCOORD + attribute vec3 inTexCoord2; + varying vec3 texCoord2; +#endif + +#ifdef HAS_VERTEXCOLOR + attribute vec4 inColor; + varying vec4 vertColor; +#endif + +void main(){ + #ifdef NEED_TEXCOORD1 + texCoord1 = inTexCoord; + #endif + + #ifdef SEPARATE_TEXCOORD + texCoord2 = inTexCoord2; + #endif + + #ifdef HAS_VERTEXCOLOR + vertColor = inColor; + #endif + + gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0); +} + diff --git a/jme3-examples/src/main/resources/jme3test/texture/tex3D.frag b/jme3-examples/src/main/resources/jme3test/texture/tex3D.frag new file mode 100644 index 000000000..55862acf9 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/texture/tex3D.frag @@ -0,0 +1,7 @@ +uniform sampler3D m_Texture; + +varying vec3 texCoord; + +void main(){ + gl_FragColor= texture3D(m_Texture,texCoord); +} \ No newline at end of file diff --git a/jme3-examples/src/main/resources/jme3test/texture/tex3D.j3md b/jme3-examples/src/main/resources/jme3test/texture/tex3D.j3md new file mode 100644 index 000000000..1ba260594 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/texture/tex3D.j3md @@ -0,0 +1,16 @@ +MaterialDef My MaterialDef { + + MaterialParameters { + Texture3D Texture + } + + Technique { + VertexShader GLSL100: jme3test/texture/tex3D.vert + FragmentShader GLSL100: jme3test/texture/tex3D.frag + + WorldParameters { + WorldViewProjectionMatrix + } + } + +} diff --git a/jme3-examples/src/main/resources/jme3test/texture/tex3D.vert b/jme3-examples/src/main/resources/jme3test/texture/tex3D.vert new file mode 100644 index 000000000..f91b7b309 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/texture/tex3D.vert @@ -0,0 +1,11 @@ +uniform mat4 g_WorldViewProjectionMatrix; + +attribute vec3 inTexCoord; +attribute vec3 inPosition; + +varying vec3 texCoord; + +void main(){ + gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0); + texCoord=inTexCoord; +} \ No newline at end of file diff --git a/jme3-examples/src/main/resources/jme3test/texture/tex3DThumb.frag b/jme3-examples/src/main/resources/jme3test/texture/tex3DThumb.frag new file mode 100644 index 000000000..e70bc5b25 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/texture/tex3DThumb.frag @@ -0,0 +1,15 @@ +uniform sampler3D m_Texture; +uniform int m_Rows; +uniform float m_InvDepth; + +varying vec2 texCoord; + +void main(){ + float rows = float(m_Rows); + float depthx = floor(texCoord.x); + float depthy = (rows - 1.0) - floor(texCoord.y); + //vec3 texC=vec3(texCoord.x,texCoord.y ,0.7);// + + vec3 texC = vec3(fract(texCoord.x),fract(texCoord.y),(depthy * rows + depthx) * m_InvDepth); + gl_FragColor = texture3D(m_Texture, texC); +} \ No newline at end of file diff --git a/jme3-examples/src/main/resources/jme3test/texture/tex3DThumb.j3md b/jme3-examples/src/main/resources/jme3test/texture/tex3DThumb.j3md new file mode 100644 index 000000000..a42bd381e --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/texture/tex3DThumb.j3md @@ -0,0 +1,18 @@ +MaterialDef Tex3DThumb { + + MaterialParameters { + Texture3D Texture + Int Rows; + Float InvDepth; + } + + Technique { + VertexShader GLSL100: jme3test/texture/tex3DThumb.vert + FragmentShader GLSL100: jme3test/texture/tex3DThumb.frag + + WorldParameters { + WorldViewProjectionMatrix + } + } + +} diff --git a/jme3-examples/src/main/resources/jme3test/texture/tex3DThumb.vert b/jme3-examples/src/main/resources/jme3test/texture/tex3DThumb.vert new file mode 100644 index 000000000..6d27bc030 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/texture/tex3DThumb.vert @@ -0,0 +1,11 @@ +uniform mat4 g_WorldViewProjectionMatrix; + +attribute vec2 inTexCoord; +attribute vec3 inPosition; + +varying vec2 texCoord; + +void main(){ + gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0); + texCoord=inTexCoord; +} \ No newline at end of file diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java new file mode 100644 index 000000000..484f52133 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -0,0 +1,914 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet; + +import com.bulletphysics.BulletGlobals; +import com.bulletphysics.ContactAddedCallback; +import com.bulletphysics.ContactDestroyedCallback; +import com.bulletphysics.ContactProcessedCallback; +import com.bulletphysics.collision.broadphase.*; +import com.bulletphysics.collision.dispatch.*; +import com.bulletphysics.collision.dispatch.CollisionWorld.LocalConvexResult; +import com.bulletphysics.collision.dispatch.CollisionWorld.LocalRayResult; +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.dynamics.constraintsolver.TypedConstraint; +import com.bulletphysics.dynamics.vehicle.RaycastVehicle; +import com.bulletphysics.extras.gimpact.GImpactCollisionAlgorithm; +import com.jme3.app.AppTask; +import com.jme3.asset.AssetManager; +import com.jme3.bullet.collision.*; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.PhysicsControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.PhysicsCharacter; +import com.jme3.bullet.objects.PhysicsGhostObject; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.bullet.util.Converter; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import java.util.Collection; +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 { + + private static final Logger logger = Logger.getLogger(PhysicsSpace.class.getName()); + 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 physicsGhostObjects = new ConcurrentHashMap(); + private Map physicsCharacters = new ConcurrentHashMap(); + private Map physicsBodies = new ConcurrentHashMap(); + private Map physicsJoints = new ConcurrentHashMap(); + private Map physicsVehicles = new ConcurrentHashMap(); + private Map collisionGroupListeners = new ConcurrentHashMap(); + private ConcurrentLinkedQueue tickListeners = new ConcurrentLinkedQueue(); + private List collisionListeners = new LinkedList(); + private List collisionEvents = new LinkedList(); + 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.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); + 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 (physicsJoint.getBodyA() instanceof PhysicsControl) { + add(physicsJoint.getBodyA()); + } else { + addRigidBody(physicsJoint.getBodyA()); + } + if (physicsJoint.getBodyA() instanceof PhysicsControl) { + add(physicsJoint.getBodyB()); + } else { + addRigidBody(physicsJoint.getBodyB()); + } + 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); + 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 (physicsJoint.getBodyA() instanceof PhysicsControl) { + remove(physicsJoint.getBodyA()); + } else { + removeRigidBody(physicsJoint.getBodyA()); + } + if (physicsJoint.getBodyA() instanceof PhysicsControl) { + remove(physicsJoint.getBodyB()); + } else { + removeRigidBody(physicsJoint.getBodyB()); + } + 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(); + removeAll(spat); + } + } + } + + private void addGhostObject(PhysicsGhostObject node) { + if(physicsGhostObjects.containsKey(node.getObjectId())){ + logger.log(Level.WARNING, "GhostObject {0} already exists in PhysicsSpace, cannot add.", node); + return; + } + physicsGhostObjects.put(node.getObjectId(), node); + logger.log(Level.FINE, "Adding ghost object {0} to physics space.", node.getObjectId()); + dynamicsWorld.addCollisionObject(node.getObjectId()); + } + + private void removeGhostObject(PhysicsGhostObject node) { + if(!physicsGhostObjects.containsKey(node.getObjectId())){ + logger.log(Level.WARNING, "GhostObject {0} does not exist in PhysicsSpace, cannot remove.", node); + return; + } + physicsGhostObjects.remove(node.getObjectId()); + logger.log(Level.FINE, "Removing ghost object {0} from physics space.", node.getObjectId()); + dynamicsWorld.removeCollisionObject(node.getObjectId()); + } + + private void addCharacter(PhysicsCharacter node) { + if(physicsCharacters.containsKey(node.getObjectId())){ + logger.log(Level.WARNING, "Character {0} already exists in PhysicsSpace, cannot add.", node); + return; + } + physicsCharacters.put(node.getObjectId(), node); + logger.log(Level.FINE, "Adding character {0} to physics space.", 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) { + if(!physicsCharacters.containsKey(node.getObjectId())){ + logger.log(Level.WARNING, "Character {0} does not exist in PhysicsSpace, cannot remove.", node); + return; + } + physicsCharacters.remove(node.getObjectId()); + logger.log(Level.FINE, "Removing character {0} from physics space.", node.getObjectId()); + dynamicsWorld.removeAction(node.getControllerId()); + dynamicsWorld.removeCollisionObject(node.getObjectId()); + } + + private void addRigidBody(PhysicsRigidBody node) { + if(physicsBodies.containsKey(node.getObjectId())){ + logger.log(Level.WARNING, "RigidBody {0} already exists in PhysicsSpace, cannot add.", node); + return; + } + physicsBodies.put(node.getObjectId(), node); + + //Workaround + //It seems that adding a Kinematic RigidBody to the dynamicWorld prevent it from being non kinematic again afterward. + //so we add it non kinematic, then set it kinematic again. + boolean kinematic = false; + if (node.isKinematic()) { + kinematic = true; + node.setKinematic(false); + } + dynamicsWorld.addRigidBody(node.getObjectId()); + if (kinematic) { + node.setKinematic(true); + } + + logger.log(Level.FINE, "Adding RigidBody {0} to physics space.", node.getObjectId()); + if (node instanceof PhysicsVehicle) { + logger.log(Level.FINE, "Adding vehicle constraint {0} to physics space.", ((PhysicsVehicle) node).getVehicleId()); + ((PhysicsVehicle) node).createVehicle(this); + physicsVehicles.put(((PhysicsVehicle) node).getVehicleId(), (PhysicsVehicle)node); + dynamicsWorld.addVehicle(((PhysicsVehicle) node).getVehicleId()); + } + } + + private void removeRigidBody(PhysicsRigidBody node) { + if(!physicsBodies.containsKey(node.getObjectId())){ + logger.log(Level.WARNING, "RigidBody {0} does not exist in PhysicsSpace, cannot remove.", node); + return; + } + if (node instanceof PhysicsVehicle) { + logger.log(Level.FINE, "Removing vehicle constraint {0} from physics space.", ((PhysicsVehicle) node).getVehicleId()); + physicsVehicles.remove(((PhysicsVehicle) node).getVehicleId()); + dynamicsWorld.removeVehicle(((PhysicsVehicle) node).getVehicleId()); + } + logger.log(Level.FINE, "Removing RigidBody {0} from physics space.", node.getObjectId()); + physicsBodies.remove(node.getObjectId()); + dynamicsWorld.removeRigidBody(node.getObjectId()); + } + + private void addJoint(PhysicsJoint joint) { + if(physicsJoints.containsKey(joint.getObjectId())){ + logger.log(Level.WARNING, "Joint {0} already exists in PhysicsSpace, cannot add.", joint); + return; + } + logger.log(Level.FINE, "Adding Joint {0} to physics space.", joint.getObjectId()); + physicsJoints.put(joint.getObjectId(), joint); + dynamicsWorld.addConstraint(joint.getObjectId(), !joint.isCollisionBetweenLinkedBodys()); + } + + private void removeJoint(PhysicsJoint joint) { + if(!physicsJoints.containsKey(joint.getObjectId())){ + logger.log(Level.WARNING, "Joint {0} does not exist in PhysicsSpace, cannot remove.", joint); + return; + } + logger.log(Level.FINE, "Removing Joint {0} from physics space.", joint.getObjectId()); + physicsJoints.remove(joint.getObjectId()); + dynamicsWorld.removeConstraint(joint.getObjectId()); + } + + public Collection getRigidBodyList(){ + return new LinkedList(physicsBodies.values()); + } + + public Collection getGhostObjectList(){ + return new LinkedList(physicsGhostObjects.values()); + } + + public Collection getCharacterList(){ + return new LinkedList(physicsCharacters.values()); + } + + public Collection getJointList(){ + return new LinkedList(physicsJoints.values()); + } + + public Collection getVehicleList(){ + return new LinkedList(physicsVehicles.values()); + } + + /** + * Sets the gravity of the PhysicsSpace, set before adding physics objects! + * @param gravity + */ + public void setGravity(Vector3f gravity) { + dynamicsWorld.setGravity(Converter.convert(gravity)); + } + + /** + * Gets the gravity of the PhysicsSpace + * @param gravity + */ + public Vector3f getGravity(Vector3f gravity) { + javax.vecmath.Vector3f tempVec = new javax.vecmath.Vector3f(); + dynamicsWorld.getGravity(tempVec); + return Converter.convert(tempVec, 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.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.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() { + physicsBodies.clear(); + physicsJoints.clear(); + + dynamicsWorld.destroy(); + dynamicsWorld = null; + } + + /** + * used internally + * @return the dynamicsWorld + */ + public DynamicsWorld getDynamicsWorld() { + return dynamicsWorld; + } + + public BroadphaseType getBroadphaseType() { + return broadphaseType; + } + + public void setBroadphaseType(BroadphaseType broadphaseType) { + this.broadphaseType = broadphaseType; + } + + /** + * Sets the maximum amount of extra steps that will be used to step the physics + * when the fps is below the physics fps. Doing this maintains determinism in physics. + * For example a maximum number of 2 can compensate for framerates as low as 30fps + * when the physics has the default accuracy of 60 fps. Note that setting this + * value too high can make the physics drive down its own fps in case its overloaded. + * @param steps The maximum number of extra steps, default is 4. + */ + public void setMaxSubSteps(int steps) { + maxSubSteps = steps; + } + + /** + * get the current accuracy of the physics computation + * @return the current accuracy + */ + public float getAccuracy() { + return accuracy; + } + + /** + * sets the accuracy of the physics computation, default=1/60s
      + * @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. + * + * @deprecated in favor of BulletDebugAppState, use + * BulletAppState.setDebugEnabled(boolean) to add automatically + * @param manager AssetManager to use to create debug materials + */ + @Deprecated + 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/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java new file mode 100644 index 000000000..4b1ec5836 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +import com.bulletphysics.collision.narrowphase.ManifoldPoint; +import com.jme3.bullet.util.Converter; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.util.EventObject; + +/** + * A CollisionEvent stores all information about a collision in the PhysicsWorld. + * Do not store this Object, as it will be reused after the collision() method has been called. + * Get/reference all data you need in the collide method. + * @author normenhansen + */ +public class PhysicsCollisionEvent extends EventObject { + + public static final int TYPE_ADDED = 0; + public static final int TYPE_PROCESSED = 1; + public static final int TYPE_DESTROYED = 2; + private int type; + private PhysicsCollisionObject nodeA; + private PhysicsCollisionObject nodeB; + private ManifoldPoint cp; + + public PhysicsCollisionEvent(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, ManifoldPoint cp) { + super(source); + this.type = type; + this.nodeA = source; + this.nodeB = nodeB; + this.cp = cp; + } + + /** + * used by event factory, called when event is destroyed + */ + public void clean() { + source = null; + type = 0; + nodeA = null; + nodeB = null; + cp = null; + } + + /** + * used by event factory, called when event reused + */ + public void refactor(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, ManifoldPoint cp) { + this.source = source; + this.type = type; + this.nodeA = source; + this.nodeB = nodeB; + this.cp = cp; + } + + public int getType() { + return type; + } + + /** + * @return A Spatial if the UserObject of the PhysicsCollisionObject is a Spatial + */ + public Spatial getNodeA() { + if (nodeA.getUserObject() instanceof Spatial) { + return (Spatial) nodeA.getUserObject(); + } + return null; + } + + /** + * @return A Spatial if the UserObject of the PhysicsCollisionObject is a Spatial + */ + public Spatial getNodeB() { + if (nodeB.getUserObject() instanceof Spatial) { + return (Spatial) nodeB.getUserObject(); + } + return null; + } + + public PhysicsCollisionObject getObjectA() { + return nodeA; + } + + public PhysicsCollisionObject getObjectB() { + return nodeB; + } + + public float getAppliedImpulse() { + return cp.appliedImpulse; + } + + public float getAppliedImpulseLateral1() { + return cp.appliedImpulseLateral1; + } + + public float getAppliedImpulseLateral2() { + return cp.appliedImpulseLateral2; + } + + public float getCombinedFriction() { + return cp.combinedFriction; + } + + public float getCombinedRestitution() { + return cp.combinedRestitution; + } + + public float getDistance1() { + return cp.distance1; + } + + public int getIndex0() { + return cp.index0; + } + + public int getIndex1() { + return cp.index1; + } + + public Vector3f getLateralFrictionDir1() { + return Converter.convert(cp.lateralFrictionDir1); + } + + public Vector3f getLateralFrictionDir2() { + return Converter.convert(cp.lateralFrictionDir2); + } + + public boolean isLateralFrictionInitialized() { + return cp.lateralFrictionInitialized; + } + + public int getLifeTime() { + return cp.lifeTime; + } + + public Vector3f getLocalPointA() { + return Converter.convert(cp.localPointA); + } + + public Vector3f getLocalPointB() { + return Converter.convert(cp.localPointB); + } + + public Vector3f getNormalWorldOnB() { + return Converter.convert(cp.normalWorldOnB); + } + + public int getPartId0() { + return cp.partId0; + } + + public int getPartId1() { + return cp.partId1; + } + + public Vector3f getPositionWorldOnA() { + return Converter.convert(cp.positionWorldOnA); + } + + public Vector3f getPositionWorldOnB() { + return Converter.convert(cp.positionWorldOnB); + } + + public Object getUserPersistentData() { + return cp.userPersistentData; + } +} diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java new file mode 100644 index 000000000..07357c0ed --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +import com.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/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java new file mode 100644 index 000000000..0416799b8 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.export.*; +import java.io.IOException; + +/** + * Base class for collision objects (PhysicsRigidBody, PhysicsGhostObject) + * @author normenhansen + */ +public abstract class PhysicsCollisionObject implements Savable { + + 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; + } + + /** + * @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 collisionGroups + */ + public void setCollideWithGroups(int collisionGroups) { + this.collisionGroupsMask = collisionGroups; + } + + /** + * Gets the bitmask of collision groups that this object collides with. + * @return + */ + public int getCollideWithGroups() { + return collisionGroupsMask; + } + + /** + * @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(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); + CollisionShape shape = (CollisionShape) capsule.readSavable("collisionShape", null); + collisionShape = shape; + } +} diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java new file mode 100644 index 000000000..5652edf65 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +import com.jme3.math.Vector3f; + +/** + * Contains the results of a PhysicsSpace rayTest. + * Read data only in callback method, object is reused + * @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 PhysicsObject the ray collided with + */ + public PhysicsCollisionObject getCollisionObject() { + return collisionObject; + } + + /** + * @return the normal of the collision in the objects local space + */ + public Vector3f getHitNormalLocal() { + return hitNormalLocal; + } + + /** + * The hitFraction is the fraction of the ray length (yeah, I know) at which the collision occurred. + * If e.g. the raytest was from 0,0,0 to 0,6,0 and the hitFraction is 0.5 then the collision occurred at 0,3,0 + * @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/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsSweepTestResult.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsSweepTestResult.java new file mode 100644 index 000000000..8e61b66a2 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsSweepTestResult.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision; + +import com.jme3.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/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java new file mode 100644 index 000000000..e26193c4f --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.bulletphysics.collision.shapes.BoxShape; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * Basic box collision shape + * @author normenhansen + */ +public class BoxCollisionShape extends CollisionShape { + + private Vector3f halfExtents; + + public BoxCollisionShape() { + } + + /** + * creates a collision box from the given halfExtents + * @param halfExtents the halfExtents of the CollisionBox + */ + public BoxCollisionShape(Vector3f halfExtents) { + this.halfExtents = halfExtents; + createShape(); + } + + public final Vector3f getHalfExtents() { + return halfExtents; + } + + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(halfExtents, "halfExtents", new Vector3f(1, 1, 1)); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + Vector3f halfExtents = (Vector3f) capsule.readSavable("halfExtents", new Vector3f(1, 1, 1)); + this.halfExtents = halfExtents; + createShape(); + } + + protected void createShape() { + cShape = new BoxShape(Converter.convert(halfExtents)); + cShape.setLocalScaling(Converter.convert(getScale())); + cShape.setMargin(margin); + } + +} diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java new file mode 100644 index 000000000..a8fce13eb --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.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 com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Basic capsule collision shape + * @author normenhansen + */ +public class CapsuleCollisionShape extends CollisionShape{ + protected float radius,height; + protected int axis; + + public CapsuleCollisionShape() { + } + + /** + * Creates a new CapsuleCollisionShape with the given radius and height. + * The capsule is oriented along the Y axis (1). + * @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/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java new file mode 100644 index 000000000..2a4ad596d --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.bullet.util.Converter; +import com.jme3.export.*; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * This Object holds information about a jbullet CollisionShape to be able to reuse + * CollisionShapes (as suggested in bullet manuals) + * TODO: add static methods to create shapes from nodes (like jbullet-jme constructor) + * @author normenhansen + */ +public abstract class CollisionShape implements Savable { + + protected com.bulletphysics.collision.shapes.CollisionShape cShape; + protected Vector3f scale = new Vector3f(1, 1, 1); + protected float margin = 0.0f; + + public CollisionShape() { + } + + /** + * used internally, not safe + */ + public void calculateLocalInertia(float mass, javax.vecmath.Vector3f vector) { + if (cShape == null) { + return; + } + if (this instanceof MeshCollisionShape) { + vector.set(0, 0, 0); + } else { + cShape.calculateLocalInertia(mass, vector); + } + } + + /** + * used internally + */ + public com.bulletphysics.collision.shapes.CollisionShape getCShape() { + return cShape; + } + + /** + * used internally + */ + public void setCShape(com.bulletphysics.collision.shapes.CollisionShape cShape) { + this.cShape = cShape; + } + + public void setScale(Vector3f scale) { + this.scale.set(scale); + cShape.setLocalScaling(Converter.convert(scale)); + } + + public float getMargin() { + return cShape.getMargin(); + } + + public void setMargin(float margin) { + cShape.setMargin(margin); + this.margin = margin; + } + + public Vector3f getScale() { + return scale; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(scale, "scale", new Vector3f(1, 1, 1)); + capsule.write(getMargin(), "margin", 0.0f); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule capsule = im.getCapsule(this); + this.scale = (Vector3f) capsule.readSavable("scale", new Vector3f(1, 1, 1)); + this.margin = capsule.readFloat("margin", 0.0f); + } +} diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java new file mode 100644 index 000000000..5cbc208d4 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.bulletphysics.collision.shapes.CompoundShape; +import com.bulletphysics.linearmath.Transform; +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A CompoundCollisionShape allows combining multiple base shapes + * to generate a more sophisticated shape. + * @author normenhansen + */ +public class CompoundCollisionShape extends CollisionShape { + + protected ArrayList 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/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java new file mode 100644 index 000000000..2dc27dcb1 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.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; + +/** + * Cone collision shape represents a 3D cone with a radius, height, and axis (X, Y or Z). + * + * @author normenhansen + */ +public class ConeCollisionShape extends CollisionShape { + + protected float radius; + protected float height; + protected int axis; + + /** + * Serialization only, do not use. + */ + public ConeCollisionShape() { + } + + /** + * Creates a new cone collision shape with the given height, radius, and axis. + * + * @param radius The radius of the cone in world units. + * @param height The height of the cone in world units. + * @param The axis towards which the cone faces, see the PhysicsSpace.AXIS_* constants. + */ + public ConeCollisionShape(float radius, float height, int axis) { + this.radius = radius; + this.height = height; + this.axis = axis; + if (axis < PhysicsSpace.AXIS_X || axis > PhysicsSpace.AXIS_Z) { + throw new UnsupportedOperationException("axis must be one of the PhysicsSpace.AXIS_* constants!"); + } + createShape(); + } + + /** + * Creates a new cone collision shape with the given height, radius and default Y axis. + * + * @param radius The radius of the cone in world units. + * @param height The height of the cone in world units. + */ + public ConeCollisionShape(float radius, float height) { + this.radius = radius; + this.height = height; + this.axis = PhysicsSpace.AXIS_Y; + 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", 0.5f); + capsule.write(axis, "axis", PhysicsSpace.AXIS_Y); + } + + 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", PhysicsSpace.AXIS_Y); + 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); + } else { + throw new UnsupportedOperationException("Unexpected axis: " + axis); + } + cShape.setLocalScaling(Converter.convert(getScale())); + cShape.setMargin(margin); + } +} diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java new file mode 100644 index 000000000..1a33c97fe --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.bulletphysics.collision.shapes.CylinderShape; +import com.bulletphysics.collision.shapes.CylinderShapeX; +import com.bulletphysics.collision.shapes.CylinderShapeZ; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Basic cylinder collision shape + * @author normenhansen + */ +public class CylinderCollisionShape extends CollisionShape { + + protected Vector3f halfExtents; + protected int axis; + + public CylinderCollisionShape() { + } + + /** + * creates a cylinder shape from the given halfextents + * @param halfExtents the halfextents to use + */ + public CylinderCollisionShape(Vector3f halfExtents) { + this.halfExtents = halfExtents; + this.axis = 2; + createShape(); + } + + /** + * Creates a cylinder shape around the given axis from the given halfextents + * @param halfExtents the halfextents to use + * @param axis (0=X,1=Y,2=Z) + */ + public CylinderCollisionShape(Vector3f halfExtents, int axis) { + this.halfExtents = halfExtents; + this.axis = axis; + createShape(); + } + + public final Vector3f getHalfExtents() { + return halfExtents; + } + + public int getAxis() { + return axis; + } + + /** + * WARNING - CompoundCollisionShape scaling has no effect. + */ + @Override + public void setScale(Vector3f scale) { + Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "CylinderCollisionShape cannot be scaled"); + } + + 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/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java new file mode 100644 index 000000000..5a7921fc0 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.bulletphysics.collision.shapes.IndexedMesh; +import com.bulletphysics.collision.shapes.TriangleIndexVertexArray; +import com.bulletphysics.extras.gimpact.GImpactMeshShape; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Basic mesh collision shape + * @author normenhansen + */ +public class GImpactCollisionShape extends CollisionShape{ + + protected Vector3f worldScale; + protected int numVertices, numTriangles, vertexStride, triangleIndexStride; + protected ByteBuffer triangleIndexBase, vertexBase; + protected IndexedMesh bulletMesh; + + public GImpactCollisionShape() { + } + + /** + * creates a collision shape from the given Mesh + * @param mesh the Mesh to use + */ + public GImpactCollisionShape(Mesh mesh) { + createCollisionMesh(mesh, new Vector3f(1,1,1)); + } + + + private void createCollisionMesh(Mesh mesh, Vector3f worldScale) { + this.worldScale = worldScale; + bulletMesh = Converter.convert(mesh); + this.numVertices = bulletMesh.numVertices; + this.numTriangles = bulletMesh.numTriangles; + this.vertexStride = bulletMesh.vertexStride; + this.triangleIndexStride = bulletMesh.triangleIndexStride; + this.triangleIndexBase = bulletMesh.triangleIndexBase; + this.vertexBase = bulletMesh.vertexBase; + createShape(); + } + + /** + * creates a jme mesh from the collision shape, only needed for debugging + */ + public Mesh createJmeMesh(){ + return Converter.convert(bulletMesh); + } + + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(worldScale, "worldScale", new Vector3f(1, 1, 1)); + capsule.write(numVertices, "numVertices", 0); + capsule.write(numTriangles, "numTriangles", 0); + capsule.write(vertexStride, "vertexStride", 0); + capsule.write(triangleIndexStride, "triangleIndexStride", 0); + + capsule.write(triangleIndexBase.array(), "triangleIndexBase", new byte[0]); + capsule.write(vertexBase.array(), "vertexBase", new byte[0]); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + worldScale = (Vector3f) capsule.readSavable("worldScale", new Vector3f(1, 1, 1)); + numVertices = capsule.readInt("numVertices", 0); + numTriangles = capsule.readInt("numTriangles", 0); + vertexStride = capsule.readInt("vertexStride", 0); + triangleIndexStride = capsule.readInt("triangleIndexStride", 0); + + triangleIndexBase = ByteBuffer.wrap(capsule.readByteArray("triangleIndexBase", new byte[0])); + vertexBase = ByteBuffer.wrap(capsule.readByteArray("vertexBase", new byte[0])); + createShape(); + } + + protected void createShape() { + bulletMesh = new IndexedMesh(); + bulletMesh.numVertices = numVertices; + bulletMesh.numTriangles = numTriangles; + bulletMesh.vertexStride = vertexStride; + bulletMesh.triangleIndexStride = triangleIndexStride; + bulletMesh.triangleIndexBase = triangleIndexBase; + bulletMesh.vertexBase = vertexBase; + bulletMesh.triangleIndexBase = triangleIndexBase; + TriangleIndexVertexArray tiv = new TriangleIndexVertexArray(numTriangles, triangleIndexBase, triangleIndexStride, numVertices, vertexBase, vertexStride); + cShape = new GImpactMeshShape(tiv); + cShape.setLocalScaling(Converter.convert(worldScale)); + ((GImpactMeshShape)cShape).updateBound(); + cShape.setLocalScaling(Converter.convert(getScale())); + cShape.setMargin(margin); + } + +} diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java new file mode 100644 index 000000000..c4665823e --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.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/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java new file mode 100644 index 000000000..edce38409 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.bulletphysics.collision.shapes.ConvexHullShape; +import com.bulletphysics.util.ObjectArrayList; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import java.io.IOException; +import java.nio.FloatBuffer; +import javax.vecmath.Vector3f; + +public class HullCollisionShape extends CollisionShape { + + private float[] points; + + public HullCollisionShape() { + } + + public HullCollisionShape(Mesh mesh) { + this.points = getPoints(mesh); + createShape(this.points); + } + + public HullCollisionShape(float[] points) { + this.points = points; + createShape(this.points); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(points, "points", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + + // for backwards compatability + Mesh mesh = (Mesh) capsule.readSavable("hullMesh", null); + if (mesh != null) { + this.points = getPoints(mesh); + } else { + this.points = capsule.readFloatArray("points", null); + + } + createShape(this.points); + } + + protected void createShape(float[] points) { + ObjectArrayList 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/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java new file mode 100644 index 000000000..0feb4753e --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.bulletphysics.collision.shapes.BvhTriangleMeshShape; +import com.bulletphysics.collision.shapes.IndexedMesh; +import com.bulletphysics.collision.shapes.TriangleIndexVertexArray; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Basic mesh collision shape + * @author normenhansen + */ +public class MeshCollisionShape extends CollisionShape { + + protected int numVertices, numTriangles, vertexStride, triangleIndexStride; + protected ByteBuffer triangleIndexBase, vertexBase; + protected IndexedMesh bulletMesh; + + public MeshCollisionShape() { + } + + /** + * Creates a collision shape from the given TriMesh + * + * @param mesh + * the TriMesh to use + */ + public MeshCollisionShape(Mesh mesh) { + this(mesh, false); + } + + /** + * API compatibility with native bullet. + * + * @param mesh the TriMesh to use + * @param dummy Unused + */ + public MeshCollisionShape(Mesh mesh, boolean dummy) { + 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/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java new file mode 100644 index 000000000..5c91e10e1 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.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/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java new file mode 100644 index 000000000..4f8a352a0 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.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/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java new file mode 100644 index 000000000..fb6976aa3 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.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 com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Basic sphere collision shape + * @author normenhansen + */ +public class SphereCollisionShape extends CollisionShape { + + protected float radius; + + public SphereCollisionShape() { + } + + /** + * creates a SphereCollisionShape with the given radius + * @param radius + */ + public SphereCollisionShape(float radius) { + this.radius = radius; + createShape(); + } + + public float getRadius() { + return radius; + } + + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(radius, "radius", 0.5f); + } + + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule capsule = im.getCapsule(this); + radius = capsule.readFloat("radius", 0.5f); + createShape(); + } + + protected void createShape() { + cShape = new SphereShape(radius); + cShape.setLocalScaling(Converter.convert(getScale())); + cShape.setMargin(margin); + } + +} diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java new file mode 100644 index 000000000..45afa9d1a --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.bulletphysics.dynamics.constraintsolver.ConeTwistConstraint; +import com.bulletphysics.linearmath.Transform; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * 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/jme3-jbullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java new file mode 100644 index 000000000..96dea1d72 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.bulletphysics.dynamics.constraintsolver.HingeConstraint; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * 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(); + } + + /** + * Enables the motor. + * @param enable if true, motor is enabled. + * @param targetVelocity the target velocity of the rotation. + * @param maxMotorImpulse the max force applied to the hinge to rotate it. + */ + public void enableMotor(boolean enable, float targetVelocity, float maxMotorImpulse) { + ((HingeConstraint) constraint).enableAngularMotor(enable, targetVelocity, maxMotorImpulse); + } + + /** + * Sets the limits of this joint. + * @param low the low limit in radians. + * @param high the high limit in radians. + */ + public void setLimit(float low, float high) { + ((HingeConstraint) constraint).setLimit(low, high); + } + + /** + * Sets the limits of this joint. + * If you're above the softness, velocities that would shoot through the actual limit are slowed down. The bias be in the range of 0.2 - 0.5. + * @param low the low limit in radians. + * @param high the high limit in radians. + * @param _softness the factor at which the velocity error correction starts operating,i.e a softness of 0.9 means that the vel. corr starts at 90% of the limit range. + * @param _biasFactor the magnitude of the position correction. It tells you how strictly the position error (drift ) is corrected. + * @param _relaxationFactor the rate at which velocity errors are corrected. This can be seen as the strength of the limits. A low value will make the the limits more spongy. + */ + 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/jme3-jbullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java new file mode 100644 index 000000000..543209a0e --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.bulletphysics.dynamics.constraintsolver.TypedConstraint; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.export.*; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + *

      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 getObjectId() { + 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/jme3-jbullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java new file mode 100644 index 000000000..894d1c8f6 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.bulletphysics.dynamics.constraintsolver.Point2PointConstraint; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * 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/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java new file mode 100644 index 000000000..afb45b487 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.bulletphysics.dynamics.constraintsolver.Generic6DofConstraint; +import com.bulletphysics.linearmath.Transform; +import com.jme3.bullet.joints.motors.RotationalLimitMotor; +import com.jme3.bullet.joints.motors.TranslationalLimitMotor; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedList; + +/** + * 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/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java new file mode 100644 index 000000000..5adf0b8b2 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +import com.bulletphysics.dynamics.constraintsolver.SliderConstraint; +import com.bulletphysics.linearmath.Transform; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * 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/jme3-jbullet/src/main/java/com/jme3/bullet/joints/motors/RotationalLimitMotor.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/motors/RotationalLimitMotor.java new file mode 100644 index 000000000..b1d4e3c80 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/motors/RotationalLimitMotor.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.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/jme3-jbullet/src/main/java/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java new file mode 100644 index 000000000..38e663f91 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints.motors; + +import com.jme3.bullet.util.Converter; +import com.jme3.math.Vector3f; + +/** + * + * @author normenhansen + */ +public class TranslationalLimitMotor { + + private com.bulletphysics.dynamics.constraintsolver.TranslationalLimitMotor motor; + + public TranslationalLimitMotor(com.bulletphysics.dynamics.constraintsolver.TranslationalLimitMotor motor) { + this.motor = motor; + } + + public com.bulletphysics.dynamics.constraintsolver.TranslationalLimitMotor getMotor() { + return motor; + } + + public Vector3f getLowerLimit() { + return Converter.convert(motor.lowerLimit); + } + + public void setLowerLimit(Vector3f lowerLimit) { + Converter.convert(lowerLimit, motor.lowerLimit); + } + + public Vector3f getUpperLimit() { + return Converter.convert(motor.upperLimit); + } + + public void setUpperLimit(Vector3f upperLimit) { + Converter.convert(upperLimit, motor.upperLimit); + } + + public Vector3f getAccumulatedImpulse() { + return Converter.convert(motor.accumulatedImpulse); + } + + public void setAccumulatedImpulse(Vector3f accumulatedImpulse) { + Converter.convert(accumulatedImpulse, motor.accumulatedImpulse); + } + + public float getLimitSoftness() { + return motor.limitSoftness; + } + + public void setLimitSoftness(float limitSoftness) { + motor.limitSoftness = limitSoftness; + } + + public float getDamping() { + return motor.damping; + } + + public void setDamping(float damping) { + motor.damping = damping; + } + + public float getRestitution() { + return motor.restitution; + } + + public void setRestitution(float restitution) { + motor.restitution = restitution; + } +} diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java new file mode 100644 index 000000000..c057d99d2 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects; + +import com.bulletphysics.collision.dispatch.CollisionFlags; +import com.bulletphysics.collision.dispatch.PairCachingGhostObject; +import com.bulletphysics.collision.shapes.ConvexShape; +import com.bulletphysics.dynamics.character.KinematicCharacterController; +import com.bulletphysics.linearmath.Transform; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * Basic Bullet Character + * @author normenhansen + */ +public class PhysicsCharacter extends PhysicsCollisionObject { + + protected KinematicCharacterController character; + protected float stepHeight; + protected Vector3f walkDirection = new Vector3f(); + protected float fallSpeed = 55.0f; + protected float jumpSpeed = 10.0f; + protected int upAxis = 1; + protected PairCachingGhostObject gObject; + protected boolean locationDirty = false; + //TEMP VARIABLES + protected final Quaternion tmp_inverseWorldRotation = new Quaternion(); + private Transform tempTrans = new Transform(Converter.convert(new Matrix3f())); + private com.jme3.math.Transform physicsLocation = new com.jme3.math.Transform(); + private javax.vecmath.Vector3f tempVec = new javax.vecmath.Vector3f(); + + public PhysicsCharacter() { + } + + /** + * @param shape The CollisionShape (no Mesh or CompoundCollisionShapes) + * @param stepHeight The quantization size for vertical movement + */ + public PhysicsCharacter(CollisionShape shape, float stepHeight) { + this.collisionShape = shape; + if (!(shape.getCShape() instanceof ConvexShape)) { + throw (new UnsupportedOperationException("Kinematic character nodes cannot have mesh collision shapes")); + } + this.stepHeight = stepHeight; + buildObject(); + } + + protected void buildObject() { + if (gObject == null) { + gObject = new PairCachingGhostObject(); + } + gObject.setCollisionFlags(CollisionFlags.CHARACTER_OBJECT); + gObject.setCollisionFlags(gObject.getCollisionFlags() & ~CollisionFlags.NO_CONTACT_RESPONSE); + gObject.setCollisionShape(collisionShape.getCShape()); + gObject.setUserPointer(this); + character = new KinematicCharacterController(gObject, (ConvexShape) collisionShape.getCShape(), stepHeight); + } + + /** + * Sets the location of this physics character + * @param location + */ + public void warp(Vector3f location) { + character.warp(Converter.convert(location, tempVec)); + } + + /** + * Set the walk direction, works continuously. + * This should probably be called setPositionIncrementPerSimulatorStep. + * This is neither a direction nor a velocity, but the amount to + * increment the position each physics tick. So vector length = accuracy*speed in m/s + * @param vec the walk direction to set + */ + public void setWalkDirection(Vector3f vec) { + walkDirection.set(vec); + character.setWalkDirection(Converter.convert(walkDirection, tempVec)); + } + + /** + * @return the currently set walkDirection + */ + public Vector3f getWalkDirection() { + return walkDirection; + } + + public void setUpAxis(int axis) { + upAxis = axis; + character.setUpAxis(axis); + } + + public int getUpAxis() { + return upAxis; + } + + public void setFallSpeed(float fallSpeed) { + this.fallSpeed = fallSpeed; + character.setFallSpeed(fallSpeed); + } + + public float getFallSpeed() { + return fallSpeed; + } + + public void setJumpSpeed(float jumpSpeed) { + this.jumpSpeed = jumpSpeed; + character.setJumpSpeed(jumpSpeed); + } + + public float getJumpSpeed() { + return jumpSpeed; + } + + //does nothing.. +// public void setMaxJumpHeight(float height) { +// character.setMaxJumpHeight(height); +// } + public void setGravity(float value) { + character.setGravity(value); + } + + public float getGravity() { + return character.getGravity(); + } + + public void setMaxSlope(float slopeRadians) { + character.setMaxSlope(slopeRadians); + } + + public float getMaxSlope() { + return character.getMaxSlope(); + } + + public boolean onGround() { + return character.onGround(); + } + + public void jump() { + character.jump(); + } + + @Override + public void setCollisionShape(CollisionShape collisionShape) { + if (!(collisionShape.getCShape() instanceof ConvexShape)) { + throw (new UnsupportedOperationException("Kinematic character nodes cannot have mesh collision shapes")); + } + super.setCollisionShape(collisionShape); + if (gObject == null) { + buildObject(); + }else{ + gObject.setCollisionShape(collisionShape.getCShape()); + } + } + + /** + * Set the physics location (same as warp()) + * @param location the location of the actual physics object + */ + public void setPhysicsLocation(Vector3f location) { + warp(location); + } + + /** + * @return the physicsLocation + */ + public Vector3f getPhysicsLocation(Vector3f trans) { + if (trans == null) { + trans = new Vector3f(); + } + gObject.getWorldTransform(tempTrans); + Converter.convert(tempTrans.origin, physicsLocation.getTranslation()); + return trans.set(physicsLocation.getTranslation()); + } + + /** + * @return the physicsLocation + */ + public Vector3f getPhysicsLocation() { + gObject.getWorldTransform(tempTrans); + Converter.convert(tempTrans.origin, physicsLocation.getTranslation()); + return physicsLocation.getTranslation(); + } + + public void setCcdSweptSphereRadius(float radius) { + gObject.setCcdSweptSphereRadius(radius); + } + + public void setCcdMotionThreshold(float threshold) { + gObject.setCcdMotionThreshold(threshold); + } + + public float getCcdSweptSphereRadius() { + return gObject.getCcdSweptSphereRadius(); + } + + public float getCcdMotionThreshold() { + return gObject.getCcdMotionThreshold(); + } + + public float getCcdSquareMotionThreshold() { + return gObject.getCcdSquareMotionThreshold(); + } + + /** + * used internally + */ + public KinematicCharacterController getControllerId() { + return character; + } + + /** + * used internally + */ + public PairCachingGhostObject getObjectId() { + return gObject; + } + + public void destroy() { + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(stepHeight, "stepHeight", 1.0f); + capsule.write(getGravity(), "gravity", 9.8f * 3); + capsule.write(getMaxSlope(), "maxSlope", 1.0f); + capsule.write(fallSpeed, "fallSpeed", 55.0f); + capsule.write(jumpSpeed, "jumpSpeed", 10.0f); + capsule.write(upAxis, "upAxis", 1); + capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0); + capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0); + capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f()); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + stepHeight = capsule.readFloat("stepHeight", 1.0f); + buildObject(); + character = new KinematicCharacterController(gObject, (ConvexShape) collisionShape.getCShape(), stepHeight); + setGravity(capsule.readFloat("gravity", 9.8f * 3)); + setMaxSlope(capsule.readFloat("maxSlope", 1.0f)); + setFallSpeed(capsule.readFloat("fallSpeed", 55.0f)); + setJumpSpeed(capsule.readFloat("jumpSpeed", 10.0f)); + setUpAxis(capsule.readInt("upAxis", 1)); + setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0)); + setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0)); + setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f())); + } +} diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java new file mode 100644 index 000000000..19c08ddd2 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects; + +import com.bulletphysics.collision.dispatch.CollisionFlags; +import com.bulletphysics.collision.dispatch.PairCachingGhostObject; +import com.bulletphysics.linearmath.Transform; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +/** + * 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/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java new file mode 100644 index 000000000..6798c63fd --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java @@ -0,0 +1,668 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects; + +import com.bulletphysics.collision.dispatch.CollisionFlags; +import com.bulletphysics.dynamics.RigidBody; +import com.bulletphysics.dynamics.RigidBodyConstructionInfo; +import com.bulletphysics.linearmath.Transform; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.infos.RigidBodyMotionState; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.ArrayList; +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 shape + */ + public PhysicsRigidBody(CollisionShape shape) { + collisionShape = shape; + rebuildRigidBody(); + } + + public PhysicsRigidBody(CollisionShape shape, float mass) { + collisionShape = shape; + this.mass = mass; + rebuildRigidBody(); + } + + /** + * Builds/rebuilds the phyiscs body when parameters have changed + */ + protected void rebuildRigidBody() { + boolean removed = false; + if(collisionShape instanceof MeshCollisionShape && mass != 0){ + throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!"); + } + if (rBody != null) { + if (rBody.isInWorld()) { + PhysicsSpace.getPhysicsSpace().remove(this); + removed = true; + } + rBody.destroy(); + } + preRebuild(); + rBody = new RigidBody(constructionInfo); + postRebuild(); + if (removed) { + PhysicsSpace.getPhysicsSpace().add(this); + } + } + + protected void preRebuild() { + collisionShape.calculateLocalInertia(mass, localInertia); + if (constructionInfo == null) { + constructionInfo = new RigidBodyConstructionInfo(mass, motionState, collisionShape.getCShape(), localInertia); + } else { + constructionInfo.mass = mass; + constructionInfo.collisionShape = collisionShape.getCShape(); + constructionInfo.motionState = motionState; + } + } + + protected void postRebuild() { + rBody.setUserPointer(this); + if (mass == 0.0f) { + rBody.setCollisionFlags(rBody.getCollisionFlags() | CollisionFlags.STATIC_OBJECT); + } else { + rBody.setCollisionFlags(rBody.getCollisionFlags() & ~CollisionFlags.STATIC_OBJECT); + } + } + + /** + * @return the motionState + */ + public RigidBodyMotionState getMotionState() { + return motionState; + } + + /** + * Sets the physics object location + * @param location the location of the actual physics object + */ + public void setPhysicsLocation(Vector3f location) { + rBody.getCenterOfMassTransform(tempTrans); + Converter.convert(location, tempTrans.origin); + rBody.setCenterOfMassTransform(tempTrans); + motionState.setWorldTransform(tempTrans); + } + + /** + * Sets the physics object rotation + * @param rotation the rotation of the actual physics object + */ + public void setPhysicsRotation(Matrix3f rotation) { + rBody.getCenterOfMassTransform(tempTrans); + Converter.convert(rotation, tempTrans.basis); + rBody.setCenterOfMassTransform(tempTrans); + motionState.setWorldTransform(tempTrans); + } + + /** + * Sets the physics object rotation + * @param rotation the rotation of the actual physics object + */ + public void setPhysicsRotation(Quaternion rotation) { + rBody.getCenterOfMassTransform(tempTrans); + Converter.convert(rotation, tempTrans.basis); + rBody.setCenterOfMassTransform(tempTrans); + motionState.setWorldTransform(tempTrans); + } + + /** + * Gets the physics object location, instantiates a new Vector3f object + */ + public Vector3f getPhysicsLocation() { + return getPhysicsLocation(null); + } + + /** + * Gets the physics object rotation + */ + public Matrix3f getPhysicsRotationMatrix() { + return getPhysicsRotationMatrix(null); + } + + /** + * Gets the physics object location, no object instantiation + * @param location the location of the actual physics object is stored in this Vector3f + */ + public Vector3f getPhysicsLocation(Vector3f location) { + if (location == null) { + location = new Vector3f(); + } + rBody.getCenterOfMassTransform(tempTrans); + return Converter.convert(tempTrans.origin, location); + } + + /** + * Gets the physics object rotation as a matrix, no conversions and no object instantiation + * @param rotation the rotation of the actual physics object is stored in this Matrix3f + */ + public Matrix3f getPhysicsRotationMatrix(Matrix3f rotation) { + if (rotation == null) { + rotation = new Matrix3f(); + } + rBody.getCenterOfMassTransform(tempTrans); + return Converter.convert(tempTrans.basis, rotation); + } + + /** + * Gets the physics object rotation as a quaternion, converts the bullet Matrix3f value, + * instantiates new object + */ + public Quaternion getPhysicsRotation(){ + return getPhysicsRotation(null); + } + + /** + * Gets the physics object rotation as a quaternion, converts the bullet Matrix3f value + * @param rotation the rotation of the actual physics object is stored in this Quaternion + */ + public Quaternion getPhysicsRotation(Quaternion rotation){ + if (rotation == null) { + rotation = new Quaternion(); + } + rBody.getCenterOfMassTransform(tempTrans); + return Converter.convert(tempTrans.basis, rotation); + } + + /** + * Gets the physics object location + * @param location the location of the actual physics object is stored in this Vector3f + */ + public Vector3f getInterpolatedPhysicsLocation(Vector3f location) { + if (location == null) { + location = new Vector3f(); + } + rBody.getInterpolationWorldTransform(tempTrans); + return Converter.convert(tempTrans.origin, location); + } + + /** + * Gets the physics object rotation + * @param rotation the rotation of the actual physics object is stored in this Matrix3f + */ + public Matrix3f getInterpolatedPhysicsRotation(Matrix3f rotation) { + if (rotation == null) { + rotation = new Matrix3f(); + } + rBody.getInterpolationWorldTransform(tempTrans); + return Converter.convert(tempTrans.basis, rotation); + } + + /** + * Sets the node to kinematic mode. in this mode the node is not affected by physics + * but affects other physics objects. Its kinetic force is calculated by the amount + * of movement it is exposed to and its weight. + * @param kinematic + */ + public void setKinematic(boolean kinematic) { + this.kinematic = kinematic; + if (kinematic) { + rBody.setCollisionFlags(rBody.getCollisionFlags() | CollisionFlags.KINEMATIC_OBJECT); + rBody.setActivationState(com.bulletphysics.collision.dispatch.CollisionObject.DISABLE_DEACTIVATION); + } else { + rBody.setCollisionFlags(rBody.getCollisionFlags() & ~CollisionFlags.KINEMATIC_OBJECT); + rBody.setActivationState(com.bulletphysics.collision.dispatch.CollisionObject.ACTIVE_TAG); + } + } + + public boolean isKinematic() { + return kinematic; + } + + public void setCcdSweptSphereRadius(float radius) { + rBody.setCcdSweptSphereRadius(radius); + } + + /** + * Sets the amount of motion that has to happen in one physics tick to trigger the continuous motion detection
      + * This avoids the problem of fast objects moving through other objects, set to zero to disable (default) + * @param threshold + */ + public void setCcdMotionThreshold(float threshold) { + rBody.setCcdMotionThreshold(threshold); + } + + public float getCcdSweptSphereRadius() { + return rBody.getCcdSweptSphereRadius(); + } + + public float getCcdMotionThreshold() { + return rBody.getCcdMotionThreshold(); + } + + public float getCcdSquareMotionThreshold() { + return rBody.getCcdSquareMotionThreshold(); + } + + public float getMass() { + return mass; + } + + /** + * Sets the mass of this PhysicsRigidBody, objects with mass=0 are static. + * @param mass + */ + public void setMass(float mass) { + this.mass = mass; + if(collisionShape instanceof MeshCollisionShape && mass != 0){ + throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!"); + } + if (collisionShape != null) { + collisionShape.calculateLocalInertia(mass, localInertia); + } + if (rBody != null) { + rBody.setMassProps(mass, localInertia); + if (mass == 0.0f) { + rBody.setCollisionFlags(rBody.getCollisionFlags() | CollisionFlags.STATIC_OBJECT); + } else { + rBody.setCollisionFlags(rBody.getCollisionFlags() & ~CollisionFlags.STATIC_OBJECT); + } + } + } + + public Vector3f getGravity() { + return getGravity(null); + } + + public Vector3f getGravity(Vector3f gravity) { + if (gravity == null) { + gravity = new Vector3f(); + } + rBody.getGravity(tempVec); + return Converter.convert(tempVec, gravity); + } + + /** + * Set the local gravity of this PhysicsRigidBody
      + * 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(collisionShape instanceof MeshCollisionShape && mass!=0){ + throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!"); + } + if (rBody == null) { + rebuildRigidBody(); + } else { + collisionShape.calculateLocalInertia(mass, localInertia); + constructionInfo.collisionShape = collisionShape.getCShape(); + rBody.setCollisionShape(collisionShape.getCShape()); + } + } + + /** + * reactivates this PhysicsRigidBody when it has been deactivated because it was not moving + */ + public void activate() { + rBody.activate(); + } + + public boolean isActive() { + return rBody.isActive(); + } + + /** + * sets the sleeping thresholds, these define when the object gets deactivated + * to save ressources. Low values keep the object active when it barely moves + * @param linear the linear sleeping threshold + * @param angular the angular sleeping threshold + */ + public void setSleepingThresholds(float linear, float angular) { + constructionInfo.linearSleepingThreshold = linear; + constructionInfo.angularSleepingThreshold = angular; + rBody.setSleepingThresholds(linear, angular); + } + + public void setLinearSleepingThreshold(float linearSleepingThreshold) { + constructionInfo.linearSleepingThreshold = linearSleepingThreshold; + rBody.setSleepingThresholds(linearSleepingThreshold, constructionInfo.angularSleepingThreshold); + } + + public void setAngularSleepingThreshold(float angularSleepingThreshold) { + constructionInfo.angularSleepingThreshold = angularSleepingThreshold; + rBody.setSleepingThresholds(constructionInfo.linearSleepingThreshold, angularSleepingThreshold); + } + + public float getLinearSleepingThreshold() { + return constructionInfo.linearSleepingThreshold; + } + + public float getAngularSleepingThreshold() { + return constructionInfo.angularSleepingThreshold; + } + + public float getAngularFactor() { + return rBody.getAngularFactor(); + } + + public void setAngularFactor(float factor) { + rBody.setAngularFactor(factor); + } + + /** + * do not use manually, joints are added automatically + */ + public void addJoint(PhysicsJoint joint) { + if (!joints.contains(joint)) { + joints.add(joint); + } + } + + /** + * + */ + 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 + 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/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java new file mode 100644 index 000000000..5cebd5edb --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects; + +import com.bulletphysics.collision.dispatch.CollisionObject; +import com.bulletphysics.dynamics.vehicle.*; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.util.Converter; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.Arrow; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; + +/** + *

      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); + 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(); + } + + /** + * 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 The object to write the forward vector values to. + * Passing null will cause a new {@link Vector3f) to be created. + * @return The forward vector + */ + 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 + 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/jme3-jbullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java new file mode 100644 index 000000000..0ddbbb852 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects; + +import com.bulletphysics.dynamics.RigidBody; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.util.Converter; +import com.jme3.export.*; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.io.IOException; + +/** + * Stores info about one wheel of a PhysicsVehicle + * @author normenhansen + */ +public class VehicleWheel implements Savable { + + protected com.bulletphysics.dynamics.vehicle.WheelInfo wheelInfo; + protected boolean frontWheel; + protected Vector3f location = new Vector3f(); + protected Vector3f direction = new Vector3f(); + protected Vector3f axle = new Vector3f(); + protected float suspensionStiffness = 20.0f; + protected float wheelsDampingRelaxation = 2.3f; + protected float wheelsDampingCompression = 4.4f; + protected float frictionSlip = 10.5f; + protected float rollInfluence = 1.0f; + protected float maxSuspensionTravelCm = 500f; + protected float maxSuspensionForce = 6000f; + protected float radius = 0.5f; + protected float restLength = 1f; + protected Vector3f wheelWorldLocation = new Vector3f(); + protected Quaternion wheelWorldRotation = new Quaternion(); + protected Spatial wheelSpatial; + protected com.jme3.math.Matrix3f tmp_Matrix = new com.jme3.math.Matrix3f(); + protected final Quaternion tmp_inverseWorldRotation = new Quaternion(); + private boolean applyLocal = false; + + public VehicleWheel() { + } + + public VehicleWheel(Spatial spat, Vector3f location, Vector3f direction, Vector3f axle, + float restLength, float radius, boolean frontWheel) { + this(location, direction, axle, restLength, radius, frontWheel); + wheelSpatial = spat; + } + + public VehicleWheel(Vector3f location, Vector3f direction, Vector3f axle, + float restLength, float radius, boolean frontWheel) { + this.location.set(location); + this.direction.set(direction); + this.axle.set(axle); + this.frontWheel = frontWheel; + this.restLength = restLength; + this.radius = radius; + } + + public void updatePhysicsState() { + Converter.convert(wheelInfo.worldTransform.origin, wheelWorldLocation); + Converter.convert(wheelInfo.worldTransform.basis, tmp_Matrix); + wheelWorldRotation.fromRotationMatrix(tmp_Matrix); + } + + public 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 maxSuspensionForce + */ + public void setMaxSuspensionForce(float maxSuspensionForce) { + this.maxSuspensionForce = maxSuspensionForce; + applyInfo(); + } + + private void applyInfo() { + if (wheelInfo == null) { + return; + } + wheelInfo.suspensionStiffness = suspensionStiffness; + wheelInfo.wheelsDampingRelaxation = wheelsDampingRelaxation; + wheelInfo.wheelsDampingCompression = wheelsDampingCompression; + wheelInfo.frictionSlip = frictionSlip; + wheelInfo.rollInfluence = rollInfluence; + wheelInfo.maxSuspensionTravelCm = maxSuspensionTravelCm; + wheelInfo.maxSuspensionForce = maxSuspensionForce; + wheelInfo.wheelsRadius = radius; + wheelInfo.bIsFrontWheel = frontWheel; + wheelInfo.suspensionRestLength1 = restLength; + } + + public float getRadius() { + return radius; + } + + public void setRadius(float radius) { + this.radius = radius; + applyInfo(); + } + + public float getRestLength() { + return restLength; + } + + public void setRestLength(float restLength) { + this.restLength = restLength; + applyInfo(); + } + + /** + * returns the object this wheel is in contact with or null if no contact + * @return the PhysicsCollisionObject (PhysicsRigidBody, PhysicsGhostObject) + */ + public PhysicsCollisionObject getGroundObject() { + if (wheelInfo.raycastInfo.groundObject == null) { + return null; + } else if (wheelInfo.raycastInfo.groundObject instanceof RigidBody) { + System.out.println("RigidBody"); + return (PhysicsRigidBody) ((RigidBody) wheelInfo.raycastInfo.groundObject).getUserPointer(); + } else { + return null; + } + } + + /** + * returns the location where the wheel collides with the ground (world space) + */ + public Vector3f getCollisionLocation(Vector3f vec) { + Converter.convert(wheelInfo.raycastInfo.contactPointWS, vec); + return vec; + } + + /** + * returns the location where the wheel collides with the ground (world space) + */ + public Vector3f getCollisionLocation() { + return Converter.convert(wheelInfo.raycastInfo.contactPointWS); + } + + /** + * returns the normal where the wheel collides with the ground (world space) + */ + public Vector3f getCollisionNormal(Vector3f vec) { + Converter.convert(wheelInfo.raycastInfo.contactNormalWS, vec); + return vec; + } + + /** + * returns the normal where the wheel collides with the ground (world space) + */ + public Vector3f getCollisionNormal() { + return Converter.convert(wheelInfo.raycastInfo.contactNormalWS); + } + + /** + * returns how much the wheel skids on the ground (for skid sounds/smoke etc.)
      + * 0.0 = wheels are sliding, 1.0 = wheels have traction. + */ + public float getSkidInfo() { + return wheelInfo.skidInfo; + } + + /** + * returns how many degrees the wheel has turned since the last physics + * step. + */ + public float getDeltaRotation() { + return wheelInfo.deltaRotation; + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule capsule = im.getCapsule(this); + wheelSpatial = (Spatial) capsule.readSavable("wheelSpatial", null); + frontWheel = capsule.readBoolean("frontWheel", false); + location = (Vector3f) capsule.readSavable("wheelLocation", new Vector3f()); + direction = (Vector3f) capsule.readSavable("wheelDirection", new Vector3f()); + axle = (Vector3f) capsule.readSavable("wheelAxle", new Vector3f()); + suspensionStiffness = capsule.readFloat("suspensionStiffness", 20.0f); + wheelsDampingRelaxation = capsule.readFloat("wheelsDampingRelaxation", 2.3f); + wheelsDampingCompression = capsule.readFloat("wheelsDampingCompression", 4.4f); + frictionSlip = capsule.readFloat("frictionSlip", 10.5f); + rollInfluence = capsule.readFloat("rollInfluence", 1.0f); + maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f); + maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f); + radius = capsule.readFloat("wheelRadius", 0.5f); + restLength = capsule.readFloat("restLength", 1f); + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(wheelSpatial, "wheelSpatial", null); + capsule.write(frontWheel, "frontWheel", false); + capsule.write(location, "wheelLocation", new Vector3f()); + capsule.write(direction, "wheelDirection", new Vector3f()); + capsule.write(axle, "wheelAxle", new Vector3f()); + capsule.write(suspensionStiffness, "suspensionStiffness", 20.0f); + capsule.write(wheelsDampingRelaxation, "wheelsDampingRelaxation", 2.3f); + capsule.write(wheelsDampingCompression, "wheelsDampingCompression", 4.4f); + capsule.write(frictionSlip, "frictionSlip", 10.5f); + capsule.write(rollInfluence, "rollInfluence", 1.0f); + capsule.write(maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f); + capsule.write(maxSuspensionForce, "maxSuspensionForce", 6000f); + capsule.write(radius, "wheelRadius", 0.5f); + capsule.write(restLength, "restLength", 1f); + } + + /** + * @return the wheelSpatial + */ + public Spatial getWheelSpatial() { + return wheelSpatial; + } + + /** + * @param wheelSpatial the wheelSpatial to set + */ + public void setWheelSpatial(Spatial wheelSpatial) { + this.wheelSpatial = wheelSpatial; + } + + public boolean isApplyLocal() { + return applyLocal; + } + + public void setApplyLocal(boolean applyLocal) { + this.applyLocal = applyLocal; + } +} diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java new file mode 100644 index 000000000..6de4f040b --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects.infos; + +import com.bulletphysics.linearmath.MotionState; +import com.bulletphysics.linearmath.Transform; +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.bullet.util.Converter; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; + +/** + * stores transform info of a PhysicsNode in a threadsafe manner to + * allow multithreaded access from the jme scenegraph and the bullet physicsspace + * @author normenhansen + */ +public class RigidBodyMotionState extends MotionState { + //stores the bullet transform + + private Transform motionStateTrans = new Transform(Converter.convert(new Matrix3f())); + private Vector3f worldLocation = new Vector3f(); + private Matrix3f worldRotation = new Matrix3f(); + private Quaternion worldRotationQuat = new Quaternion(); + private Vector3f localLocation = new Vector3f(); + private Quaternion localRotationQuat = new Quaternion(); + //keep track of transform changes + private boolean physicsLocationDirty = false; + private boolean jmeLocationDirty = false; + //temp variable for conversion + private Quaternion tmp_inverseWorldRotation = new Quaternion(); + private PhysicsVehicle vehicle; + private boolean applyPhysicsLocal = false; +// protected LinkedList listeners = new LinkedList(); + + public RigidBodyMotionState() { + } + + /** + * called from bullet when creating the rigidbody + * @param t + * @return + */ + public Transform getWorldTransform(Transform t) { + t.set(motionStateTrans); + return t; + } + + /** + * called from bullet when the transform of the rigidbody changes + * @param worldTrans + */ + public 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 boolean applyTransform(Spatial spatial) { + if (!physicsLocationDirty) { + return false; + } + if (!applyPhysicsLocal && spatial.getParent() != null) { + localLocation.set(worldLocation).subtractLocal(spatial.getParent().getWorldTranslation()); + localLocation.divideLocal(spatial.getParent().getWorldScale()); + tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation); + + localRotationQuat.set(worldRotationQuat); + tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().mult(localRotationQuat, localRotationQuat); + + spatial.setLocalTranslation(localLocation); + spatial.setLocalRotation(localRotationQuat); + } else { + spatial.setLocalTranslation(worldLocation); + spatial.setLocalRotation(worldRotationQuat); + } + physicsLocationDirty = false; + return true; + } + + /** + * @return the worldLocation + */ + public Vector3f getWorldLocation() { + return worldLocation; + } + + /** + * @return the worldRotation + */ + public Matrix3f getWorldRotation() { + return worldRotation; + } + + /** + * @return the worldRotationQuat + */ + public Quaternion getWorldRotationQuat() { + return worldRotationQuat; + } + + /** + * @param vehicle the vehicle to set + */ + public void setVehicle(PhysicsVehicle vehicle) { + this.vehicle = vehicle; + } + + public boolean isApplyPhysicsLocal() { + return applyPhysicsLocal; + } + + public void setApplyPhysicsLocal(boolean applyPhysicsLocal) { + this.applyPhysicsLocal = applyPhysicsLocal; + } +// public void addMotionStateListener(PhysicsMotionStateListener listener){ +// listeners.add(listener); +// } +// +// public void removeMotionStateListener(PhysicsMotionStateListener listener){ +// listeners.remove(listener); +// } +// public synchronized boolean applyTransform(com.jme3.math.Transform trans) { +// if (!physicsLocationDirty) { +// return false; +// } +// trans.setTranslation(worldLocation); +// trans.setRotation(worldRotationQuat); +// physicsLocationDirty = false; +// return true; +// } +// +// /** +// * called from jme when the location of the jme Node changes +// * @param location +// * @param rotation +// */ +// public synchronized void setWorldTransform(Vector3f location, Quaternion rotation) { +// worldLocation.set(location); +// worldRotationQuat.set(rotation); +// worldRotation.set(rotation.toRotationMatrix()); +// Converter.convert(worldLocation, motionStateTrans.origin); +// Converter.convert(worldRotation, motionStateTrans.basis); +// jmeLocationDirty = true; +// } +// +// /** +// * applies the current transform to the given RigidBody if the value has been changed on the jme side +// * @param rBody +// */ +// public synchronized void applyTransform(RigidBody rBody) { +// if (!jmeLocationDirty) { +// return; +// } +// assert (rBody != null); +// rBody.setWorldTransform(motionStateTrans); +// rBody.activate(); +// jmeLocationDirty = false; +// } +} diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/util/Converter.java b/jme3-jbullet/src/main/java/com/jme3/bullet/util/Converter.java new file mode 100644 index 000000000..f9956d080 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/util/Converter.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.util; + +import com.bulletphysics.collision.shapes.IndexedMesh; +import com.bulletphysics.dom.HeightfieldTerrainShape; +import com.jme3.math.FastMath; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +/** + * Nice convenience methods for conversion between javax.vecmath and com.jme3.math + * Objects, also some jme to jbullet mesh conversion. + * @author normenhansen + */ +public class Converter { + + private Converter() { + } + + public static com.jme3.math.Vector3f convert(javax.vecmath.Vector3f oldVec) { + com.jme3.math.Vector3f newVec = new com.jme3.math.Vector3f(); + convert(oldVec, newVec); + return newVec; + } + + public static com.jme3.math.Vector3f convert(javax.vecmath.Vector3f oldVec, com.jme3.math.Vector3f newVec) { + newVec.x = oldVec.x; + newVec.y = oldVec.y; + newVec.z = oldVec.z; + return newVec; + } + + public static javax.vecmath.Vector3f convert(com.jme3.math.Vector3f oldVec) { + javax.vecmath.Vector3f newVec = new javax.vecmath.Vector3f(); + convert(oldVec, newVec); + return newVec; + } + + public static javax.vecmath.Vector3f convert(com.jme3.math.Vector3f oldVec, javax.vecmath.Vector3f newVec) { + newVec.x = oldVec.x; + newVec.y = oldVec.y; + newVec.z = oldVec.z; + return newVec; + } + + public static javax.vecmath.Quat4f convert(com.jme3.math.Quaternion oldQuat, javax.vecmath.Quat4f newQuat) { + newQuat.w = oldQuat.getW(); + newQuat.x = oldQuat.getX(); + newQuat.y = oldQuat.getY(); + newQuat.z = oldQuat.getZ(); + return newQuat; + } + + public static javax.vecmath.Quat4f convert(com.jme3.math.Quaternion oldQuat) { + javax.vecmath.Quat4f newQuat = new javax.vecmath.Quat4f(); + convert(oldQuat, newQuat); + return newQuat; + } + + public static com.jme3.math.Quaternion convert(javax.vecmath.Quat4f oldQuat, com.jme3.math.Quaternion newQuat) { + newQuat.set(oldQuat.x, oldQuat.y, oldQuat.z, oldQuat.w); + return newQuat; + } + + public static com.jme3.math.Quaternion convert(javax.vecmath.Quat4f oldQuat) { + com.jme3.math.Quaternion newQuat = new com.jme3.math.Quaternion(); + convert(oldQuat, newQuat); + return newQuat; + } + + public static com.jme3.math.Quaternion convert(javax.vecmath.Matrix3f oldMatrix, com.jme3.math.Quaternion newQuaternion) { + // the trace is the sum of the diagonal elements; see + // http://mathworld.wolfram.com/MatrixTrace.html + float t = oldMatrix.m00 + oldMatrix.m11 + oldMatrix.m22; + float w, x, y, z; + // we protect the division by s by ensuring that s>=1 + if (t >= 0) { // |w| >= .5 + float s = FastMath.sqrt(t + 1); // |s|>=1 ... + w = 0.5f * s; + s = 0.5f / s; // so this division isn't bad + x = (oldMatrix.m21 - oldMatrix.m12) * s; + y = (oldMatrix.m02 - oldMatrix.m20) * s; + z = (oldMatrix.m10 - oldMatrix.m01) * s; + } else if ((oldMatrix.m00 > oldMatrix.m11) && (oldMatrix.m00 > oldMatrix.m22)) { + float s = FastMath.sqrt(1.0f + oldMatrix.m00 - oldMatrix.m11 - oldMatrix.m22); // |s|>=1 + x = s * 0.5f; // |x| >= .5 + s = 0.5f / s; + y = (oldMatrix.m10 + oldMatrix.m01) * s; + z = (oldMatrix.m02 + oldMatrix.m20) * s; + w = (oldMatrix.m21 - oldMatrix.m12) * s; + } else if (oldMatrix.m11 > oldMatrix.m22) { + float s = FastMath.sqrt(1.0f + oldMatrix.m11 - oldMatrix.m00 - oldMatrix.m22); // |s|>=1 + y = s * 0.5f; // |y| >= .5 + s = 0.5f / s; + x = (oldMatrix.m10 + oldMatrix.m01) * s; + z = (oldMatrix.m21 + oldMatrix.m12) * s; + w = (oldMatrix.m02 - oldMatrix.m20) * s; + } else { + float s = FastMath.sqrt(1.0f + oldMatrix.m22 - oldMatrix.m00 - oldMatrix.m11); // |s|>=1 + z = s * 0.5f; // |z| >= .5 + s = 0.5f / s; + x = (oldMatrix.m02 + oldMatrix.m20) * s; + y = (oldMatrix.m21 + oldMatrix.m12) * s; + w = (oldMatrix.m10 - oldMatrix.m01) * s; + } + return newQuaternion.set(x, y, z, w); + } + + public static javax.vecmath.Matrix3f convert(com.jme3.math.Quaternion oldQuaternion, javax.vecmath.Matrix3f newMatrix) { + float norm = oldQuaternion.getW() * oldQuaternion.getW() + oldQuaternion.getX() * oldQuaternion.getX() + oldQuaternion.getY() * oldQuaternion.getY() + oldQuaternion.getZ() * oldQuaternion.getZ(); + float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + float xs = oldQuaternion.getX() * s; + float ys = oldQuaternion.getY() * s; + float zs = oldQuaternion.getZ() * s; + float xx = oldQuaternion.getX() * xs; + float xy = oldQuaternion.getX() * ys; + float xz = oldQuaternion.getX() * zs; + float xw = oldQuaternion.getW() * xs; + float yy = oldQuaternion.getY() * ys; + float yz = oldQuaternion.getY() * zs; + float yw = oldQuaternion.getW() * ys; + float zz = oldQuaternion.getZ() * zs; + float zw = oldQuaternion.getW() * zs; + + // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here + newMatrix.m00 = 1 - (yy + zz); + newMatrix.m01 = (xy - zw); + newMatrix.m02 = (xz + yw); + newMatrix.m10 = (xy + zw); + newMatrix.m11 = 1 - (xx + zz); + newMatrix.m12 = (yz - xw); + newMatrix.m20 = (xz - yw); + newMatrix.m21 = (yz + xw); + newMatrix.m22 = 1 - (xx + yy); + + return newMatrix; + } + + public static com.jme3.math.Matrix3f convert(javax.vecmath.Matrix3f oldMatrix) { + com.jme3.math.Matrix3f newMatrix = new com.jme3.math.Matrix3f(); + convert(oldMatrix, newMatrix); + return newMatrix; + } + + public static com.jme3.math.Matrix3f convert(javax.vecmath.Matrix3f oldMatrix, com.jme3.math.Matrix3f newMatrix) { + newMatrix.set(0, 0, oldMatrix.m00); + newMatrix.set(0, 1, oldMatrix.m01); + newMatrix.set(0, 2, oldMatrix.m02); + newMatrix.set(1, 0, oldMatrix.m10); + newMatrix.set(1, 1, oldMatrix.m11); + newMatrix.set(1, 2, oldMatrix.m12); + newMatrix.set(2, 0, oldMatrix.m20); + newMatrix.set(2, 1, oldMatrix.m21); + newMatrix.set(2, 2, oldMatrix.m22); + return newMatrix; + } + + public static javax.vecmath.Matrix3f convert(com.jme3.math.Matrix3f oldMatrix) { + javax.vecmath.Matrix3f newMatrix = new javax.vecmath.Matrix3f(); + convert(oldMatrix, newMatrix); + return newMatrix; + } + + public static javax.vecmath.Matrix3f convert(com.jme3.math.Matrix3f oldMatrix, javax.vecmath.Matrix3f newMatrix) { + newMatrix.m00 = oldMatrix.get(0, 0); + newMatrix.m01 = oldMatrix.get(0, 1); + newMatrix.m02 = oldMatrix.get(0, 2); + newMatrix.m10 = oldMatrix.get(1, 0); + newMatrix.m11 = oldMatrix.get(1, 1); + newMatrix.m12 = oldMatrix.get(1, 2); + newMatrix.m20 = oldMatrix.get(2, 0); + newMatrix.m21 = oldMatrix.get(2, 1); + newMatrix.m22 = oldMatrix.get(2, 2); + return newMatrix; + } + + public static com.bulletphysics.linearmath.Transform convert(com.jme3.math.Transform in, com.bulletphysics.linearmath.Transform out) { + convert(in.getTranslation(), out.origin); + convert(in.getRotation(), out.basis); + return out; + } + + public static com.jme3.math.Transform convert(com.bulletphysics.linearmath.Transform in, com.jme3.math.Transform out) { + convert(in.origin, out.getTranslation()); + convert(in.basis, out.getRotation()); + return out; + } + + public static synchronized IndexedMesh convert(Mesh mesh) { + IndexedMesh jBulletIndexedMesh = new IndexedMesh(); + jBulletIndexedMesh.triangleIndexBase = ByteBuffer.allocate(mesh.getTriangleCount() * 3 * 4); + jBulletIndexedMesh.vertexBase = ByteBuffer.allocate(mesh.getVertexCount() * 3 * 4); + + IndexBuffer indices = mesh.getIndicesAsList(); + + FloatBuffer vertices = mesh.getFloatBuffer(Type.Position); + vertices.rewind(); + + int verticesLength = mesh.getVertexCount() * 3; + jBulletIndexedMesh.numVertices = mesh.getVertexCount(); + jBulletIndexedMesh.vertexStride = 12; //3 verts * 4 bytes per. + for (int i = 0; i < verticesLength; i++) { + float tempFloat = vertices.get(); + jBulletIndexedMesh.vertexBase.putFloat(tempFloat); + } + + int indicesLength = mesh.getTriangleCount() * 3; + jBulletIndexedMesh.numTriangles = mesh.getTriangleCount(); + jBulletIndexedMesh.triangleIndexStride = 12; //3 index entries * 4 bytes each. + for (int i = 0; i < indicesLength; i++) { + jBulletIndexedMesh.triangleIndexBase.putInt(indices.get(i)); + } + vertices.rewind(); + vertices.clear(); + + return jBulletIndexedMesh; + } + + public static Mesh convert(IndexedMesh mesh) { + Mesh jmeMesh = new Mesh(); + + jmeMesh.setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(mesh.numTriangles * 3)); + jmeMesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(mesh.numVertices * 3)); + + IndexBuffer indicess = jmeMesh.getIndexBuffer(); + FloatBuffer vertices = jmeMesh.getFloatBuffer(Type.Position); + + for (int i = 0; i < mesh.numTriangles * 3; i++) { + indicess.put(i, mesh.triangleIndexBase.getInt(i * 4)); + } + + for (int i = 0; i < mesh.numVertices * 3; i++) { + vertices.put(i, mesh.vertexBase.getFloat(i * 4)); + } + jmeMesh.updateCounts(); + jmeMesh.updateBound(); + jmeMesh.getFloatBuffer(Type.Position).clear(); + + return jmeMesh; + } + + public static Mesh convert(HeightfieldTerrainShape heightfieldShape) { + return null; //TODO!! + } +} diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java b/jme3-jbullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java new file mode 100644 index 000000000..077326d57 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.util; + +import com.bulletphysics.collision.shapes.ConcaveShape; +import com.bulletphysics.collision.shapes.ConvexShape; +import com.bulletphysics.collision.shapes.ShapeHull; +import com.bulletphysics.collision.shapes.TriangleCallback; +import com.bulletphysics.util.IntArrayList; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.math.Matrix3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.vecmath.Vector3f; + +/** + * + * @author CJ Hare, normenhansen + */ +public class DebugShapeFactory { + + /** The maximum corner for the aabb used for triangles to include in ConcaveShape processing.*/ + private static final Vector3f aabbMax = new Vector3f(1e30f, 1e30f, 1e30f); + /** The minimum corner for the aabb used for triangles to include in ConcaveShape processing.*/ + private static final Vector3f aabbMin = new Vector3f(-1e30f, -1e30f, -1e30f); + + /** + * Creates a debug shape from the given collision shape. This is mostly used internally.
      + * 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(); + + Matrix3f tempRot = vars.tempMat3; + + tempRot.set(geometry.getLocalRotation()); + childCollisionShape.rotation.mult(tempRot, tempRot); + geometry.setLocalRotation(tempRot); + + vars.release(); + + node.attachChild(geometry); + } + debugShape = node; + } else { + debugShape = createDebugShape(collisionShape); + } + if (debugShape == null) { + return null; + } + debugShape.updateGeometricState(); + return debugShape; + } + + private static Geometry createDebugShape(CollisionShape shape) { + Geometry geom = new Geometry(); + geom.setMesh(DebugShapeFactory.getDebugMesh(shape)); +// geom.setLocalScale(shape.getScale()); + geom.updateModelBound(); + return geom; + } + + public static Mesh getDebugMesh(CollisionShape shape) { + Mesh mesh = null; + if (shape.getCShape() instanceof ConvexShape) { + mesh = new Mesh(); + mesh.setBuffer(Type.Position, 3, getVertices((ConvexShape) shape.getCShape())); + mesh.getFloatBuffer(Type.Position).clear(); + } else if (shape.getCShape() instanceof ConcaveShape) { + mesh = new Mesh(); + mesh.setBuffer(Type.Position, 3, getVertices((ConcaveShape) shape.getCShape())); + mesh.getFloatBuffer(Type.Position).clear(); + } + return mesh; + } + + /** + * Constructs the buffer for the vertices of the concave shape. + * + * @param concaveShape the shape to get the vertices for / from. + * @return the shape as stored by the given broadphase rigid body. + */ + private static FloatBuffer getVertices(ConcaveShape concaveShape) { + // Create the call back that'll create the vertex buffer + BufferedTriangleCallback triangleProcessor = new BufferedTriangleCallback(); + concaveShape.processAllTriangles(triangleProcessor, aabbMin, aabbMax); + + // Retrieve the vextex and index buffers + return triangleProcessor.getVertices(); + } + + /** + * Processes the given convex shape to retrieve a correctly ordered FloatBuffer to + * construct the shape from with a TriMesh. + * + * @param convexShape the shape to retreieve the vertices from. + * @return the vertices as a FloatBuffer, ordered as Triangles. + */ + private static FloatBuffer getVertices(ConvexShape convexShape) { + // Check there is a hull shape to render + if (convexShape.getUserPointer() == null) { + // create a hull approximation + ShapeHull hull = new ShapeHull(convexShape); + float margin = convexShape.getMargin(); + hull.buildHull(margin); + convexShape.setUserPointer(hull); + } + + // Assert state - should have a pointer to a hull (shape) that'll be drawn + assert convexShape.getUserPointer() != null : "Should have a shape for the userPointer, instead got null"; + ShapeHull hull = (ShapeHull) convexShape.getUserPointer(); + + // Assert we actually have a shape to render + assert hull.numTriangles() > 0 : "Expecting the Hull shape to have triangles"; + int numberOfTriangles = hull.numTriangles(); + + // The number of bytes needed is: (floats in a vertex) * (vertices in a triangle) * (# of triangles) * (size of float in bytes) + final int numberOfFloats = 3 * 3 * numberOfTriangles; + FloatBuffer vertices = BufferUtils.createFloatBuffer(numberOfFloats); + + // Force the limit, set the cap - most number of floats we will use the buffer for + vertices.limit(numberOfFloats); + + // Loop variables + final IntArrayList hullIndicies = hull.getIndexPointer(); + final List 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; + FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(numberOfFloats); + + // Force the limit, set the cap - most number of floats we will use the buffer for + verticesBuffer.limit(numberOfFloats); + + // Copy the values from the list to the direct float buffer + for (Vector3f v : vertices) { + verticesBuffer.put(v.x).put(v.y).put(v.z); + } + + vertices.clear(); + return verticesBuffer; + } +} diff --git a/jme3-jogg/src/main/java/com/jme3/audio/plugins/CachedOggStream.java b/jme3-jogg/src/main/java/com/jme3/audio/plugins/CachedOggStream.java new file mode 100644 index 000000000..956d4af51 --- /dev/null +++ b/jme3-jogg/src/main/java/com/jme3/audio/plugins/CachedOggStream.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio.plugins; + +import com.jme3.util.IntMap; +import de.jarnbjo.ogg.LogicalOggStream; +import de.jarnbjo.ogg.LogicalOggStreamImpl; +import de.jarnbjo.ogg.OggPage; +import de.jarnbjo.ogg.PhysicalOggStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Implementation of the 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 boolean closed = false; + private boolean eos = false; + private boolean bos = false; + private InputStream sourceStream; + private HashMap logicalStreams + = new HashMap(); + + private IntMap oggPages = new IntMap(); + private OggPage lastPage; + + private int pageNumber; + + public CachedOggStream(InputStream in) throws IOException { + sourceStream = in; + + // Read all OGG pages in file + long time = System.nanoTime(); + while (!eos){ + readOggNextPage(); + } + long dt = System.nanoTime() - time; + Logger.getLogger(CachedOggStream.class.getName()).log(Level.FINE, "Took {0} ms to load OGG", dt/1000000); + } + + public OggPage getLastOggPage() { + return lastPage; + } + + private LogicalOggStream getLogicalStream(int serialNumber) { + return logicalStreams.get(Integer.valueOf(serialNumber)); + } + + public Collection getLogicalStreams() { + return logicalStreams.values(); + } + + public boolean isOpen() { + return !closed; + } + + public void close() throws IOException { + closed = true; + sourceStream.close(); + } + + public OggPage getOggPage(int index) throws IOException { + return oggPages.get(index); + } + + public void setTime(long granulePosition) throws IOException { + for (LogicalOggStream los : getLogicalStreams()){ + los.setTime(granulePosition); + } + } + + private int readOggNextPage() throws IOException { + if (eos) + return -1; + + OggPage op = OggPage.create(sourceStream); + if (!op.isBos()){ + bos = true; + } + if (op.isEos()){ + eos = true; + lastPage = op; + } + + LogicalOggStreamImpl los = (LogicalOggStreamImpl) logicalStreams.get(op.getStreamSerialNumber()); + if(los == null) { + los = new LogicalOggStreamImpl(this, op.getStreamSerialNumber()); + logicalStreams.put(op.getStreamSerialNumber(), los); + los.checkFormat(op); + } + + los.addPageNumberMapping(pageNumber); + los.addGranulePosition(op.getAbsoluteGranulePosition()); + + oggPages.put(pageNumber, op); + pageNumber++; + + return pageNumber-1; + } + + public boolean isSeekable() { + return true; + } +} \ No newline at end of file diff --git a/jme3-jogg/src/main/java/com/jme3/audio/plugins/OGGLoader.java b/jme3-jogg/src/main/java/com/jme3/audio/plugins/OGGLoader.java new file mode 100644 index 000000000..5fb030981 --- /dev/null +++ b/jme3-jogg/src/main/java/com/jme3/audio/plugins/OGGLoader.java @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.audio.AudioBuffer; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioKey; +import com.jme3.audio.AudioStream; +import com.jme3.audio.SeekableStream; +import com.jme3.util.BufferUtils; +import de.jarnbjo.ogg.EndOfOggStreamException; +import de.jarnbjo.ogg.LogicalOggStream; +import de.jarnbjo.ogg.PhysicalOggStream; +import de.jarnbjo.vorbis.IdentificationHeader; +import de.jarnbjo.vorbis.VorbisStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class OGGLoader implements AssetLoader { + +// private static int BLOCK_SIZE = 4096*64; + + private PhysicalOggStream oggStream; + private LogicalOggStream loStream; + private VorbisStream vorbisStream; + +// private CommentHeader commentHdr; + private IdentificationHeader streamHdr; + + private static class JOggInputStream extends InputStream { + + private boolean endOfStream = false; + protected final VorbisStream vs; + + public JOggInputStream(VorbisStream vs){ + this.vs = vs; + } + + @Override + public int read() throws IOException { + return 0; + } + + @Override + public int read(byte[] buf) throws IOException{ + return read(buf,0,buf.length); + } + + @Override + public int read(byte[] buf, int offset, int length) throws IOException{ + if (endOfStream) + return -1; + + int bytesRead = 0, cnt = 0; + assert length % 2 == 0; // read buffer should be even + + while (bytesRead 0){ + baos.write(buf, 0, read); + } + } catch (EndOfOggStreamException ex){ + } + + + byte[] dataBytes = baos.toByteArray(); + swapBytes(dataBytes, 0, dataBytes.length); + + int bytesToCopy = getOggTotalBytes( dataBytes.length ); + + ByteBuffer data = BufferUtils.createByteBuffer(bytesToCopy); + data.put(dataBytes, 0, bytesToCopy).flip(); + + vorbisStream.close(); + loStream.close(); + oggStream.close(); + + return data; + } + + private static void swapBytes(byte[] b, int off, int len) { + byte tempByte; + for (int i = off; i < (off+len); i+=2) { + tempByte = b[i]; + b[i] = b[i+1]; + b[i+1] = tempByte; + } + } + + private InputStream readToStream(boolean seekable,float streamDuration){ + if(seekable){ + return new SeekableJOggInputStream(vorbisStream,loStream,streamDuration); + }else{ + return new JOggInputStream(vorbisStream); + } + } + + private AudioData load(InputStream in, boolean readStream, boolean streamCache) throws IOException{ + if (readStream && streamCache){ + oggStream = new CachedOggStream(in); + }else{ + oggStream = new UncachedOggStream(in); + } + + Collection streams = oggStream.getLogicalStreams(); + loStream = streams.iterator().next(); + +// if (loStream == null){ +// throw new IOException("OGG File does not contain vorbis audio stream"); +// } + + vorbisStream = new VorbisStream(loStream); + streamHdr = vorbisStream.getIdentificationHeader(); +// commentHdr = vorbisStream.getCommentHeader(); + + if (!readStream){ + AudioBuffer audioBuffer = new AudioBuffer(); + audioBuffer.setupFormat(streamHdr.getChannels(), 16, streamHdr.getSampleRate()); + audioBuffer.updateData(readToBuffer()); + return audioBuffer; + }else{ + AudioStream audioStream = new AudioStream(); + audioStream.setupFormat(streamHdr.getChannels(), 16, streamHdr.getSampleRate()); + + // might return -1 if unknown + float streamDuration = computeStreamDuration(); + + audioStream.updateData(readToStream(oggStream.isSeekable(),streamDuration), streamDuration); + return audioStream; + } + } + + public Object load(AssetInfo info) throws IOException { + if (!(info.getKey() instanceof AudioKey)){ + throw new IllegalArgumentException("Audio assets must be loaded using an AudioKey"); + } + + AudioKey key = (AudioKey) info.getKey(); + boolean readStream = key.isStream(); + boolean streamCache = key.useStreamCache(); + + InputStream in = null; + try { + in = info.openStream(); + AudioData data = load(in, readStream, streamCache); + if (data instanceof AudioStream){ + // audio streams must remain open + in = null; + } + return data; + } finally { + if (in != null){ + in.close(); + } + } + + } + +} diff --git a/jme3-jogg/src/main/java/com/jme3/audio/plugins/UncachedOggStream.java b/jme3-jogg/src/main/java/com/jme3/audio/plugins/UncachedOggStream.java new file mode 100644 index 000000000..7c3e7ced9 --- /dev/null +++ b/jme3-jogg/src/main/java/com/jme3/audio/plugins/UncachedOggStream.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio.plugins; + +import de.jarnbjo.ogg.*; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; + +/** + * Single-threaded physical ogg stream. Decodes audio in the same thread + * that reads. + */ +public class UncachedOggStream implements PhysicalOggStream { + + private boolean closed = false; + private boolean eos = false; + private boolean bos = false; + private InputStream sourceStream; + private LinkedList pageCache = new LinkedList(); + private HashMap logicalStreams + = new HashMap(); + private OggPage lastPage = null; + + public UncachedOggStream(InputStream in) throws OggFormatException, IOException { + this.sourceStream = in; + + // read until beginning of stream + while (!bos){ + readNextOggPage(); + } + + // now buffer up an addition 25 pages +// while (pageCache.size() < 25 && !eos){ +// readNextOggPage(); +// } + } + + public OggPage getLastOggPage() { + return lastPage; + } + + private void readNextOggPage() throws IOException { + OggPage op = OggPage.create(sourceStream); + if (!op.isBos()){ + bos = true; + } + if (op.isEos()){ + eos = true; + lastPage = op; + } + + LogicalOggStreamImpl los = (LogicalOggStreamImpl) getLogicalStream(op.getStreamSerialNumber()); + if (los == null){ + los = new LogicalOggStreamImpl(this, op.getStreamSerialNumber()); + logicalStreams.put(op.getStreamSerialNumber(), los); + los.checkFormat(op); + } + + pageCache.add(op); + } + + public OggPage getOggPage(int index) throws IOException { + if (eos){ + return null; + } + +// if (!eos){ +// int num = pageCache.size(); +// long fiveMillis = 5000000; +// long timeStart = System.nanoTime(); +// do { +// readNextOggPage(); +// } while ( !eos && (System.nanoTime() - timeStart) < fiveMillis ); +// System.out.println( pageCache.size() - num ); + + if (pageCache.size() == 0 /*&& !eos*/){ + readNextOggPage(); + } +// } + + return pageCache.removeFirst(); + } + + private LogicalOggStream getLogicalStream(int serialNumber) { + return logicalStreams.get(Integer.valueOf(serialNumber)); + } + + public Collection 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/jme3-jogl/src/main/java/com/jme3/audio/joal/JoalAudioRenderer.java b/jme3-jogl/src/main/java/com/jme3/audio/joal/JoalAudioRenderer.java new file mode 100644 index 000000000..9f99ba458 --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/audio/joal/JoalAudioRenderer.java @@ -0,0 +1,1113 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio.joal; + +import com.jme3.audio.AudioSource.Status; +import com.jme3.audio.*; +import com.jme3.math.Vector3f; +import com.jme3.util.BufferUtils; +import com.jme3.util.NativeObjectManager; +import com.jogamp.common.nio.Buffers; +import com.jogamp.openal.*; +import com.jogamp.openal.util.ALut; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class JoalAudioRenderer implements AudioRenderer, Runnable { + + private static final Logger logger = Logger.getLogger(JoalAudioRenderer.class.getName()); + private final NativeObjectManager objManager = new NativeObjectManager(); + // When multiplied by STREAMING_BUFFER_COUNT, will equal 44100 * 2 * 2 + // which is exactly 1 second of audio. + private static final int BUFFER_SIZE = 35280; + private static final int STREAMING_BUFFER_COUNT = 5; + private final static int MAX_NUM_CHANNELS = 64; + private IntBuffer ib = BufferUtils.createIntBuffer(1); + private final FloatBuffer fb = BufferUtils.createVector3Buffer(2); + private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE); + private final byte[] arrayBuf = new byte[BUFFER_SIZE]; + private int[] channels; + private AudioSource[] 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; + // Update audio 20 times per second + private static final float UPDATE_RATE = 0.05f; + private final Thread audioThread = new Thread(this, "jME3 Audio Thread"); + private final AtomicBoolean threadLock = new AtomicBoolean(false); + + private ALC alc; + private AL al; + private ALExt alExt; + + static { + ALut.alutInit(); + } + + public JoalAudioRenderer() { + } + + 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 { + alc = ALFactory.getALC(); + al = ALFactory.getAL(); + alExt = ALFactory.getALExt(); + + // Get handle to default device. + ALCdevice device = alc.alcOpenDevice(null); + if (device == null) { + throw new ALException("Error opening default OpenAL device"); + } + + // Get the device specifier. + String deviceName = alc.alcGetString(device, ALC.ALC_DEVICE_SPECIFIER); + if (deviceName == null) { + throw new ALException("Error getting specifier for default OpenAL device"); + } + + logger.log(Level.FINER, "Audio Device: {0}", deviceName); + logger.log(Level.FINER, "Audio Vendor: {0}", al.alGetString(ALConstants.AL_VENDOR)); + logger.log(Level.FINER, "Audio Renderer: {0}", al.alGetString(ALConstants.AL_RENDERER)); + logger.log(Level.FINER, "Audio Version: {0}", al.alGetString(ALConstants.AL_VERSION)); + + // Create audio context. + ALCcontext context = alc.alcCreateContext(device, null); + if (context == null) { + throw new ALException("Error creating OpenAL context"); + } + + // Set active context. + alc.alcMakeContextCurrent(context); + + // Check for an error. + if (alc.alcGetError(device) != ALC.ALC_NO_ERROR) { + throw new ALException("Error making OpenAL context current"); + } + + // Find maximum # of sources supported by this implementation + ArrayList channelList = new ArrayList(); + IntBuffer channelsNioBuffer = Buffers.newDirectIntBuffer(MAX_NUM_CHANNELS); + al.alGenSources(MAX_NUM_CHANNELS, channelsNioBuffer); + for (int i = 0; i < MAX_NUM_CHANNELS; i++) { + int chan = channelsNioBuffer.get(i); + if (chan != 0) { + 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 AudioSource[channels.length]; + + logger.log(Level.FINE, "AudioRenderer supports {0} channels", channels.length); + + supportEfx = false;//FIXME update JOAL with EFX support + //supportEfx = alc.alcIsExtensionPresent(device, "ALC_EXT_EFX"); + if (supportEfx) { + ib.position(0).limit(1); + alc.alcGetIntegerv(device, ALExtConstants.ALC_EFX_MAJOR_VERSION, 1, ib); + int major = ib.get(0); + ib.position(0).limit(1); + alc.alcGetIntegerv(device, ALExtConstants.ALC_EFX_MINOR_VERSION, 1, ib); + int minor = ib.get(0); + logger.log(Level.FINE, "Audio effect extension version: {0}.{1}", new Object[]{major, minor}); + + alc.alcGetIntegerv(device, ALExtConstants.ALC_MAX_AUXILIARY_SENDS, 1, ib); + auxSends = ib.get(0); + logger.log(Level.FINE, "Audio max auxilary sends: {0}", auxSends); + + // create slot + ib.position(0).limit(1); + alExt.alGenAuxiliaryEffectSlots(1, ib); + reverbFxSlot = ib.get(0); + + // create effect + ib.position(0).limit(1); + alExt.alGenEffects(1, ib); + reverbFx = ib.get(0); + alExt.alEffecti(reverbFx, ALExtConstants.AL_EFFECT_TYPE, ALExtConstants.AL_EFFECT_REVERB); + + // attach reverb effect to effect slot + alExt.alAuxiliaryEffectSloti(reverbFxSlot, ALExtConstants.AL_EFFECTSLOT_EFFECT, reverbFx); + } else { + logger.log(Level.WARNING, "OpenAL EFX not available! Audio effects won't work."); + } + } catch (ALException ex) { + logger.log(Level.SEVERE, "Failed to load audio library", ex); + audioDisabled = true; + } catch (UnsatisfiedLinkError ex) { + logger.log(Level.SEVERE, "Failed to load audio library", ex); + audioDisabled = true; + } + } + + public void cleanupInThread() { + if (audioDisabled) { + //FIXME + //AL.destroy(); + return; + } + + // stop any playing channels + for (int i = 0; i < chanSrcs.length; i++) { + if (chanSrcs[i] != null) { + clearChannel(i); + } + } + + // delete channel-based sources + ib.clear(); + ib.put(channels); + ib.flip(); + al.alDeleteSources(ib.limit(), ib); + + // delete audio buffers and filters + objManager.deleteAllObjects(this); + + if (supportEfx) { + ib.position(0).limit(1); + ib.put(0, reverbFx); + alExt.alDeleteEffects(1, ib); + + // If this is not allocated, why is it deleted? + // Commented out to fix native crash in OpenAL. + ib.position(0).limit(1); + ib.put(0, reverbFxSlot); + alExt.alDeleteAuxiliaryEffectSlots(1, ib); + } + + //FIXME + //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); + alExt.alGenFilters(1, ib); + id = ib.get(0); + f.setId(id); + + objManager.registerObject(f); + } + + if (f instanceof LowPassFilter) { + LowPassFilter lpf = (LowPassFilter) f; + alExt.alFilteri(id, ALExtConstants.AL_FILTER_TYPE, ALExtConstants.AL_FILTER_LOWPASS); + alExt.alFilterf(id, ALExtConstants.AL_LOWPASS_GAIN, lpf.getVolume()); + alExt.alFilterf(id, ALExtConstants.AL_LOWPASS_GAINHF, lpf.getHighFreqVolume()); + } else { + throw new UnsupportedOperationException("Filter type unsupported: " + + f.getClass().getName()); + } + + f.clearUpdateNeeded(); + } + + public void updateSourceParam(AudioSource src, AudioParam param) { + checkDead(); + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled) { + return; + } + + // There is a race condition in AudioSource that can + // cause this to be called for a node that has been + // detached from its channel. For example, setVolume() + // called from the render thread may see that that AudioSource + // still has a channel value but the audio thread may + // clear that channel before setVolume() gets to call + // updateSourceParam() (because the audio stopped playing + // on its own right as the volume was set). In this case, + // it should be safe to just ignore the update + if (src.getChannel() < 0) { + return; + } + + assert src.getChannel() >= 0; + + int id = channels[src.getChannel()]; + switch (param) { + case Position: + if (!src.isPositional()) { + return; + } + + Vector3f pos = src.getPosition(); + al.alSource3f(id, ALConstants.AL_POSITION, pos.x, pos.y, pos.z); + break; + case Velocity: + if (!src.isPositional()) { + return; + } + + Vector3f vel = src.getVelocity(); + al.alSource3f(id, ALConstants.AL_VELOCITY, vel.x, vel.y, vel.z); + break; + case MaxDistance: + if (!src.isPositional()) { + return; + } + + al.alSourcef(id, ALConstants.AL_MAX_DISTANCE, src.getMaxDistance()); + break; + case RefDistance: + if (!src.isPositional()) { + return; + } + + al.alSourcef(id, ALConstants.AL_REFERENCE_DISTANCE, src.getRefDistance()); + break; + case ReverbFilter: + if (!supportEfx || !src.isPositional() || !src.isReverbEnabled()) { + return; + } + + int filter = ALExtConstants.AL_FILTER_NULL; + if (src.getReverbFilter() != null) { + Filter f = src.getReverbFilter(); + if (f.isUpdateNeeded()) { + updateFilter(f); + } + filter = f.getId(); + } + al.alSource3i(id, ALExtConstants.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); + break; + case ReverbEnabled: + if (!supportEfx || !src.isPositional()) { + return; + } + + if (src.isReverbEnabled()) { + updateSourceParam(src, AudioParam.ReverbFilter); + } else { + al.alSource3i(id, ALExtConstants.AL_AUXILIARY_SEND_FILTER, 0, 0, ALExtConstants.AL_FILTER_NULL); + } + break; + case IsPositional: + if (!src.isPositional()) { + // Play in headspace + al.alSourcei(id, ALConstants.AL_SOURCE_RELATIVE, ALConstants.AL_TRUE); + al.alSource3f(id, ALConstants.AL_POSITION, 0, 0, 0); + al.alSource3f(id, ALConstants.AL_VELOCITY, 0, 0, 0); + + // Disable reverb + al.alSource3i(id, ALExtConstants.AL_AUXILIARY_SEND_FILTER, 0, 0, ALExtConstants.AL_FILTER_NULL); + } else { + al.alSourcei(id, ALConstants.AL_SOURCE_RELATIVE, ALConstants.AL_FALSE); + updateSourceParam(src, AudioParam.Position); + updateSourceParam(src, AudioParam.Velocity); + updateSourceParam(src, AudioParam.MaxDistance); + updateSourceParam(src, AudioParam.RefDistance); + updateSourceParam(src, AudioParam.ReverbEnabled); + } + break; + case Direction: + if (!src.isDirectional()) { + return; + } + + Vector3f dir = src.getDirection(); + al.alSource3f(id, ALConstants.AL_DIRECTION, dir.x, dir.y, dir.z); + break; + case InnerAngle: + if (!src.isDirectional()) { + return; + } + + al.alSourcef(id, ALConstants.AL_CONE_INNER_ANGLE, src.getInnerAngle()); + break; + case OuterAngle: + if (!src.isDirectional()) { + return; + } + + al.alSourcef(id, ALConstants.AL_CONE_OUTER_ANGLE, src.getOuterAngle()); + break; + case IsDirectional: + if (src.isDirectional()) { + updateSourceParam(src, AudioParam.Direction); + updateSourceParam(src, AudioParam.InnerAngle); + updateSourceParam(src, AudioParam.OuterAngle); + al.alSourcef(id, ALConstants.AL_CONE_OUTER_GAIN, 0); + } else { + al.alSourcef(id, ALConstants.AL_CONE_INNER_ANGLE, 360); + al.alSourcef(id, ALConstants.AL_CONE_OUTER_ANGLE, 360); + al.alSourcef(id, ALConstants.AL_CONE_OUTER_GAIN, 1f); + } + break; + case DryFilter: + if (!supportEfx) { + return; + } + + if (src.getDryFilter() != null) { + Filter f = src.getDryFilter(); + if (f.isUpdateNeeded()) { + updateFilter(f); + + // NOTE: must re-attach filter for changes to apply. + al.alSourcei(id, ALExtConstants.AL_DIRECT_FILTER, f.getId()); + } + } else { + al.alSourcei(id, ALExtConstants.AL_DIRECT_FILTER, ALExtConstants.AL_FILTER_NULL); + } + break; + case Looping: + if (src.isLooping()) { + if (!(src.getAudioData() instanceof AudioStream)) { + al.alSourcei(id, ALConstants.AL_LOOPING, ALConstants.AL_TRUE); + } + } else { + al.alSourcei(id, ALConstants.AL_LOOPING, ALConstants.AL_FALSE); + } + break; + case Volume: + al.alSourcef(id, ALConstants.AL_GAIN, src.getVolume()); + break; + case Pitch: + al.alSourcef(id, ALConstants.AL_PITCH, src.getPitch()); + break; + } + } + } + + private void setSourceParams(int id, AudioSource src, boolean forceNonLoop) { + if (src.isPositional()) { + Vector3f pos = src.getPosition(); + Vector3f vel = src.getVelocity(); + al.alSource3f(id, ALConstants.AL_POSITION, pos.x, pos.y, pos.z); + al.alSource3f(id, ALConstants.AL_VELOCITY, vel.x, vel.y, vel.z); + al.alSourcef(id, ALConstants.AL_MAX_DISTANCE, src.getMaxDistance()); + al.alSourcef(id, ALConstants.AL_REFERENCE_DISTANCE, src.getRefDistance()); + al.alSourcei(id, ALConstants.AL_SOURCE_RELATIVE, ALConstants.AL_FALSE); + + if (src.isReverbEnabled() && supportEfx) { + int filter = ALExtConstants.AL_FILTER_NULL; + if (src.getReverbFilter() != null) { + Filter f = src.getReverbFilter(); + if (f.isUpdateNeeded()) { + updateFilter(f); + } + filter = f.getId(); + } + al.alSource3i(id, ALExtConstants.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); + } + } else { + // play in headspace + al.alSourcei(id, ALConstants.AL_SOURCE_RELATIVE, ALConstants.AL_TRUE); + al.alSource3f(id, ALConstants.AL_POSITION, 0, 0, 0); + al.alSource3f(id, ALConstants.AL_VELOCITY, 0, 0, 0); + } + + if (src.getDryFilter() != null && supportEfx) { + Filter f = src.getDryFilter(); + if (f.isUpdateNeeded()) { + updateFilter(f); + + // NOTE: must re-attach filter for changes to apply. + al.alSourcei(id, ALExtConstants.AL_DIRECT_FILTER, f.getId()); + } + } + + if (forceNonLoop) { + al.alSourcei(id, ALConstants.AL_LOOPING, ALConstants.AL_FALSE); + } else { + al.alSourcei(id, ALConstants.AL_LOOPING, src.isLooping() ? ALConstants.AL_TRUE : ALConstants.AL_FALSE); + } + al.alSourcef(id, ALConstants.AL_GAIN, src.getVolume()); + al.alSourcef(id, ALConstants.AL_PITCH, src.getPitch()); + al.alSourcef(id, AL.AL_SEC_OFFSET, src.getTimeOffset()); + + if (src.isDirectional()) { + Vector3f dir = src.getDirection(); + al.alSource3f(id, ALConstants.AL_DIRECTION, dir.x, dir.y, dir.z); + al.alSourcef(id, ALConstants.AL_CONE_INNER_ANGLE, src.getInnerAngle()); + al.alSourcef(id, ALConstants.AL_CONE_OUTER_ANGLE, src.getOuterAngle()); + al.alSourcef(id, ALConstants.AL_CONE_OUTER_GAIN, 0); + } else { + al.alSourcef(id, ALConstants.AL_CONE_INNER_ANGLE, 360); + al.alSourcef(id, ALConstants.AL_CONE_OUTER_ANGLE, 360); + al.alSourcef(id, ALConstants.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(); + al.alListener3f(ALConstants.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(); + al.alListenerfv(ALConstants.AL_ORIENTATION, fb); + break; + case Velocity: + Vector3f vel = listener.getVelocity(); + al.alListener3f(ALConstants.AL_VELOCITY, vel.x, vel.y, vel.z); + break; + case Volume: + al.alListenerf(ALConstants.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(); + + al.alListener3f(ALConstants.AL_POSITION, pos.x, pos.y, pos.z); + al.alListener3f(ALConstants.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(); + al.alListenerfv(ALConstants.AL_ORIENTATION, fb); + al.alListenerf(ALConstants.AL_GAIN, listener.getVolume()); + } + + private int newChannel() { + if (freeChans.size() > 0) { + return freeChans.remove(0); + } else if (nextChan < channels.length) { + return nextChan++; + } else { + return -1; + } + } + + private void freeChannel(int index) { + if (index == nextChan - 1) { + nextChan--; + } else { + freeChans.add(index); + } + } + + public void setEnvironment(Environment env) { + checkDead(); + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled || !supportEfx) { + return; + } + + alExt.alEffectf(reverbFx, ALExtConstants.AL_REVERB_DENSITY, env.getDensity()); + alExt.alEffectf(reverbFx, ALExtConstants.AL_REVERB_DIFFUSION, env.getDiffusion()); + alExt.alEffectf(reverbFx, ALExtConstants.AL_REVERB_GAIN, env.getGain()); + alExt.alEffectf(reverbFx, ALExtConstants.AL_REVERB_GAINHF, env.getGainHf()); + alExt.alEffectf(reverbFx, ALExtConstants.AL_REVERB_DECAY_TIME, env.getDecayTime()); + alExt.alEffectf(reverbFx, ALExtConstants.AL_REVERB_DECAY_HFRATIO, env.getDecayHFRatio()); + alExt.alEffectf(reverbFx, ALExtConstants.AL_REVERB_REFLECTIONS_GAIN, env.getReflectGain()); + alExt.alEffectf(reverbFx, ALExtConstants.AL_REVERB_REFLECTIONS_DELAY, env.getReflectDelay()); + alExt.alEffectf(reverbFx, ALExtConstants.AL_REVERB_LATE_REVERB_GAIN, env.getLateReverbGain()); + alExt.alEffectf(reverbFx, ALExtConstants.AL_REVERB_LATE_REVERB_DELAY, env.getLateReverbDelay()); + alExt.alEffectf(reverbFx, ALExtConstants.AL_REVERB_AIR_ABSORPTION_GAINHF, env.getAirAbsorbGainHf()); + alExt.alEffectf(reverbFx, ALExtConstants.AL_REVERB_ROOM_ROLLOFF_FACTOR, env.getRoomRolloffFactor()); + + // attach effect to slot + alExt.alAuxiliaryEffectSloti(reverbFxSlot, ALExtConstants.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(); + + al.alBufferData(id, convertFormat(stream), nativeBuf, size, stream.getSampleRate()); + + return true; + } + + private boolean fillStreamingSource(int sourceId, AudioStream stream) { + if (!stream.isOpen()) { + return false; + } + + boolean active = true; + al.alGetSourcei(sourceId, AL.AL_BUFFERS_PROCESSED, ib); + int processed = ib.get(0); + +// while((processed--) != 0){ + if (processed > 0) { + int buffer; + + ib.position(0).limit(1); + al.alSourceUnqueueBuffers(sourceId, 1, ib); + buffer = ib.get(0); + + active = fillBuffer(stream, buffer); + + ib.position(0).limit(1); + ib.put(0, buffer); + al.alSourceQueueBuffers(sourceId, 1, 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(); + al.alSourceQueueBuffers(sourceId, 1, ib); + } + return active; + } + + private boolean attachBufferToSource(int sourceId, AudioBuffer buffer) { + al.alSourcei(sourceId, ALConstants.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) { + AudioSource src = chanSrcs[index]; + + int sourceId = channels[index]; + al.alSourceStop(sourceId); + + if (src.getAudioData() instanceof AudioStream) { + AudioStream str = (AudioStream) src.getAudioData(); + ib.position(0).limit(STREAMING_BUFFER_COUNT); + ib.put(str.getIds()).flip(); + al.alSourceUnqueueBuffers(sourceId, 1, ib); + } else if (src.getAudioData() instanceof AudioBuffer) { + al.alSourcei(sourceId, AL.AL_BUFFER, 0); + } + + if (src.getDryFilter() != null && supportEfx) { + // detach filter + al.alSourcei(sourceId, ALExtConstants.AL_DIRECT_FILTER, ALExtConstants.AL_FILTER_NULL); + } + if (src.isPositional()) { + AudioSource pas = (AudioSource) src; + if (pas.isReverbEnabled() && supportEfx) { + al.alSource3i(sourceId, ALExtConstants.AL_AUXILIARY_SEND_FILTER, 0, 0, ALExtConstants.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++) { + AudioSource 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); + + ib.position(0).limit(1); + al.alGetSourcei(sourceId, AL.AL_SOURCE_STATE, ib); + int state = ib.get(0); + boolean wantPlaying = src.getStatus() == AudioSource.Status.Playing; + boolean stopped = state == ALConstants.AL_STOPPED; + + if (streaming && wantPlaying) { + AudioStream stream = (AudioStream) src.getAudioData(); + if (stream.isOpen()) { + fillStreamingSource(sourceId, stream); + if (stopped) { + al.alSourcePlay(sourceId); + } + } else { + if (stopped) { + // became inactive + src.setStatus(AudioSource.Status.Stopped); + src.setChannel(-1); + clearChannel(i); + freeChannel(i); + + // And free the audio since it cannot be + // played again anyway. + deleteAudioData(stream); + } + } + } else if (!streaming) { + boolean paused = state == ALConstants.AL_PAUSED; + + // make sure OAL pause state & source state coincide + assert (src.getStatus() == AudioSource.Status.Paused && paused) || (!paused); + + if (stopped) { + if (boundSource) { + src.setStatus(AudioSource.Status.Stopped); + src.setChannel(-1); + } + clearChannel(i); + freeChannel(i); + } + } + } + + // Delete any unused objects. + objManager.deleteUnused(this); + } + + public void setListener(Listener listener) { + checkDead(); + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled) { + return; + } + + if (this.listener != null) { + // previous listener no longer associated with current + // renderer + this.listener.setRenderer(null); + } + + this.listener = listener; + this.listener.setRenderer(this); + setListenerParams(listener); + } + } + + public void playSourceInstance(AudioSource 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 + al.alSourcePlay(sourceId); + } + } + + public void playSource(AudioSource 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() == AudioSource.Status.Playing) { + return; + } else if (src.getStatus() == AudioSource.Status.Stopped) { + + // allocate channel to this source + int index = newChannel(); + if (index == -1) { + logger.log(Level.WARNING, "No channel available to play {0}", src); + return; + } + clearChannel(index); + src.setChannel(index); + + AudioData data = src.getAudioData(); + if (data.isUpdateNeeded()) { + updateAudioData(data); + } + + chanSrcs[index] = src; + setSourceParams(channels[index], src, false); + attachAudioToSource(channels[index], data); + } + + al.alSourcePlay(channels[src.getChannel()]); + src.setStatus(AudioSource.Status.Playing); + } + } + + public void pauseSource(AudioSource src) { + checkDead(); + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled) { + return; + } + + if (src.getStatus() == AudioSource.Status.Playing) { + assert src.getChannel() != -1; + + al.alSourcePause(channels[src.getChannel()]); + src.setStatus(AudioSource.Status.Paused); + } + } + } + + public void stopSource(AudioSource src) { + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled) { + return; + } + + if (src.getStatus() != AudioSource.Status.Stopped) { + int chan = src.getChannel(); + assert chan != -1; // if it's not stopped, must have id + + src.setStatus(AudioSource.Status.Stopped); + src.setChannel(-1); + clearChannel(chan); + freeChannel(chan); + + if (src.getAudioData() instanceof AudioStream) { + AudioStream stream = (AudioStream) src.getAudioData(); + if (stream.isOpen()) { + stream.close(); + } + + // And free the audio since it cannot be + // played again anyway. + deleteAudioData(src.getAudioData()); + } + } + } + } + + private int convertFormat(AudioData ad) { + switch (ad.getBitsPerSample()) { + case 8: + if (ad.getChannels() == 1) { + return ALConstants.AL_FORMAT_MONO8; + } else if (ad.getChannels() == 2) { + return ALConstants.AL_FORMAT_STEREO8; + } + + break; + case 16: + if (ad.getChannels() == 1) { + return ALConstants.AL_FORMAT_MONO16; + } else { + return ALConstants.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); + al.alGenBuffers(ib.limit(), ib); + id = ib.get(0); + ab.setId(id); + + objManager.registerObject(ab); + } + + ab.getData().clear(); + al.alBufferData(id, convertFormat(ab), ab.getData(), ab.getData().remaining(), 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); + al.alGenBuffers(ib.limit(), ib); + ib.position(0).limit(STREAMING_BUFFER_COUNT); + ib.get(ids); + + // Not registered with object manager. + // AudioStreams can be handled without object manager + // since their lifecycle is known to the audio renderer. + + as.setIds(ids); + as.clearUpdateNeeded(); + } + + private void updateAudioData(AudioData ad) { + if (ad instanceof AudioBuffer) { + updateAudioBuffer((AudioBuffer) ad); + } else if (ad instanceof AudioStream) { + updateAudioStream((AudioStream) ad); + } + } + + public void deleteFilter(Filter filter) { + int id = filter.getId(); + if (id != -1) { + ib.put(0, id); + ib.position(0).limit(1); + alExt.alDeleteFilters(1, ib); + } + } + + public void deleteAudioData(AudioData ad) { + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled) { + return; + } + + if (ad instanceof AudioBuffer) { + AudioBuffer ab = (AudioBuffer) ad; + int id = ab.getId(); + if (id != -1) { + ib.put(0, id); + ib.position(0).limit(1); + al.alDeleteBuffers(ib.limit(), 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(); + al.alDeleteBuffers(ib.limit(), ib); + as.resetObject(); + } + } + } + } +} \ No newline at end of file diff --git a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtKeyInput.java b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtKeyInput.java new file mode 100644 index 000000000..8ae427074 --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtKeyInput.java @@ -0,0 +1,589 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.jogl; + +import com.jme3.input.KeyInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.KeyInputEvent; +import com.jogamp.newt.event.KeyEvent; +import com.jogamp.newt.event.KeyListener; +import com.jogamp.newt.opengl.GLWindow; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class NewtKeyInput implements KeyInput, KeyListener { + + private static final Logger logger = Logger.getLogger(NewtKeyInput.class.getName()); + + private final ArrayList eventQueue = new ArrayList(); + private RawInputListener listener; + private GLWindow component; + + public NewtKeyInput() { + } + + public void initialize() { + } + + public void destroy() { + } + + public void setInputSource(GLWindow comp){ + synchronized (eventQueue){ + if (component != null){ + component.removeKeyListener(this); + eventQueue.clear(); + } + component = comp; + component.addKeyListener(this); + } + } + + public long getInputTimeNanos() { + return System.nanoTime(); + } + + public void update() { + synchronized (eventQueue){ + // flush events to listener + for (int i = 0; i < eventQueue.size(); i++){ + listener.onKeyEvent(eventQueue.get(i)); + } + eventQueue.clear(); + } + } + + public boolean isInitialized() { + return true; + } + + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + public void keyTyped(KeyEvent evt) { + } + + public void keyPressed(KeyEvent evt) { + int code = convertNewtKey(evt.getKeySymbol()); + KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), true, evt.isAutoRepeat()); + keyEvent.setTime(evt.getWhen()); + synchronized (eventQueue){ + eventQueue.add(keyEvent); + } + } + + public void keyReleased(KeyEvent evt) { + int code = convertNewtKey(evt.getKeySymbol()); + KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), false, evt.isAutoRepeat()); + keyEvent.setTime(evt.getWhen()); + synchronized (eventQueue) { + 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.log(Level.WARNING, "unsupported key:{0}", 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 convertNewtKey(short 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.log( Level.WARNING, "unsupported key:{0}", key); + if ( key >= 0x10000 ) { + return key - 0x10000; + } + + return 0; + } + +} diff --git a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java new file mode 100644 index 000000000..65c749f10 --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.jogl; + +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.MouseInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jogamp.newt.event.MouseEvent; +import com.jogamp.newt.event.MouseListener; +import com.jogamp.newt.opengl.GLWindow; +import java.util.ArrayList; +import java.util.logging.Logger; +import javax.media.nativewindow.util.Point; + +public class NewtMouseInput implements MouseInput, MouseListener { + + 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(NewtMouseInput.class.getName()); + + private boolean visible = true; + + private RawInputListener listener; + + private GLWindow component; + + private final ArrayList eventQueue = new ArrayList(); + private final ArrayList eventQueueCopy = new ArrayList(); + + private int lastEventX; + private int lastEventY; + private int lastEventWheel; + + private int wheelPos; + private Point location; + private Point centerLocation; + private Point centerLocationOnScreen; + private Point lastKnownLocation; + private boolean isRecentering; + private boolean cursorMoved; + private int eventsSinceRecenter; + + public NewtMouseInput() { + location = new Point(); + centerLocation = new Point(); + centerLocationOnScreen = new Point(); + lastKnownLocation = new Point(); + } + + public void setInputSource(GLWindow comp) { + if (component != null) { + component.removeMouseListener(this); + + eventQueue.clear(); + + wheelPos = 0; + isRecentering = false; + eventsSinceRecenter = 0; + lastEventX = 0; + lastEventY = 0; + lastEventWheel = 0; + location = new Point(); + centerLocation = new Point(); + centerLocationOnScreen = new Point(); + lastKnownLocation = new Point(); + } + + component = comp; + component.addMouseListener(this); + } + + public void initialize() { + } + + public void destroy() { + } + + public boolean isInitialized() { + return true; + } + + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + public long getInputTimeNanos() { + return System.nanoTime(); + } + + public void setCursorVisible(boolean visible) { + if (this.visible != visible) { + lastKnownLocation.setX(0); + lastKnownLocation.setY(0); + + this.visible = visible; + component.setPointerVisible(visible); + if (!visible) { + recenterMouse(component); + } + } + } + + public void update() { + if (cursorMoved) { + int newX = location.getX(); + int newY = location.getY(); + int newWheel = wheelPos; + + // invert DY + int actualX = lastKnownLocation.getX(); + int actualY = component.getHeight() - lastKnownLocation.getY(); + MouseMotionEvent evt = new MouseMotionEvent(actualX, actualY, + newX - lastEventX, + lastEventY - newY, + wheelPos, lastEventWheel - wheelPos); + listener.onMouseMotionEvent(evt); + + lastEventX = newX; + lastEventY = newY; + lastEventWheel = newWheel; + + cursorMoved = false; + } + + synchronized (eventQueue) { + eventQueueCopy.clear(); + eventQueueCopy.addAll(eventQueue); + eventQueue.clear(); + } + + int size = eventQueueCopy.size(); + for (int i = 0; i < size; i++) { + listener.onMouseButtonEvent(eventQueueCopy.get(i)); + } + } + + public int getButtonCount() { + return 3; + } + + public void mouseClicked(MouseEvent awtEvt) { +// MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false); +// listener.onMouseButtonEvent(evt); + } + + public void mousePressed(MouseEvent awtEvt) { + MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(awtEvt), true, awtEvt.getX(), awtEvt.getY()); + evt.setTime(awtEvt.getWhen()); + synchronized (eventQueue) { + eventQueue.add(evt); + } + } + + public void mouseReleased(MouseEvent awtEvt) { + MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(awtEvt), false, awtEvt.getX(), awtEvt.getY()); + evt.setTime(awtEvt.getWhen()); + synchronized (eventQueue) { + eventQueue.add(evt); + } + } + + public void mouseEntered(MouseEvent awtEvt) { + if (!visible) { + recenterMouse(component); + } + } + + public void mouseExited(MouseEvent awtEvt) { + if (!visible) { + recenterMouse(component); + } + } + + public void mouseWheelMoved(MouseEvent awtEvt) { + //FIXME not sure this is the right way to handle this case + // [0] should be used when the shift key is down + float dwheel = awtEvt.getRotation()[1]; + wheelPos += dwheel * WHEEL_AMP; + cursorMoved = true; + } + + public void mouseDragged(MouseEvent awtEvt) { + mouseMoved(awtEvt); + } + + public void mouseMoved(MouseEvent awtEvt) { + 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.getX() == awtEvt.getX() && centerLocation.getY() == awtEvt.getY()) || eventsSinceRecenter++ == 5) { + lastKnownLocation.setX(awtEvt.getX()); + lastKnownLocation.setY(awtEvt.getY()); + isRecentering = false; + } + } else { + // MHenze (cylab) Fix Issue 35: + // Compute the delta and absolute coordinates and recenter the mouse if necessary + int dx = awtEvt.getX() - lastKnownLocation.getX(); + int dy = awtEvt.getY() - lastKnownLocation.getY(); + location.setX(location.getX() + dx); + location.setY(location.getY() + dy); + if (!visible) { + recenterMouse(component); + } + lastKnownLocation.setX(awtEvt.getX()); + lastKnownLocation.setY(awtEvt.getY()); + + cursorMoved = true; + } + } + + // MHenze (cylab) Fix Issue 35: A method to generate recenter the mouse to allow the InputSystem to "grab" the mouse + private void recenterMouse(final GLWindow component) { + eventsSinceRecenter = 0; + isRecentering = true; + centerLocation.setX(component.getWidth() / 2); + centerLocation.setY(component.getHeight() / 2); + centerLocationOnScreen.setX(centerLocation.getX()); + centerLocationOnScreen.setY(centerLocation.getY()); + + component.warpPointer(centerLocationOnScreen.getX(), centerLocationOnScreen.getY()); + } + + private int getJMEButtonIndex(MouseEvent awtEvt) { + int index; + switch (awtEvt.getButton()) { + default: + case MouseEvent.BUTTON1: //left + index = MouseInput.BUTTON_LEFT; + break; + case MouseEvent.BUTTON2: //middle + index = MouseInput.BUTTON_MIDDLE; + break; + case MouseEvent.BUTTON3: //right + index = MouseInput.BUTTON_RIGHT; + break; + case MouseEvent.BUTTON4: + case MouseEvent.BUTTON5: + case MouseEvent.BUTTON6: + case MouseEvent.BUTTON7: + case MouseEvent.BUTTON8: + case MouseEvent.BUTTON9: + //FIXME + index = 0; + break; + } + return index; + } + + public void setNativeCursor(JmeCursor cursor) { + } +} \ No newline at end of file diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL1Renderer.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL1Renderer.java new file mode 100644 index 000000000..18f25206a --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL1Renderer.java @@ -0,0 +1,1257 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.jogl; + +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.EnumSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +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.GLMatrixFunc; +import javax.media.opengl.fixedfunc.GLPointerFunc; + +import jme3tools.converters.MipMapGenerator; + +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.material.FixedFuncBinding; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.GL1Renderer; +import com.jme3.renderer.RenderContext; +import com.jme3.renderer.RendererException; +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.NativeObjectManager; + +public class JoglGL1Renderer implements GL1Renderer { + + private static final Logger logger = Logger.getLogger(JoglRenderer.class.getName()); + private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); + private final StringBuilder stringBuf = new StringBuilder(250); + private final IntBuffer ib1 = BufferUtils.createIntBuffer(1); + private final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16); + private final FloatBuffer fb16 = BufferUtils.createFloatBuffer(16); + private final FloatBuffer fb4Null = BufferUtils.createFloatBuffer(4); + private final RenderContext context = new RenderContext(); + private final NativeObjectManager objManager = new NativeObjectManager(); + private final EnumSet caps = EnumSet.noneOf(Caps.class); + private int maxTexSize; + private int maxCubeTexSize; + private int maxVertCount; + private int maxTriCount; + private int maxLights; + private boolean gl12 = false; + private final Statistics statistics = new Statistics(); + private int vpX, vpY, vpW, vpH; + private int clipX, clipY, clipW, clipH; + + private Matrix4f worldMatrix = new Matrix4f(); + private Matrix4f viewMatrix = new Matrix4f(); + + private ArrayList lightList = new ArrayList(8); + private ColorRGBA materialAmbientColor = new ColorRGBA(); + private Vector3f tempVec = new Vector3f(); + + protected void updateNameBuffer() { + int len = stringBuf.length(); + + nameBuf.position(0); + nameBuf.limit(len); + for (int i = 0; i < len; i++) { + nameBuf.put((byte) stringBuf.charAt(i)); + } + + nameBuf.rewind(); + } + + public Statistics getStatistics() { + return statistics; + } + + public EnumSet getCaps() { + return caps; + } + + public void initialize() { + GL gl = GLContext.getCurrentGL(); + if (gl.isExtensionAvailable("GL_VERSION_1_2")){ + gl12 = true; + } + + // Default values for certain GL state. + gl.getGL2ES1().glShadeModel(GLLightingFunc.GL_SMOOTH); + gl.getGL2().glColorMaterial(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_DIFFUSE); + gl.glHint(GL2ES1.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST); + + // Enable rescaling/normaling of normal vectors. + // Fixes lighting issues with scaled models. + if (gl12){ + gl.glEnable(GL2ES1.GL_RESCALE_NORMAL); + }else{ + gl.glEnable(GLLightingFunc.GL_NORMALIZE); + } + + if (gl.isExtensionAvailable("GL_ARB_texture_non_power_of_two")) { + caps.add(Caps.NonPowerOfTwoTextures); + } else { + logger.log(Level.WARNING, "Your graphics card does not " + + "support non-power-of-2 textures. " + + "Some features might not work."); + } + + gl.glGetIntegerv(GL2ES1.GL_MAX_LIGHTS, ib1); + maxLights = ib1.get(0); + + gl.glGetIntegerv(GL.GL_MAX_TEXTURE_SIZE, ib1); + maxTexSize = ib1.get(0); + } + + public void invalidateState() { + context.reset(); + } + + public void resetGLObjects() { + logger.log(Level.FINE, "Reseting objects and invalidating state"); + objManager.resetObjects(); + statistics.clearMemory(); + invalidateState(); + } + + public void cleanup() { + logger.log(Level.FINE, "Deleting objects and invalidating state"); + objManager.deleteAllObjects(this); + statistics.clearMemory(); + invalidateState(); + } + + public void setDepthRange(float start, float end) { + GL gl = GLContext.getCurrentGL(); + gl.getGL2ES2().glDepthRange(start, end); + } + + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + GL gl = GLContext.getCurrentGL(); + int bits = 0; + if (color) { + //See explanations of the depth below, we must enable color write to be able to clear the color buffer + if (context.colorWriteEnabled == false) { + gl.glColorMask(true, true, true, true); + context.colorWriteEnabled = true; + } + bits = GL.GL_COLOR_BUFFER_BIT; + } + if (depth) { + + //glClear(GL_DEPTH_BUFFER_BIT) seems to not work when glDepthMask is false + //here s some link on openl board + //http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=257223 + //if depth clear is requested, we enable the depthMask + if (context.depthWriteEnabled == false) { + gl.glDepthMask(true); + context.depthWriteEnabled = true; + } + bits |= GL.GL_DEPTH_BUFFER_BIT; + } + if (stencil) { + bits |= GL.GL_STENCIL_BUFFER_BIT; + } + if (bits != 0) { + gl.glClear(bits); + } + } + + public void setBackgroundColor(ColorRGBA color) { + GL gl = GLContext.getCurrentGL(); + gl.glClearColor(color.r, color.g, color.b, color.a); + } + + private void setMaterialColor(int type, ColorRGBA color, ColorRGBA defaultColor) { + GL gl = GLContext.getCurrentGL(); + if (color != null){ + fb16.put(color.r).put(color.g).put(color.b).put(color.a).flip(); + }else{ + fb16.put(defaultColor.r).put(defaultColor.g).put(defaultColor.b).put(defaultColor.a).flip(); + } + gl.getGL2().glMaterialfv(GL.GL_FRONT_AND_BACK, type, fb16); + } + + /** + * Applies fixed function bindings from the context to OpenGL + */ + private void applyFixedFuncBindings(boolean forLighting){ + GL gl = GLContext.getCurrentGL(); + if (forLighting) { + gl.getGL2().glMaterialf(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_SHININESS, context.shininess); + setMaterialColor(GLLightingFunc.GL_AMBIENT, context.ambient, ColorRGBA.DarkGray); + setMaterialColor(GLLightingFunc.GL_DIFFUSE, context.diffuse, ColorRGBA.White); + setMaterialColor(GLLightingFunc.GL_SPECULAR, context.specular, ColorRGBA.Black); + + if (context.useVertexColor) { + gl.glEnable(GLLightingFunc.GL_COLOR_MATERIAL); + } else { + gl.glDisable(GLLightingFunc.GL_COLOR_MATERIAL); + } + } else { + // Ignore other values as they have no effect when + // GL_LIGHTING is disabled. + ColorRGBA color = context.color; + if (color != null) { + gl.getGL2().glColor4f(color.r, color.g, color.b, color.a); + } else { + gl.getGL2().glColor4f(1, 1, 1, 1); + } + } + if (context.alphaTestFallOff > 0f) { + gl.glEnable(GL2ES1.GL_ALPHA_TEST); + gl.getGL2ES1().glAlphaFunc(GL.GL_GREATER, context.alphaTestFallOff); + } else { + gl.glDisable(GL2ES1.GL_ALPHA_TEST); + } + } + + /** + * Reset fixed function bindings to default values. + */ + private void resetFixedFuncBindings(){ + context.alphaTestFallOff = 0f; // zero means disable alpha test! + context.color = null; + context.ambient = null; + context.diffuse = null; + context.specular = null; + context.shininess = 0; + context.useVertexColor = false; + } + + public void setFixedFuncBinding(FixedFuncBinding ffBinding, Object val) { + switch (ffBinding) { + case Color: + context.color = (ColorRGBA) val; + break; + case MaterialAmbient: + context.ambient = (ColorRGBA) val; + break; + case MaterialDiffuse: + context.diffuse = (ColorRGBA) val; + break; + case MaterialSpecular: + context.specular = (ColorRGBA) val; + break; + case MaterialShininess: + context.shininess = (Float) val; + break; + case UseVertexColor: + context.useVertexColor = (Boolean) val; + break; + case AlphaTestFallOff: + context.alphaTestFallOff = (Float) val; + break; + } + } + + public void applyRenderState(RenderState state) { + GL gl = GLContext.getCurrentGL(); + if (state.isWireframe() && !context.wireframe) { + gl.getGL2GL3().glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_LINE); + context.wireframe = true; + } else if (!state.isWireframe() && context.wireframe) { + gl.getGL2GL3().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()) { + setFixedFuncBinding(FixedFuncBinding.AlphaTestFallOff, state.getAlphaFallOff()); + } else { + setFixedFuncBinding(FixedFuncBinding.AlphaTestFallOff, 0f); // disable it + } + + 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()) { + logger.log(Level.WARNING, "Point Sprite unsupported!"); + } + + 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 Color: + gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR); + 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(); + } + + if (state.isStencilTest()) { + throw new UnsupportedOperationException("OpenGL 1.1 doesn't support two sided stencil operations."); + } + + } + + public void setViewPort(int x, int y, int w, int h) { + if (x != vpX || vpY != y || vpW != w || vpH != h) { + GL gl = GLContext.getCurrentGL(); + gl.glViewport(x, y, w, h); + vpX = x; + vpY = y; + vpW = w; + vpH = h; + } + } + + public void setClipRect(int x, int y, int width, int height) { + GL gl = GLContext.getCurrentGL(); + if (!context.clipRectEnabled) { + gl.glEnable(GL.GL_SCISSOR_TEST); + context.clipRectEnabled = true; + } + if (clipX != x || clipY != y || clipW != width || clipH != height) { + gl.glScissor(x, y, width, height); + clipX = x; + clipY = y; + clipW = width; + clipH = height; + } + } + + public void clearClipRect() { + GL gl = GLContext.getCurrentGL(); + if (context.clipRectEnabled) { + gl.glDisable(GL.GL_SCISSOR_TEST); + context.clipRectEnabled = false; + + clipX = 0; + clipY = 0; + clipW = 0; + clipH = 0; + } + } + + public void onFrame() { + objManager.deleteUnused(this); +// statistics.clearFrame(); + } + + private FloatBuffer storeMatrix(Matrix4f matrix, FloatBuffer store) { + store.clear(); + matrix.fillFloatBuffer(store, true); + store.clear(); + return store; + } + + private void setModelView(Matrix4f modelMatrix, Matrix4f viewMatrix){ + GL gl = GLContext.getCurrentGL(); + if (context.matrixMode != GLMatrixFunc.GL_MODELVIEW) { + gl.getGL2ES1().glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + context.matrixMode = GLMatrixFunc.GL_MODELVIEW; + } + + gl.getGL2ES1().glLoadMatrixf(storeMatrix(viewMatrix, fb16)); + gl.getGL2ES1().glMultMatrixf(storeMatrix(modelMatrix, fb16)); + } + + private void setProjection(Matrix4f projMatrix){ + GL gl = GLContext.getCurrentGL(); + if (context.matrixMode != GLMatrixFunc.GL_PROJECTION) { + gl.getGL2ES1().glMatrixMode(GLMatrixFunc.GL_PROJECTION); + context.matrixMode = GLMatrixFunc.GL_PROJECTION; + } + + gl.getGL2ES1().glLoadMatrixf(storeMatrix(projMatrix, fb16)); + } + + public void setWorldMatrix(Matrix4f worldMatrix) { + this.worldMatrix.set(worldMatrix); + } + + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { + this.viewMatrix.set(viewMatrix); + setProjection(projMatrix); + } + + public void setLighting(LightList list) { + GL gl = GLContext.getCurrentGL(); + // XXX: This is abuse of setLighting() to + // apply fixed function bindings + // and do other book keeping. + if (list == null || list.size() == 0){ + gl.glDisable(GLLightingFunc.GL_LIGHTING); + applyFixedFuncBindings(false); + setModelView(worldMatrix, viewMatrix); + return; + } + + // Number of lights set previously + int numLightsSetPrev = lightList.size(); + + // If more than maxLights are defined, they will be ignored. + // The GL1 renderer is not permitted to crash due to a + // GL1 limitation. It must render anything that the GL2 renderer + // can render (even incorrectly). + lightList.clear(); + materialAmbientColor.set(0, 0, 0, 0); + + for (int i = 0; i < list.size(); i++){ + Light l = list.get(i); + if (l.getType() == Light.Type.Ambient){ + // Gather + materialAmbientColor.addLocal(l.getColor()); + }else{ + // Add to list + lightList.add(l); + + // Once maximum lights reached, exit loop. + if (lightList.size() >= maxLights){ + break; + } + } + } + + applyFixedFuncBindings(true); + + gl.glEnable(GLLightingFunc.GL_LIGHTING); + + fb16.clear(); + fb16.put(materialAmbientColor.r) + .put(materialAmbientColor.g) + .put(materialAmbientColor.b) + .put(1).flip(); + + gl.getGL2ES1().glLightModelfv(GL2ES1.GL_LIGHT_MODEL_AMBIENT, fb16); + + if (context.matrixMode != GLMatrixFunc.GL_MODELVIEW) { + gl.getGL2ES1().glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + context.matrixMode = GLMatrixFunc.GL_MODELVIEW; + } + // Lights are already in world space, so just convert + // them to view space. + gl.getGL2ES1().glLoadMatrixf(storeMatrix(viewMatrix, fb16)); + + for (int i = 0; i < lightList.size(); i++){ + int glLightIndex = GLLightingFunc.GL_LIGHT0 + i; + Light light = lightList.get(i); + Light.Type lightType = light.getType(); + ColorRGBA col = light.getColor(); + Vector3f pos; + + // Enable the light + gl.glEnable(glLightIndex); + + // OGL spec states default value for light ambient is black + switch (lightType){ + case Directional: + DirectionalLight dLight = (DirectionalLight) light; + + fb16.clear(); + fb16.put(col.r).put(col.g).put(col.b).put(col.a).flip(); + gl.getGL2ES1().glLightfv(glLightIndex, GLLightingFunc.GL_DIFFUSE, fb16); + gl.getGL2ES1().glLightfv(glLightIndex, GLLightingFunc.GL_SPECULAR, fb16); + + pos = tempVec.set(dLight.getDirection()).negateLocal().normalizeLocal(); + fb16.clear(); + fb16.put(pos.x).put(pos.y).put(pos.z).put(0.0f).flip(); + gl.getGL2ES1().glLightfv(glLightIndex, GLLightingFunc.GL_POSITION, fb16); + gl.getGL2ES1().glLightf(glLightIndex, GLLightingFunc.GL_SPOT_CUTOFF, 180); + break; + case Point: + PointLight pLight = (PointLight) light; + + fb16.clear(); + fb16.put(col.r).put(col.g).put(col.b).put(col.a).flip(); + gl.getGL2ES1().glLightfv(glLightIndex, GLLightingFunc.GL_DIFFUSE, fb16); + gl.getGL2ES1().glLightfv(glLightIndex, GLLightingFunc.GL_SPECULAR, fb16); + + pos = pLight.getPosition(); + fb16.clear(); + fb16.put(pos.x).put(pos.y).put(pos.z).put(1.0f).flip(); + gl.getGL2ES1().glLightfv(glLightIndex, GLLightingFunc.GL_POSITION, fb16); + gl.getGL2ES1().glLightf(glLightIndex, GLLightingFunc.GL_SPOT_CUTOFF, 180); + + if (pLight.getRadius() > 0) { + // Note: this doesn't follow the same attenuation model + // as the one used in the lighting shader. + gl.getGL2ES1().glLightf(glLightIndex, GLLightingFunc.GL_CONSTANT_ATTENUATION, 1); + gl.getGL2ES1().glLightf(glLightIndex, GLLightingFunc.GL_LINEAR_ATTENUATION, pLight.getInvRadius() * 2); + gl.getGL2ES1().glLightf(glLightIndex, GLLightingFunc.GL_QUADRATIC_ATTENUATION, pLight.getInvRadius() * pLight.getInvRadius()); + }else{ + gl.getGL2ES1().glLightf(glLightIndex, GLLightingFunc.GL_CONSTANT_ATTENUATION, 1); + gl.getGL2ES1().glLightf(glLightIndex, GLLightingFunc.GL_LINEAR_ATTENUATION, 0); + gl.getGL2ES1().glLightf(glLightIndex, GLLightingFunc.GL_QUADRATIC_ATTENUATION, 0); + } + + break; + case Spot: + SpotLight sLight = (SpotLight) light; + + fb16.clear(); + fb16.put(col.r).put(col.g).put(col.b).put(col.a).flip(); + gl.getGL2ES1().glLightfv(glLightIndex, GLLightingFunc.GL_DIFFUSE, fb16); + gl.getGL2ES1().glLightfv(glLightIndex, GLLightingFunc.GL_SPECULAR, fb16); + + pos = sLight.getPosition(); + fb16.clear(); + fb16.put(pos.x).put(pos.y).put(pos.z).put(1.0f).flip(); + gl.getGL2().glLightfv(glLightIndex, GLLightingFunc.GL_POSITION, fb16); + + Vector3f dir = sLight.getDirection(); + fb16.clear(); + fb16.put(dir.x).put(dir.y).put(dir.z).put(1.0f).flip(); + gl.getGL2ES1().glLightfv(glLightIndex, GLLightingFunc.GL_SPOT_DIRECTION, fb16); + + float outerAngleRad = sLight.getSpotOuterAngle(); + float innerAngleRad = sLight.getSpotInnerAngle(); + float spotCut = outerAngleRad * FastMath.RAD_TO_DEG; + float spotExpo = 0.0f; + if (outerAngleRad > 0) { + spotExpo = (1.0f - (innerAngleRad / outerAngleRad)) * 128.0f; + } + + gl.getGL2ES1().glLightf(glLightIndex, GLLightingFunc.GL_SPOT_CUTOFF, spotCut); + gl.getGL2ES1().glLightf(glLightIndex, GLLightingFunc.GL_SPOT_EXPONENT, spotExpo); + + if (sLight.getSpotRange() > 0) { + gl.getGL2ES1().glLightf(glLightIndex, GLLightingFunc.GL_LINEAR_ATTENUATION, sLight.getInvSpotRange()); + }else{ + gl.getGL2ES1().glLightf(glLightIndex, GLLightingFunc.GL_LINEAR_ATTENUATION, 0); + } + + break; + default: + throw new UnsupportedOperationException( + "Unrecognized light type: " + lightType); + } + } + + // Disable lights after the index + for (int i = lightList.size(); i < numLightsSetPrev; i++){ + gl.glDisable(GLLightingFunc.GL_LIGHT0 + i); + } + + // This will set view matrix as well. + setModelView(worldMatrix, viewMatrix); + } + + private int convertTextureType(Texture.Type type) { + switch (type) { + case TwoDimensional: + return GL.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.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 EdgeClamp: + case Clamp: + case BorderClamp: + return GL2.GL_CLAMP; + case Repeat: + return GL.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()); + GL gl = GLContext.getCurrentGL(); + 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: +// glTexParameteri(target, 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()); + } + } + + public void updateTexImageData(Image img, Texture.Type type, int unit) { + int texId = img.getId(); + GL gl = GLContext.getCurrentGL(); + if (texId == -1) { + // create texture + gl.glGenTextures(1, ib1); + texId = ib1.get(0); + img.setId(texId); + objManager.registerObject(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) { + gl.glEnable(target); + gl.glBindTexture(target, texId); + context.boundTextures[unit] = img; + + statistics.onTextureUse(img, true); + } + + // Check sizes if graphics card doesn't support NPOT + if (!gl.isExtensionAvailable("GL_ARB_texture_non_power_of_two")) { + if (img.getWidth() != 0 && img.getHeight() != 0) { + if (!FastMath.isPowerOfTwo(img.getWidth()) + || !FastMath.isPowerOfTwo(img.getHeight())) { + + // Resize texture to Power-of-2 size + MipMapGenerator.resizeToPowerOf2(img); + } + } + } + + if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) { + // No pregenerated mips available, + // generate from base level if required + + // Check if hardware mips are supported + if (gl.isExtensionAvailable("GL_VERSION_1_4")) { + gl.glTexParameteri(target, GL2ES1.GL_GENERATE_MIPMAP, GL.GL_TRUE); + } else { + MipMapGenerator.generateMipMaps(img); + } + img.setMipmapsGenerated(true); + } else { + } + + if (img.getWidth() > maxTexSize || img.getHeight() > maxTexSize) { + throw new RendererException("Cannot upload texture " + img + ". The maximum supported texture resolution is " + maxTexSize); + } + + /* + 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); + //} + + 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() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated()) ) { + updateTexImageData(image, tex.getType(), 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){ +// gl.glActiveTexture(GL.GL_TEXTURE0 + unit); +// context.boundTextureUnit = unit; +// } +// gl.glEnable(type); +// } + +// if (context.boundTextureUnit != unit) { +// gl.glActiveTexture(GL.GL_TEXTURE0 + unit); +// context.boundTextureUnit = unit; +// } + + if (textures[unit] != image) { + GL gl = GLContext.getCurrentGL(); + gl.glEnable(type); + gl.glBindTexture(type, texId); + textures[unit] = image; + + statistics.onTextureUse(image, true); + } else { + statistics.onTextureUse(image, false); + } + + setupTextureParams(tex); + } + + public void modifyTexture(Texture tex, Image pixels, int x, int y) { + setTexture(0, tex); + TextureUtil.uploadSubTexture(pixels, convertTextureType(tex.getType()), 0, x, y); + } + + private void clearTextureUnits() { + Image[] textures = context.boundTextures; + if (textures[0] != null) { + GL gl = GLContext.getCurrentGL(); + gl.glDisable(GL.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); + GL gl = GLContext.getCurrentGL(); + gl.glDeleteTextures(ib1.limit() ,ib1); + image.resetObject(); + } + } + + private 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 + } + } + + private int convertVertexFormat(VertexBuffer.Format fmt) { + switch (fmt) { + case Byte: + return GL.GL_BYTE; + case Float: + return GL.GL_FLOAT; + case Int: + return GL2ES2.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); + } + } + + public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) { + if (count > 1) { + throw new UnsupportedOperationException(); + } + GL gl = GLContext.getCurrentGL(); + gl.glDrawArrays(convertElementMode(mode), 0, vertCount); + } + + public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { + if (vb.getBufferType() == VertexBuffer.Type.Color && !context.useVertexColor) { + // Ignore vertex color buffer if vertex color is disabled. + return; + } + + int arrayType = convertArrayType(vb.getBufferType()); + if (arrayType == -1) { + return; // unsupported + } + GL gl = GLContext.getCurrentGL(); + gl.getGL2GL3().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.rewind(); + + switch (vb.getBufferType()) { + case Position: + if (!(data instanceof FloatBuffer)) { + throw new UnsupportedOperationException(); + } + + gl.getGL2().glVertexPointer(comps, type, vb.getStride(), (FloatBuffer) data); + break; + case Normal: + if (!(data instanceof FloatBuffer)) { + throw new UnsupportedOperationException(); + } + + gl.getGL2().glNormalPointer(type, vb.getStride(), (FloatBuffer) data); + break; + case Color: + if (data instanceof FloatBuffer) { + gl.getGL2().glColorPointer(comps, type, vb.getStride(), (FloatBuffer) data); + } else if (data instanceof ByteBuffer) { + gl.getGL2().glColorPointer(comps, type, vb.getStride(), (ByteBuffer) data); + } else { + throw new UnsupportedOperationException(); + } + break; + case TexCoord: + if (!(data instanceof FloatBuffer)) { + throw new UnsupportedOperationException(); + } + + gl.getGL2().glTexCoordPointer(comps, type, vb.getStride(), (FloatBuffer) data); + break; + default: + // Ignore, this is an unsupported attribute for OpenGL1. + break; + } + } + + public void setVertexAttrib(VertexBuffer vb) { + setVertexAttrib(vb, null); + } + + private void drawElements(int mode, int format, Buffer data) { + GL gl = GLContext.getCurrentGL(); + switch (format) { + case GL.GL_UNSIGNED_BYTE: + gl.getGL2().glDrawElements(mode, data.limit(), format, (ByteBuffer) data); + break; + case GL.GL_UNSIGNED_SHORT: + gl.getGL2().glDrawElements(mode, data.limit(), format, (ShortBuffer) data); + break; + case GL.GL_UNSIGNED_INT: + gl.getGL2().glDrawElements(mode, data.limit(), format, (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()); + GL gl = GLContext.getCurrentGL(); + gl.getGL2().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); + } + + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = mesh.getBuffer(Type.Index); + } + for (VertexBuffer vb : mesh.getBufferList().getArray()) { + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } + + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + + if (indices != null) { + drawTriangleList(indices, mesh, count); + } else { + GL gl = GLContext.getCurrentGL(); + gl.glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + } + + // TODO: Fix these to use IDList?? + clearVertexAttribs(); + clearTextureUnits(); + resetFixedFuncBindings(); + } + + public void renderMesh(Mesh mesh, int lod, int count) { + if (mesh.getVertexCount() == 0) { + return; + } + GL gl = GLContext.getCurrentGL(); + if (context.pointSize != mesh.getPointSize()) { + gl.getGL2().glPointSize(mesh.getPointSize()); + context.pointSize = mesh.getPointSize(); + } + if (context.lineWidth != mesh.getLineWidth()) { + gl.getGL2().glLineWidth(mesh.getLineWidth()); + context.lineWidth = mesh.getLineWidth(); + } + + boolean dynamic = false; + if (mesh.getBuffer(Type.InterleavedData) != null) { + throw new UnsupportedOperationException("Interleaved meshes are not supported"); + } + + if (mesh.getNumLodLevels() == 0) { + for (VertexBuffer vb : mesh.getBufferList().getArray()) { + if (vb.getUsage() != VertexBuffer.Usage.Static) { + dynamic = true; + break; + } + } + } else { + dynamic = true; + } + + statistics.onMeshDrawn(mesh, lod); + +// if (!dynamic) { + // dealing with a static object, generate display list +// renderMeshDisplayList(mesh); +// } else { + renderMeshDefault(mesh, lod, count); +// } + + + } + + public void setAlphaToCoverage(boolean value) { + } + + public void setShader(Shader shader) { + } + + public void deleteShader(Shader shader) { + } + + public void deleteShaderSource(ShaderSource source) { + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { + } + + public void setMainFrameBufferOverride(FrameBuffer fb){ + } + + public void setFrameBuffer(FrameBuffer fb) { + } + + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { + } + + public void deleteFrameBuffer(FrameBuffer fb) { + } + + public void updateBufferData(VertexBuffer vb) { + } + + public void deleteBuffer(VertexBuffer vb) { + } +} diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java new file mode 100644 index 000000000..c512323f5 --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java @@ -0,0 +1,2588 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.jogl; + +import com.jme3.light.LightList; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +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.IDList; +import com.jme3.renderer.RenderContext; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.RendererException; +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.Attribute; +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.FrameBuffer.RenderBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapAxis; +import com.jme3.util.BufferUtils; +import com.jme3.util.ListMap; +import com.jme3.util.NativeObjectManager; +import com.jme3.util.SafeArrayList; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.media.nativewindow.NativeWindowFactory; +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.GL3; +import javax.media.opengl.GLContext; +import jme3tools.converters.MipMapGenerator; +import jme3tools.shader.ShaderDebug; + +public class JoglRenderer implements Renderer { + + private static final Logger logger = Logger.getLogger(JoglRenderer.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); + protected FloatBuffer fb16 = BufferUtils.createFloatBuffer(16); + private RenderContext context = new RenderContext(); + private NativeObjectManager objManager = new NativeObjectManager(); + private 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 FrameBuffer lastFb = null; + private FrameBuffer mainFbOverride = null; + private final Statistics statistics = new Statistics(); + private int vpX, vpY, vpW, vpH; + private int clipX, clipY, clipW, clipH; + + public JoglRenderer() { + } + + 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() { + GL gl = GLContext.getCurrentGL(); + //logger.log(Level.FINE, "Vendor: {0}", gl.glGetString(GL.GL_VENDOR)); + //logger.log(Level.FINE, "Renderer: {0}", gl.glGetString(GL.GL_RENDERER)); + //logger.log(Level.FINE, "Version: {0}", gl.glGetString(GL.GL_VERSION)); + if (gl.isExtensionAvailable("GL_VERSION_2_0")) { + caps.add(Caps.OpenGL20); + if (gl.isExtensionAvailable("GL_VERSION_2_1")) { + caps.add(Caps.OpenGL21); + if (gl.isExtensionAvailable("GL_VERSION_3_0")) { + caps.add(Caps.OpenGL30); + if (gl.isExtensionAvailable("GL_VERSION_3_1")) { + caps.add(Caps.OpenGL31); + if (gl.isExtensionAvailable("GL_VERSION_3_2")) { + caps.add(Caps.OpenGL32); + } + } + } + } + } + + String versionStr = null; + if (caps.contains(Caps.OpenGL20) || gl.isGL2ES2()) { + versionStr = gl.glGetString(GL2ES2.GL_SHADING_LANGUAGE_VERSION); + } + if (versionStr == null || versionStr.equals("")) { + glslVer = -1; + throw new UnsupportedOperationException("GLSL and OpenGL2 is " + + "required for the JOGL " + + "renderer!"); + } + + // Fix issue in TestRenderToMemory when GL_FRONT is the main + // buffer being used. + gl.glGetIntegerv(GL2GL3.GL_DRAW_BUFFER, intBuf1); + initialDrawBuf = intBuf1.get(0); + gl.glGetIntegerv(GL2GL3.GL_READ_BUFFER, intBuf1); + initialReadBuf = intBuf1.get(0); + + // XXX: This has to be GL_BACK for canvas on Mac + // Since initialDrawBuf is GL_FRONT for pbuffer, gotta + // change this value later on ... +// initialDrawBuf = GL_BACK; +// initialReadBuf = GL_BACK; + + int spaceIdx = versionStr.indexOf(" "); + if (spaceIdx >= 1) { + versionStr = versionStr.substring(0, spaceIdx); + } + + try { + float version = Float.parseFloat(versionStr); + glslVer = (int) (version * 100); + } catch (NumberFormatException e) { + // the parsing fails on Raspberry Pi + if (NativeWindowFactory.getNativeWindowType(false).equals(NativeWindowFactory.TYPE_BCM_VC_IV)) { + logger.warning("Failed parsing GLSL version assuming it's v1.00"); + glslVer = 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); + } + + 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 (caps.contains(Caps.OpenGL30)) { + caps.add(Caps.PackedDepthStencilBuffer); + } + + 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); + } + + if (gl.isExtensionAvailable("GL_ARB_texture_non_power_of_two")) { + caps.add(Caps.NonPowerOfTwoTextures); + } + else { + logger.log(Level.WARNING, "Your graphics card does not " + + "support non-power-of-2 textures. " + + "Some features might not work."); + } + + boolean latc = gl.isExtensionAvailable("GL_EXT_texture_compression_latc"); + //FIXME ignore atdc? + boolean atdc = gl.isExtensionAvailable("GL_ATI_texture_compression_3dc"); + if (latc || atdc) { + caps.add(Caps.TextureCompressionLATC); + } + + 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_texture_multisample")) { + caps.add(Caps.TextureMultisample); + + gl.glGetIntegerv(GL3.GL_MAX_COLOR_TEXTURE_SAMPLES, intBuf16); + maxColorTexSamples = intBuf16.get(0); + logger.log(Level.FINER, "Texture Multisample Color Samples: {0}", maxColorTexSamples); + + gl.glGetIntegerv(GL3.GL_MAX_DEPTH_TEXTURE_SAMPLES, intBuf16); + maxDepthTexSamples = intBuf16.get(0); + logger.log(Level.FINER, "Texture Multisample Depth Samples: {0}", maxDepthTexSamples); + } + + gl.glGetIntegerv(GL2ES2.GL_MAX_DRAW_BUFFERS, intBuf16); + maxMRTFBOAttachs = intBuf16.get(0); + if (maxMRTFBOAttachs > 1) { + caps.add(Caps.FrameBufferMRT); + logger.log(Level.FINER, "FBO Max MRT renderbuffers: {0}", maxMRTFBOAttachs); + } + + //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); + } + caps.add(Caps.Multisample); + } + + logger.log(Level.FINE, "Caps: {0}", caps); + } + + public void invalidateState() { + context.reset(); + boundShader = null; + lastFb = null; + + GL gl = GLContext.getCurrentGL(); + gl.glGetIntegerv(GL2GL3.GL_DRAW_BUFFER, intBuf1); + initialDrawBuf = intBuf1.get(0); + gl.glGetIntegerv(GL2GL3.GL_READ_BUFFER, intBuf1); + initialReadBuf = intBuf1.get(0); + } + + public void resetGLObjects() { + logger.log(Level.FINE, "Reseting objects and invalidating state"); + objManager.resetObjects(); + statistics.clearMemory(); + invalidateState(); + } + + public void cleanup() { + logger.log(Level.FINE, "Deleting objects and invalidating state"); + objManager.deleteAllObjects(this); + statistics.clearMemory(); + invalidateState(); + } + + private void checkCap(Caps cap) { + if (!caps.contains(cap)) { + throw new UnsupportedOperationException("Required capability missing: " + cap.name()); + } + } + + /*********************************************************************\ + |* Render State *| + \*********************************************************************/ + public void setDepthRange(float start, float end) { + GL gl = GLContext.getCurrentGL(); + gl.glDepthRange(start, end); + } + + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + GL gl = GLContext.getCurrentGL(); + int bits = 0; + if (color) { + //See explanations of the depth below, we must enable color write to be able to clear the color buffer + if (context.colorWriteEnabled == false) { + gl.glColorMask(true, true, true, true); + context.colorWriteEnabled = true; + } + bits = GL.GL_COLOR_BUFFER_BIT; + } + if (depth) { + + //glClear(GL_DEPTH_BUFFER_BIT) seems to not work when glDepthMask is false + //here s some link on openl board + //http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=257223 + //if depth clear is requested, we enable the depthMask + if (context.depthWriteEnabled == false) { + gl.glDepthMask(true); + context.depthWriteEnabled = true; + } + bits |= GL.GL_DEPTH_BUFFER_BIT; + } + if (stencil) { + bits |= GL.GL_STENCIL_BUFFER_BIT; + } + if (bits != 0) { + gl.glClear(bits); + } + } + + public void setBackgroundColor(ColorRGBA color) { + GL gl = GLContext.getCurrentGL(); + gl.glClearColor(color.r, color.g, color.b, color.a); + } + + public void setAlphaToCoverage(boolean value) { + if (caps.contains(Caps.Multisample)) { + GL gl = GLContext.getCurrentGL(); + if (value) { + gl.glEnable(GL.GL_SAMPLE_ALPHA_TO_COVERAGE); + } else { + gl.glDisable(GL.GL_SAMPLE_ALPHA_TO_COVERAGE); + } + } + } + + public void applyRenderState(RenderState state) { + GL gl = GLContext.getCurrentGL(); + if (state.isWireframe() && !context.wireframe) { + if (gl.isGL2GL3()) { + gl.getGL2GL3().glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_LINE); + } + context.wireframe = true; + } else if (!state.isWireframe() && context.wireframe) { + if (gl.isGL2GL3()) { + gl.getGL2GL3().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(convertTestFunction(context.depthFunc)); + context.depthTestEnabled = true; + } else if (!state.isDepthTest() && context.depthTestEnabled) { + gl.glDisable(GL.GL_DEPTH_TEST); + context.depthTestEnabled = false; + } + if (state.getDepthFunc() != context.depthFunc) { + gl.glDepthFunc(convertTestFunction(state.getDepthFunc())); + context.depthFunc = state.getDepthFunc(); + } + + if (state.isAlphaTest() && context.alphaTestFallOff == 0) { + gl.glEnable(GL2ES1.GL_ALPHA_TEST); + if (gl.isGL2ES1()) { + gl.getGL2ES1().glAlphaFunc(convertTestFunction(context.alphaFunc), state.getAlphaFallOff()); + } + context.alphaTestFallOff = state.getAlphaFallOff(); + } else if (!state.isAlphaTest() && context.alphaTestFallOff != 0) { + if (gl.isGL2ES1()) { + gl.glDisable(GL2ES1.GL_ALPHA_TEST); + } + context.alphaTestFallOff = 0; + } + if (state.getAlphaFunc() != context.alphaFunc && gl.isGL2ES1()) { + gl.getGL2ES1().glAlphaFunc(convertTestFunction(context.alphaFunc), state.getAlphaFallOff()); + context.alphaFunc = state.getAlphaFunc(); + } + + 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) { + // Only enable/disable sprite + if (context.boundTextures[0] != null) { + if (context.boundTextureUnit != 0) { + gl.glActiveTexture(GL.GL_TEXTURE0); + context.boundTextureUnit = 0; + } + if (gl.isGL2ES1()) { + gl.glEnable(GL2ES1.GL_POINT_SPRITE); + } + if (gl.isGL2GL3()) { + gl.glEnable(GL2GL3.GL_VERTEX_PROGRAM_POINT_SIZE); + } + } + context.pointSprite = true; + } else if (!state.isPointSprite() && context.pointSprite) { + if (context.boundTextures[0] != null) { + if (context.boundTextureUnit != 0) { + gl.glActiveTexture(GL.GL_TEXTURE0); + context.boundTextureUnit = 0; + } + if (gl.isGL2ES1()) { + gl.glDisable(GL2ES1.GL_POINT_SPRITE); + } + if (gl.isGL2GL3()) { + gl.glDisable(GL2GL3.GL_VERTEX_PROGRAM_POINT_SIZE); + } + context.pointSprite = 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 AlphaAdditive: + gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE); + break; + case Color: + gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR); + 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(); + } + + if (context.stencilTest != state.isStencilTest() + || context.frontStencilStencilFailOperation != state.getFrontStencilStencilFailOperation() + || context.frontStencilDepthFailOperation != state.getFrontStencilDepthFailOperation() + || context.frontStencilDepthPassOperation != state.getFrontStencilDepthPassOperation() + || context.backStencilStencilFailOperation != state.getBackStencilStencilFailOperation() + || context.backStencilDepthFailOperation != state.getBackStencilDepthFailOperation() + || context.backStencilDepthPassOperation != state.getBackStencilDepthPassOperation() + || context.frontStencilFunction != state.getFrontStencilFunction() + || context.backStencilFunction != state.getBackStencilFunction()) { + + context.frontStencilStencilFailOperation = state.getFrontStencilStencilFailOperation(); //terrible looking, I know + context.frontStencilDepthFailOperation = state.getFrontStencilDepthFailOperation(); + context.frontStencilDepthPassOperation = state.getFrontStencilDepthPassOperation(); + context.backStencilStencilFailOperation = state.getBackStencilStencilFailOperation(); + context.backStencilDepthFailOperation = state.getBackStencilDepthFailOperation(); + context.backStencilDepthPassOperation = state.getBackStencilDepthPassOperation(); + context.frontStencilFunction = state.getFrontStencilFunction(); + context.backStencilFunction = state.getBackStencilFunction(); + + if (state.isStencilTest()) { + gl.glEnable(GL.GL_STENCIL_TEST); + gl.getGL2ES2().glStencilOpSeparate(GL.GL_FRONT, + convertStencilOperation(state.getFrontStencilStencilFailOperation()), + convertStencilOperation(state.getFrontStencilDepthFailOperation()), + convertStencilOperation(state.getFrontStencilDepthPassOperation())); + gl.getGL2ES2().glStencilOpSeparate(GL.GL_BACK, + convertStencilOperation(state.getBackStencilStencilFailOperation()), + convertStencilOperation(state.getBackStencilDepthFailOperation()), + convertStencilOperation(state.getBackStencilDepthPassOperation())); + gl.getGL2ES2().glStencilFuncSeparate(GL.GL_FRONT, + convertTestFunction(state.getFrontStencilFunction()), + 0, Integer.MAX_VALUE); + gl.getGL2ES2().glStencilFuncSeparate(GL.GL_BACK, + convertTestFunction(state.getBackStencilFunction()), + 0, Integer.MAX_VALUE); + } else { + gl.glDisable(GL.GL_STENCIL_TEST); + } + } + } + + private int convertStencilOperation(RenderState.StencilOperation stencilOp) { + switch (stencilOp) { + case Keep: + return GL.GL_KEEP; + case Zero: + return GL.GL_ZERO; + case Replace: + return GL.GL_REPLACE; + case Increment: + return GL.GL_INCR; + case IncrementWrap: + return GL.GL_INCR_WRAP; + case Decrement: + return GL.GL_DECR; + case DecrementWrap: + return GL.GL_DECR_WRAP; + case Invert: + return GL.GL_INVERT; + default: + throw new UnsupportedOperationException("Unrecognized stencil operation: " + stencilOp); + } + } + + private int convertTestFunction(RenderState.TestFunction testFunc) { + switch (testFunc) { + case Never: + return GL.GL_NEVER; + case Less: + return GL.GL_LESS; + case LessOrEqual: + return GL.GL_LEQUAL; + case Greater: + return GL.GL_GREATER; + case GreaterOrEqual: + return GL.GL_GEQUAL; + case Equal: + return GL.GL_EQUAL; + case NotEqual: + return GL.GL_NOTEQUAL; + case Always: + return GL.GL_ALWAYS; + default: + throw new UnsupportedOperationException("Unrecognized test function: " + testFunc); + } + } + + /*********************************************************************\ + |* Camera and World transforms *| + \*********************************************************************/ + public void setViewPort(int x, int y, int w, int h) { + if (x != vpX || vpY != y || vpW != w || vpH != h) { + GL gl = GLContext.getCurrentGL(); + gl.glViewport(x, y, w, h); + vpX = x; + vpY = y; + vpW = w; + vpH = h; + } + } + + public void setClipRect(int x, int y, int width, int height) { + GL gl = GLContext.getCurrentGL(); + if (!context.clipRectEnabled) { + gl.glEnable(GL.GL_SCISSOR_TEST); + context.clipRectEnabled = true; + } + if (clipX != x || clipY != y || clipW != width || clipH != height) { + gl.glScissor(x, y, width, height); + clipX = x; + clipY = y; + clipW = width; + clipH = height; + } + } + + public void clearClipRect() { + if (context.clipRectEnabled) { + GL gl = GLContext.getCurrentGL(); + gl.glDisable(GL.GL_SCISSOR_TEST); + context.clipRectEnabled = false; + + clipX = 0; + clipY = 0; + clipW = 0; + clipH = 0; + } + } + + public void onFrame() { + objManager.deleteUnused(this); + } + + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { + } + + public void setWorldMatrix(Matrix4f worldMatrix) { + } + + /*********************************************************************\ + |* Shaders *| + \*********************************************************************/ + protected void updateUniformLocation(Shader shader, Uniform uniform) { + GL gl = GLContext.getCurrentGL(); + // passing a null terminated string is not necessary with JOGL 2.0 + int loc = gl.getGL2ES2().glGetUniformLocation(shader.getId(), uniform.getName()); + if (loc < 0) { + uniform.setLocation(-1); + // uniform is not declared in shader + logger.log(Level.FINE, "Uniform {0} is not declared in shader {1}.", new Object[]{uniform.getName(), shader.getSources()}); + + } else { + uniform.setLocation(loc); + } + } + + protected void bindProgram(Shader shader) { + int shaderId = shader.getId(); + if (context.boundShaderProgram != shaderId) { + GL gl = GLContext.getCurrentGL(); + gl.getGL2ES2().glUseProgram(shaderId); + statistics.onShaderUse(shader, true); + boundShader = shader; + context.boundShaderProgram = shaderId; + } else { + statistics.onShaderUse(shader, false); + } + } + + protected void updateUniform(Shader shader, Uniform uniform) { + int shaderId = shader.getId(); + + assert uniform.getName() != null; + assert shaderId > 0; + + bindProgram(shader); + + int loc = uniform.getLocation(); + if (loc == -1) { + return; + } + + if (loc == -2) { + // get uniform location + updateUniformLocation(shader, uniform); + if (uniform.getLocation() == -1) { + // not declared, ignore + uniform.clearUpdateNeeded(); + return; + } + loc = uniform.getLocation(); + } + + if (uniform.getVarType() == null) { + return; // value not set yet.. + } + statistics.onUniformSet(); + + uniform.clearUpdateNeeded(); + FloatBuffer fb; + IntBuffer ib; + GL gl = GLContext.getCurrentGL(); + switch (uniform.getVarType()) { + case Float: + Float f = (Float) uniform.getValue(); + gl.getGL2ES2().glUniform1f(loc, f.floatValue()); + break; + case Vector2: + Vector2f v2 = (Vector2f) uniform.getValue(); + gl.getGL2ES2().glUniform2f(loc, v2.getX(), v2.getY()); + break; + case Vector3: + Vector3f v3 = (Vector3f) uniform.getValue(); + gl.getGL2ES2().glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ()); + break; + case Vector4: + Object val = uniform.getValue(); + if (val instanceof ColorRGBA) { + ColorRGBA c = (ColorRGBA) val; + gl.getGL2ES2().glUniform4f(loc, c.r, c.g, c.b, c.a); + } else if (val instanceof Vector4f) { + Vector4f c = (Vector4f) val; + gl.getGL2ES2().glUniform4f(loc, c.x, c.y, c.z, c.w); + } else { + Quaternion c = (Quaternion) uniform.getValue(); + gl.getGL2ES2().glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW()); + } + break; + case Boolean: + Boolean b = (Boolean) uniform.getValue(); + gl.getGL2ES2().glUniform1i(loc, b.booleanValue() ? GL.GL_TRUE : GL.GL_FALSE); + break; + case Matrix3: + fb = (FloatBuffer) uniform.getValue(); + assert fb.remaining() == 9; + gl.getGL2ES2().glUniformMatrix3fv(loc, 1, false, fb); + break; + case Matrix4: + fb = (FloatBuffer) uniform.getValue(); + assert fb.remaining() == 16; + gl.getGL2ES2().glUniformMatrix4fv(loc, 1, false, fb); + break; + case IntArray: + ib = (IntBuffer) uniform.getValue(); + gl.getGL2ES2().glUniform1iv(loc, ib.remaining(), ib); + break; + case FloatArray: + fb = (FloatBuffer) uniform.getValue(); + gl.getGL2ES2().glUniform1fv(loc, fb.remaining(), fb); + break; + case Vector2Array: + fb = (FloatBuffer) uniform.getValue(); + gl.getGL2ES2().glUniform2fv(loc, fb.remaining(), fb); + break; + case Vector3Array: + fb = (FloatBuffer) uniform.getValue(); + gl.getGL2ES2().glUniform3fv(loc, fb.remaining(), fb); + break; + case Vector4Array: + fb = (FloatBuffer) uniform.getValue(); + gl.getGL2ES2().glUniform4fv(loc, fb.remaining(), fb); + break; + case Matrix4Array: + fb = (FloatBuffer) uniform.getValue(); + gl.getGL2ES2().glUniformMatrix4fv(loc, 1, false, fb); + break; + case Int: + Integer i = (Integer) uniform.getValue(); + gl.getGL2ES2().glUniform1i(loc, i.intValue()); + break; + default: + throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType()); + } + } + + protected void updateShaderUniforms(Shader shader) { + ListMap uniforms = shader.getUniformMap(); + 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 (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(Shader.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 UnsupportedOperationException("Unrecognized shader type."); + } + } + + public void updateShaderSourceData(ShaderSource source) { + int id = source.getId(); + GL gl = GLContext.getCurrentGL(); + if (id == -1) { + // Create id + id = gl.getGL2ES2().glCreateShader(convertShaderType(source.getType())); + if (id <= 0) { + throw new RendererException("Invalid ID received when trying to create shader."); + } + + source.setId(id); + } else { + throw new RendererException("Cannot recompile shader source"); + } + + // Upload shader source. + // Merge the defines and source code. + String language = source.getLanguage(); + 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(); + + byte[] array = new byte[codeBuf.limit()]; + codeBuf.rewind(); + codeBuf.get(array); + codeBuf.rewind(); + + gl.getGL2ES2().glShaderSource(id, 1, new String[]{new String(array)}, new int[]{array.length}, 0); + gl.getGL2ES2().glCompileShader(id); + + gl.getGL2ES2().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.getGL2ES2().glGetShaderiv(id, GL2ES2.GL_INFO_LOG_LENGTH, intBuf1); + int length = intBuf1.get(0); + if (length > 3) { + // get infos + ByteBuffer logBuf = BufferUtils.createByteBuffer(length); + gl.getGL2ES2().glGetShaderInfoLog(id, length, 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.FINE, "{0} compile success\n{1}", + new Object[]{source.getName(), infoLog}); + } else { + logger.log(Level.FINE, "{0} compile success", source.getName()); + } + source.clearUpdateNeeded(); + } else { + logger.log(Level.WARNING, "Bad compile of:\n{0}", + new Object[]{ShaderDebug.formatShaderSource(source.getDefines(), source.getSource(), stringBuf.toString())}); + if (infoLog != null) { + throw new RendererException("compile error in:" + source + " error:" + infoLog); + } else { + throw new RendererException("compile error in:" + source + " error: "); + } + } + } + + public void updateShaderData(Shader shader) { + GL gl = GLContext.getCurrentGL(); + int id = shader.getId(); + boolean needRegister = false; + if (id == -1) { + // create program + id = gl.getGL2ES2().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); + } + gl.getGL2ES2().glAttachShader(id, source.getId()); + } + + if (caps.contains(Caps.OpenGL30) && gl.isGL2GL3()) { + // Check if GLSL version is 1.5 for shader + gl.getGL2GL3().glBindFragDataLocation(id, 0, "outFragColor"); + // For MRT + for (int i = 0; i < maxMRTFBOAttachs; i++) { + gl.getGL2GL3().glBindFragDataLocation(id, i, "outFragData[" + i + "]"); + } + } + + // Link shaders to program + gl.getGL2ES2().glLinkProgram(id); + + // Check link status + gl.getGL2ES2().glGetProgramiv(id, GL2ES2.GL_LINK_STATUS, intBuf1); + boolean linkOK = intBuf1.get(0) == GL.GL_TRUE; + String infoLog = null; + + if (VALIDATE_SHADER || !linkOK) { + gl.getGL2ES2().glGetProgramiv(id, GL2ES2.GL_INFO_LOG_LENGTH, intBuf1); + int length = intBuf1.get(0); + if (length > 3) { + // get infos + ByteBuffer logBuf = BufferUtils.createByteBuffer(length); + gl.getGL2ES2().glGetProgramInfoLog(id, length, 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.FINE, "shader link success. \n{0}", infoLog); + } else { + logger.fine("shader link success"); + } + shader.clearUpdateNeeded(); + if (needRegister) { + // Register shader for clean up if it was created in this method. + objManager.registerObject(shader); + statistics.onNewShader(); + } else { + // OpenGL spec: uniform locations may change after re-link + resetUniformLocations(shader); + } + } else { + if (infoLog != null) { + throw new RendererException("Shader link failure, shader:" + shader + " info:" + infoLog); + } else { + throw new RendererException("Shader link failure, shader:" + shader + " info: "); + } + } + } + + public void setShader(Shader shader) { + if (shader == null) { + throw new IllegalArgumentException("Shader cannot be null"); + } else { + if (shader.isUpdateNeeded()) { + updateShaderData(shader); + } + + // NOTE: might want to check if any of the + // sources need an update? + + assert shader.getId() > 0; + + updateShaderUniforms(shader); + bindProgram(shader); + } + } + + public void deleteShaderSource(ShaderSource source) { + if (source.getId() < 0) { + logger.warning("Shader source is not uploaded to GPU, cannot delete."); + return; + } + source.clearUpdateNeeded(); + GL gl = GLContext.getCurrentGL(); + gl.getGL2ES2().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; + } + + GL gl = GLContext.getCurrentGL(); + for (ShaderSource source : shader.getSources()) { + if (source.getId() != -1) { + gl.getGL2ES2().glDetachShader(shader.getId(), source.getId()); + deleteShaderSource(source); + } + } + + gl.getGL2ES2().glDeleteProgram(shader.getId()); + statistics.onDeleteShader(); + shader.resetObject(); + } + + /*********************************************************************\ + |* Framebuffers *| + \*********************************************************************/ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { + copyFrameBuffer(src, dst, true); + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { + GL gl = GLContext.getCurrentGL(); + if (gl.isExtensionAvailable("GL_EXT_framebuffer_blit") && gl.isGL2GL3()) { + int srcX0 = 0; + int srcY0 = 0; + int srcX1/* = 0*/; + int srcY1/* = 0*/; + + int dstX0 = 0; + int dstY0 = 0; + int dstX1/* = 0*/; + int dstY1/* = 0*/; + + int prevFBO = context.boundFBO; + + if (mainFbOverride != null) { + if (src == null) { + src = mainFbOverride; + } + if (dst == null) { + dst = mainFbOverride; + } + } + + if (src != null && src.isUpdateNeeded()) { + updateFrameBuffer(src); + } + + if (dst != null && dst.isUpdateNeeded()) { + updateFrameBuffer(dst); + } + + if (src == null) { + gl.glBindFramebuffer(GL2GL3.GL_READ_FRAMEBUFFER, 0); + srcX0 = vpX; + srcY0 = vpY; + srcX1 = vpX + vpW; + srcY1 = vpY + vpH; + } else { + gl.glBindFramebuffer(GL2GL3.GL_READ_FRAMEBUFFER, src.getId()); + srcX1 = src.getWidth(); + srcY1 = src.getHeight(); + } + if (dst == null) { + gl.glBindFramebuffer(GL2GL3.GL_DRAW_FRAMEBUFFER, 0); + dstX0 = vpX; + dstY0 = vpY; + dstX1 = vpX + vpW; + dstY1 = vpY + vpH; + } else { + gl.glBindFramebuffer(GL2GL3.GL_DRAW_FRAMEBUFFER, dst.getId()); + dstX1 = dst.getWidth(); + dstY1 = dst.getHeight(); + } + int mask = GL.GL_COLOR_BUFFER_BIT; + if (copyDepth) { + mask |= GL.GL_DEPTH_BUFFER_BIT; + } + gl.getGL2GL3().glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, + dstX0, dstY0, dstX1, dstY1, mask, + GL.GL_NEAREST); + gl.glBindFramebuffer(GL2GL3.GL_FRAMEBUFFER, prevFBO); + + + try { + checkFrameBufferError(); + } catch (IllegalStateException ex) { + logger.log(Level.SEVERE, "Source FBO:\n{0}", src); + logger.log(Level.SEVERE, "Dest FBO:\n{0}", dst); + throw ex; + } + } else { + throw new RendererException("EXT_framebuffer_blit required."); + // TODO: support non-blit copies? + } + } + + private String getTargetBufferName(int buffer) { + switch (buffer) { + case GL.GL_NONE: + return "NONE"; + case GL.GL_FRONT: + return "GL_FRONT"; + case GL.GL_BACK: + return "GL_BACK"; + default: + if (buffer >= GL.GL_COLOR_ATTACHMENT0 + && buffer <= GL2ES2.GL_COLOR_ATTACHMENT15) { + return "GL_COLOR_ATTACHMENT" + + (buffer - GL.GL_COLOR_ATTACHMENT0); + } else { + return "UNKNOWN? " + buffer; + } + } + } + + private void printRealRenderBufferInfo(FrameBuffer fb, RenderBuffer rb, String name) { + GL gl = GLContext.getCurrentGL(); + System.out.println("== Renderbuffer " + name + " =="); + System.out.println("RB ID: " + rb.getId()); + System.out.println("Is proper? " + gl.glIsRenderbuffer(rb.getId())); + + int attachment = convertAttachmentSlot(rb.getSlot()); + + gl.glGetFramebufferAttachmentParameteriv(GL2GL3.GL_DRAW_FRAMEBUFFER, + attachment, + GL.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, intBuf16); + int type = intBuf16.get(0); + gl.glGetFramebufferAttachmentParameteriv(GL2GL3.GL_DRAW_FRAMEBUFFER, + attachment, + GL.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, intBuf16); + int rbName = intBuf16.get(0); + + switch (type) { + case GL.GL_NONE: + System.out.println("Type: None"); + break; + case GL.GL_TEXTURE: + System.out.println("Type: Texture"); + break; + case GL.GL_RENDERBUFFER: + System.out.println("Type: Buffer"); + System.out.println("RB ID: " + rbName); + break; + } + + + + } + + private void printRealFrameBufferInfo(FrameBuffer fb) { + GL gl = GLContext.getCurrentGL(); + final byte[] param = new byte[1]; + gl.glGetBooleanv(GL2GL3.GL_DOUBLEBUFFER, param, 0); + boolean doubleBuffer = param[0] != (byte) 0x00; + gl.glGetIntegerv(GL2GL3.GL_DRAW_BUFFER, intBuf16); + String drawBuf = getTargetBufferName(intBuf16.get(0)); + gl.glGetIntegerv(GL2GL3.GL_READ_BUFFER, intBuf16); + String readBuf = getTargetBufferName(intBuf16.get(0)); + + int fbId = fb.getId(); + gl.glGetIntegerv(GL2GL3.GL_DRAW_FRAMEBUFFER_BINDING, intBuf16); + int curDrawBinding = intBuf16.get(0); + gl.glGetIntegerv(GL2GL3.GL_READ_FRAMEBUFFER_BINDING, intBuf16); + int curReadBinding = intBuf16.get(0); + + System.out.println("=== OpenGL FBO State ==="); + System.out.println("Context doublebuffered? " + doubleBuffer); + System.out.println("FBO ID: " + fbId); + System.out.println("Is proper? " + gl.glIsFramebuffer(fbId)); + System.out.println("Is bound to draw? " + (fbId == curDrawBinding)); + System.out.println("Is bound to read? " + (fbId == curReadBinding)); + System.out.println("Draw buffer: " + drawBuf); + System.out.println("Read buffer: " + readBuf); + + if (context.boundFBO != fbId) { + gl.glBindFramebuffer(GL2GL3.GL_DRAW_FRAMEBUFFER, fbId); + context.boundFBO = fbId; + } + + if (fb.getDepthBuffer() != null) { + printRealRenderBufferInfo(fb, fb.getDepthBuffer(), "Depth"); + } + for (int i = 0; i < fb.getNumColorBuffers(); i++) { + printRealRenderBufferInfo(fb, fb.getColorBuffer(i), "Color" + i); + } + } + + private void checkFrameBufferError() { + 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 RendererException("Resolution " + fb.getWidth() + + ":" + fb.getHeight() + " is not supported."); + } + + TextureUtil.GLImageFormat glFmt = TextureUtil.getImageFormatWithError(rb.getFormat()); + + if (fb.getSamples() > 1 && gl.isExtensionAvailable("GL_EXT_framebuffer_multisample") + && gl.isGL2GL3()/*&& gl.isFunctionAvailable("glRenderbufferStorageMultisample")*/) { + int samples = fb.getSamples(); + if (maxFBOSamples < samples) { + samples = maxFBOSamples; + } + gl.getGL2GL3() + .glRenderbufferStorageMultisample(GL.GL_RENDERBUFFER, samples, + glFmt.internalFormat, fb.getWidth(), + fb.getHeight()); + } else { + gl.glRenderbufferStorage(GL.GL_RENDERBUFFER, + glFmt.internalFormat, fb.getWidth(), fb.getHeight()); + } + } + + 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(); + Image image = tex.getImage(); + if (image.isUpdateNeeded()) { + updateTexImageData(image, tex.getType(), 0); + + // NOTE: For depth textures, sets nearest/no-mips mode + // Required to fix "framebuffer unsupported" + // for old NVIDIA drivers! + setupTextureParams(tex); + } + + gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, convertAttachmentSlot(rb.getSlot()), + convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()), + 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) { + GL gl = GLContext.getCurrentGL(); + gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, convertAttachmentSlot(rb.getSlot()), + GL.GL_RENDERBUFFER, rb.getId()); + } + } + + public void updateFrameBuffer(FrameBuffer fb) { + GL gl = GLContext.getCurrentGL(); + int id = fb.getId(); + if (id == -1) { + // create FBO + gl.glGenFramebuffers(1, intBuf1); + id = intBuf1.get(0); + fb.setId(id); + objManager.registerObject(fb); + + statistics.onNewFrameBuffer(); + } + + if (context.boundFBO != id) { + gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 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); + GL gl = GLContext.getCurrentGL(); + if (gl.isGL2GL3()) { + for (int i = 0; i < samplePositions.length; i++) { + gl.getGL3().glGetMultisamplefv(GL3.GL_SAMPLE_POSITION, i, samplePos); + samplePos.clear(); + samplePositions[i] = new Vector2f(samplePos.get(0) - 0.5f, + samplePos.get(1) - 0.5f); + } + } + return samplePositions; + } + + public void setMainFrameBufferOverride(FrameBuffer fb) { + mainFbOverride = fb; + } + + public void setFrameBuffer(FrameBuffer fb) { + if (fb == null && mainFbOverride != null) { + fb = mainFbOverride; + } + + if (lastFb == fb) { + if (fb == null || !fb.isUpdateNeeded()) { + return; + } + } + + GL gl = GLContext.getCurrentGL(); + // generate mipmaps for last FB if needed + if (lastFb != null) { + for (int i = 0; i < lastFb.getNumColorBuffers(); i++) { + RenderBuffer rb = lastFb.getColorBuffer(i); + Texture tex = rb.getTexture(); + if (tex != null + && tex.getMinFilter().usesMipMapLevels()) { + setTexture(0, rb.getTexture()); + + int textureType = convertTextureType(tex.getType(), tex.getImage().getMultiSamples(), rb.getFace()); + gl.glEnable(textureType); + gl.glGenerateMipmap(textureType); + gl.glDisable(textureType); + } + } + } + + if (fb == null) { + // unbind any fbos + if (context.boundFBO != 0) { + gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0); + statistics.onFrameBufferUse(null, true); + + context.boundFBO = 0; + } + // select back buffer + if (context.boundDrawBuf != -1) { + if (gl.isGL2GL3()) { + gl.getGL2GL3().glDrawBuffer(initialDrawBuf); + } + context.boundDrawBuf = -1; + } + if (context.boundReadBuf != -1) { + if (gl.isGL2GL3()) { + gl.getGL2GL3().glReadBuffer(initialReadBuf); + } + context.boundReadBuf = -1; + } + + lastFb = null; + } else { + if (fb.getNumColorBuffers() == 0 && fb.getDepthBuffer() == null) { + throw new IllegalArgumentException("The framebuffer: " + fb + + "\nDoesn't have any color/depth buffers"); + } + + if (fb.isUpdateNeeded()) { + updateFrameBuffer(fb); + } + + if (context.boundFBO != fb.getId()) { + gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 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) { + if (gl.isGL2GL3()) { + gl.getGL2GL3().glDrawBuffer(GL.GL_NONE); + } + context.boundDrawBuf = -2; + } + if (context.boundReadBuf != -2) { + if (gl.isGL2GL3()) { + gl.getGL2GL3().glReadBuffer(GL.GL_NONE); + } + context.boundReadBuf = -2; + } + } else { + if (fb.getNumColorBuffers() > maxFBOAttachs) { + throw new RendererException("Framebuffer has more color " + + "attachments than are supported" + + " by the video hardware!"); + } + if (fb.isMultiTarget()) { + if (fb.getNumColorBuffers() > maxMRTFBOAttachs) { + throw new RendererException("Framebuffer has more" + + " multi targets than are supported" + + " by the video hardware!"); + } + + 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(); + if (gl.isGL2GL3()) { + gl.getGL2GL3().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()) { + if (gl.isGL2GL3()) { + gl.getGL2GL3().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, "=== jMonkeyEngine FBO State ===\n{0}", fb); + printRealFrameBufferInfo(fb); + throw ex; + } + } + } + + 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()) { + if (gl.isGL2GL3()) { + gl.getGL2GL3().glReadBuffer(GL.GL_COLOR_ATTACHMENT0 + rb.getSlot()); + } + context.boundReadBuf = rb.getSlot(); + } + } else { + setFrameBuffer(null); + } + + gl.glReadPixels(vpX, vpY, vpW, vpH, /*GL.GL_RGBA*/ GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, byteBuf); + } + + private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { + intBuf1.put(0, rb.getId()); + GL gl = GLContext.getCurrentGL(); + gl.glDeleteRenderbuffers(1, intBuf1); + } + + public void deleteFrameBuffer(FrameBuffer fb) { + if (fb.getId() != -1) { + GL gl = GLContext.getCurrentGL(); + if (context.boundFBO == fb.getId()) { + gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 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()); + gl.glDeleteFramebuffers(1, intBuf1); + fb.resetObject(); + + statistics.onDeleteFrameBuffer(); + } + } + + /*********************************************************************\ + |* Textures *| + \*********************************************************************/ + private int convertTextureType(Texture.Type type, int samples, int face) { + switch (type) { + case TwoDimensional: + if (samples > 1) { + return GL3.GL_TEXTURE_2D_MULTISAMPLE; + } else { + return GL.GL_TEXTURE_2D; + } + case TwoDimensionalArray: + if (samples > 1) { + return GL3.GL_TEXTURE_2D_MULTISAMPLE_ARRAY; + } else { + return GL.GL_TEXTURE_2D_ARRAY; + } + case ThreeDimensional: + return GL2ES2.GL_TEXTURE_3D; + case CubeMap: + if (face < 0) { + return GL.GL_TEXTURE_CUBE_MAP; + } else if (face < 6) { + return GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X + face; + } else { + throw new UnsupportedOperationException("Invalid cube map face index: " + face); + } + 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 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); + } + } + + @SuppressWarnings("fallthrough") + private void setupTextureParams(Texture tex) { + GL gl = GLContext.getCurrentGL(); + Image image = tex.getImage(); + int target = convertTextureType(tex.getType(), image != null ? image.getMultiSamples() : 1, -1); + + // 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); + + if (tex.getAnisotropicFilter() > 1) { + if (gl.isExtensionAvailable("GL_EXT_texture_filter_anisotropic")) { + gl.glTexParameterf(target, + GL.GL_TEXTURE_MAX_ANISOTROPY_EXT, + tex.getAnisotropicFilter()); + } + } + + if (context.pointSprite) { + return; // Attempt to fix glTexParameter crash for some ATI GPUs + } + // repeat modes + switch (tex.getType()) { + case ThreeDimensional: + case CubeMap: // cubemaps use 3D coords + gl.glTexParameteri(target, GL2ES2.GL_TEXTURE_WRAP_R, convertWrapMode(tex.getWrap(WrapAxis.R))); + case TwoDimensional: + case TwoDimensionalArray: + gl.glTexParameteri(target, GL2ES2.GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); + // fall down here is intentional.. +// case OneDimensional: + gl.glTexParameteri(target, GL2ES2.GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S))); + break; + default: + throw new UnsupportedOperationException("Unknown texture type: " + tex.getType()); + } + + if (tex.isNeedCompareModeUpdate()) { + // R to Texture compare mode + if (tex.getShadowCompareMode() != Texture.ShadowCompareMode.Off) { + gl.glTexParameteri(target, GL2ES2.GL_TEXTURE_COMPARE_MODE, GL2.GL_COMPARE_R_TO_TEXTURE); + gl.glTexParameteri(target, GL2.GL_DEPTH_TEXTURE_MODE, GL2.GL_INTENSITY); + if (tex.getShadowCompareMode() == Texture.ShadowCompareMode.GreaterOrEqual) { + gl.glTexParameteri(target, GL2ES2.GL_TEXTURE_COMPARE_FUNC, GL.GL_GEQUAL); + } else { + gl.glTexParameteri(target, GL2ES2.GL_TEXTURE_COMPARE_FUNC, GL.GL_LEQUAL); + } + } else { + //restoring default value + gl.glTexParameteri(target, GL2ES2.GL_TEXTURE_COMPARE_MODE, GL.GL_NONE); + } + tex.compareModeUpdated(); + } + } + + /** + * Uploads the given image to the GL driver. + * + * @param img The image to upload + * @param type How the data in the image argument should be interpreted. + * @param unit The texture slot to be used to upload the image, not + * important + */ + public void updateTexImageData(Image img, Texture.Type type, int unit) { + int texId = img.getId(); + GL gl = GLContext.getCurrentGL(); + if (texId == -1) { + // create texture + gl.glGenTextures(1, intBuf1); + texId = intBuf1.get(0); + img.setId(texId); + objManager.registerObject(img); + + statistics.onNewTexture(); + } + + // bind texture + int target = convertTextureType(type, img.getMultiSamples(), -1); + if (context.boundTextureUnit != unit) { + gl.glActiveTexture(GL.GL_TEXTURE0 + unit); + context.boundTextureUnit = unit; + } + if (context.boundTextures[unit] != img) { + gl.glBindTexture(target, texId); + context.boundTextures[unit] = img; + + statistics.onTextureUse(img, true); + } + + if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) { + // No pregenerated mips available, + // generate from base level if required + if (!gl.isExtensionAvailable("GL_VERSION_3_0")) { + if (gl.isGL2ES1()) { + gl.glTexParameteri(target, GL2ES1.GL_GENERATE_MIPMAP, GL.GL_TRUE); + } + img.setMipmapsGenerated(true); + } + } else { + // Image already has mipmaps or no mipmap generation desired. +// glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0 ); + if (img.getMipMapSizes() != null) { + if (gl.isGL2GL3()) { + gl.glTexParameteri(target, GL2GL3.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1); + } + } + } + + int imageSamples = img.getMultiSamples(); + if (imageSamples > 1) { + if (img.getFormat().isDepthFormat()) { + img.setMultiSamples(Math.min(maxDepthTexSamples, imageSamples)); + } else { + img.setMultiSamples(Math.min(maxColorTexSamples, imageSamples)); + } + } + + // Yes, some OpenGL2 cards (GeForce 5) still dont support NPOT. + if (!gl.isExtensionAvailable("GL_ARB_texture_non_power_of_two")) { + if (img.getWidth() != 0 && img.getHeight() != 0) { + if (!FastMath.isPowerOfTwo(img.getWidth()) + || !FastMath.isPowerOfTwo(img.getHeight())) { + if (img.getData(0) == null) { + throw new RendererException("non-power-of-2 framebuffer textures are not supported by the video hardware"); + } else { + MipMapGenerator.resizeToPowerOf2(img); + } + } + } + } + + // Check if graphics card doesn't support multisample textures + if (!gl.isExtensionAvailable("GL_ARB_texture_multisample")) { + if (img.getMultiSamples() > 1) { + throw new RendererException("Multisample textures not supported by graphics hardware"); + } + } + + if (target == GL.GL_TEXTURE_CUBE_MAP) { + // Check max texture size before upload + if (img.getWidth() > maxCubeTexSize || img.getHeight() > maxCubeTexSize) { + throw new RendererException("Cannot upload cubemap " + img + ". The maximum supported cubemap resolution is " + maxCubeTexSize); + } + } else { + if (img.getWidth() > maxTexSize || img.getHeight() > maxTexSize) { + throw new RendererException("Cannot upload texture " + img + ". The maximum supported texture resolution is " + maxTexSize); + } + } + + if (target == GL.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.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, 0); + } + } else if (target == GL.GL_TEXTURE_2D_ARRAY) { + List data = img.getData(); + // -1 index specifies prepare data for 2D Array + TextureUtil.uploadTexture(img, target, -1, 0); + 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); + } + } else { + TextureUtil.uploadTexture(img, target, 0, 0); + } + + if (img.getMultiSamples() != imageSamples) { + img.setMultiSamples(imageSamples); + } + + if (gl.isExtensionAvailable("GL_VERSION_3_0")) { + if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired() && img.getData() != null) { + // XXX: Required for ATI + gl.glEnable(target); + gl.glGenerateMipmap(target); + gl.glDisable(target); + img.setMipmapsGenerated(true); + } + } + + img.clearUpdateNeeded(); + } + + public void setTexture(int unit, Texture tex) { + GL gl = GLContext.getCurrentGL(); + Image image = tex.getImage(); + if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) { + updateTexImageData(image, tex.getType(), unit); + } + + int texId = image.getId(); + assert texId != -1; + + Image[] textures = context.boundTextures; + + int type = convertTextureType(tex.getType(), image.getMultiSamples(), -1); +// if (!context.textureIndexList.moveToNew(unit)) { +// if (context.boundTextureUnit != unit){ +// gl.glActiveTexture(GL.GL_TEXTURE0 + unit); +// context.boundTextureUnit = unit; +// } +// gl.glEnable(type); +// } + + if (context.boundTextureUnit != unit) { + gl.glActiveTexture(GL.GL_TEXTURE0 + unit); + context.boundTextureUnit = unit; + } + if (textures[unit] != image) { + gl.glBindTexture(type, texId); + textures[unit] = image; + + statistics.onTextureUse(image, true); + } else { + statistics.onTextureUse(image, false); + } + + setupTextureParams(tex); + } + + public void modifyTexture(Texture tex, Image pixels, int x, int y) { + setTexture(0, tex); + TextureUtil.uploadSubTexture(pixels, convertTextureType(tex.getType(), tex.getImage().getMultiSamples(), -1), 0, x, y); + } + + public void clearTextureUnits() { + /*GL gl = GLContext.getCurrentGL(); + 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 deleteImage(Image image) { + int texId = image.getId(); + if (texId != -1) { + intBuf1.put(0, texId); + intBuf1.position(0).limit(1); + GL gl = GLContext.getCurrentGL(); + gl.glDeleteTextures(1, intBuf1); + image.resetObject(); + + statistics.onDeleteTexture(); + } + } + + /*********************************************************************\ + |* Vertex Buffers and Attributes *| + \*********************************************************************/ + 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); + } + } + + private int convertFormat(VertexBuffer.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 GL.GL_UNSIGNED_INT; +// case Half: +// return NVHalfFloat.GL_HALF_FLOAT_NV; +// return ARBHalfFloatVertex.GL_HALF_FLOAT; + case Float: + return GL.GL_FLOAT; + case Double: + return GL2GL3.GL_DOUBLE; + default: + throw new UnsupportedOperationException("Unknown buffer format."); + + } + } + + public void updateBufferData(VertexBuffer vb) { + GL gl = GLContext.getCurrentGL(); + int bufId = vb.getId(); + boolean created = false; + if (bufId == -1) { + // create buffer + gl.glGenBuffers(1, intBuf1); + bufId = intBuf1.get(0); + vb.setId(bufId); + objManager.registerObject(vb); + + //statistics.onNewVertexBuffer(); + + created = true; + } + + // bind buffer + 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; + //statistics.onVertexBufferUse(vb, true); + } else { + //statistics.onVertexBufferUse(vb, false); + } + } else { + target = GL.GL_ARRAY_BUFFER; + if (context.boundArrayVBO != bufId) { + gl.glBindBuffer(target, bufId); + context.boundArrayVBO = bufId; + //statistics.onVertexBufferUse(vb, true); + } else { + //statistics.onVertexBufferUse(vb, false); + } + } + + int usage = convertUsage(vb.getUsage()); + Buffer data = vb.getData(); + data.rewind(); + + if (created || vb.hasDataSizeChanged()) { + // upload data based on format + gl.glBufferData(target, data.capacity() * vb.getFormat().getComponentSize(), data, usage); + } else { + gl.glBufferSubData(target, 0, data.capacity() * vb.getFormat().getComponentSize(), data); + } + + vb.clearUpdateNeeded(); + } + + public void deleteBuffer(VertexBuffer vb) { + GL gl = GLContext.getCurrentGL(); + int bufId = vb.getId(); + if (bufId != -1) { + // delete buffer + intBuf1.put(0, bufId); + intBuf1.position(0).limit(1); + gl.glDeleteBuffers(1, intBuf1); + vb.resetObject(); + + //statistics.onDeleteVertexBuffer(); + } + } + + public void clearVertexAttribs() { + IDList attribList = context.attribIndexList; + for (int i = 0; i < attribList.oldLen; i++) { + int idx = attribList.oldList[i]; + GL gl = GLContext.getCurrentGL(); + gl.getGL2ES2().glDisableVertexAttribArray(idx); + context.boundAttribs[idx] = null; + } + context.attribIndexList.copyNewToOld(); + } + + public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { + if (vb.getBufferType() == VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); + } + + int programId = context.boundShaderProgram; + if (programId > 0) { + GL gl = GLContext.getCurrentGL(); + Attribute attrib = boundShader.getAttribute(vb.getBufferType()); + int loc = attrib.getLocation(); + if (loc == -1) { + return; // not defined + } + if (loc == -2) { + stringBuf.setLength(0); + // JOGL 2.0 doesn't need a null terminated string + stringBuf.append("in").append(vb.getBufferType().name()); + loc = gl.getGL2ES2().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); + } + } + + if (vb.isUpdateNeeded() && idb == null) { + updateBufferData(vb); + } + + VertexBuffer[] attribs = context.boundAttribs; + if (!context.attribIndexList.moveToNew(loc)) { + gl.getGL2ES2().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) { + gl.glBindBuffer(GL.GL_ARRAY_BUFFER, bufId); + context.boundArrayVBO = bufId; + //statistics.onVertexBufferUse(vb, true); + } else { + //statistics.onVertexBufferUse(vb, false); + } + + gl.getGL2ES2().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) { + GL gl = GLContext.getCurrentGL(); + if (count > 1) { + if (gl.isGL2GL3()) { + gl.getGL2GL3().glDrawArraysInstanced(convertElementMode(mode), 0, + vertCount, count); + } + } else { + gl.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; + + GL gl = GLContext.getCurrentGL(); + + if (context.boundElementArrayVBO != bufId) { + gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, bufId); + context.boundElementArrayVBO = bufId; + //statistics.onVertexBufferUse(indexBuf, true); + } else { + //statistics.onVertexBufferUse(indexBuf, true); + } + + int vertCount = mesh.getVertexCount(); + boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); + + if (mesh.getMode() == Mode.Hybrid) { + int[] modeStart = mesh.getModeStart(); + int[] elementLengths = mesh.getElementLengths(); + + int elMode = convertElementMode(Mode.Triangles); + int fmt = convertFormat(indexBuf.getFormat()); + int elSize = indexBuf.getFormat().getComponentSize(); + int listStart = modeStart[0]; + int stripStart = modeStart[1]; + int fanStart = modeStart[2]; + int curOffset = 0; + for (int i = 0; i < elementLengths.length; i++) { + if (i == stripStart) { + elMode = convertElementMode(Mode.TriangleStrip); + } else if (i == fanStart) { + elMode = convertElementMode(Mode.TriangleStrip); + } + int elementLength = elementLengths[i]; + + if (useInstancing) { + if (gl.isGL2()) { + indexBuf.getData().position(curOffset); + indexBuf.getData().limit(curOffset + elementLength); + + gl.getGL2().glDrawElementsInstanced(elMode, + elementLength, + fmt, + indexBuf.getData(), + count); + } else { + throw new IllegalArgumentException( + "instancing is not supported."); + } + } else { + if (gl.isGL2GL3()) { + gl.getGL2GL3().glDrawRangeElements(elMode, + 0, + vertCount, + elementLength, + fmt, + curOffset); + } else { + indexBuf.getData().position(curOffset); + gl.getGL2().glDrawElements(elMode, elementLength, fmt, + indexBuf.getData()); + } + } + + //FIXME check whether elSize is required + curOffset += elementLength * elSize; + } + } else { + if (useInstancing) { + if (gl.isGL2()) { + gl.getGL2().glDrawElementsInstanced(convertElementMode(mesh.getMode()), + indexBuf.getData().limit(), + convertFormat(indexBuf.getFormat()), + indexBuf.getData(), + count); + } else { + throw new IllegalArgumentException( + "instancing is not supported."); + } + } else { + if (gl.isGL2GL3()) { + gl.getGL2GL3().glDrawRangeElements(convertElementMode(mesh.getMode()), + 0, + vertCount, + indexBuf.getData().limit(), + convertFormat(indexBuf.getFormat()), + 0); + } else { + indexBuf.getData().rewind(); + gl.glDrawElements(convertElementMode(mesh.getMode()), + indexBuf.getData().limit(), + convertFormat(indexBuf.getFormat()), 0); + } + } + } + } + + /** + * *******************************************************************\ |* + * Render Calls *| + \******************************************************************** + */ + 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); + } + } + + public void updateVertexArray(Mesh mesh) { + int id = mesh.getId(); + GL gl = GLContext.getCurrentGL(); + + if (id == -1) { + IntBuffer temp = intBuf1; + if (gl.isGL2GL3()) { + gl.getGL2GL3().glGenVertexArrays(1, temp); + } + id = temp.get(0); + mesh.setId(id); + } + + if (context.boundVertexArray != id) { + if (gl.isGL2GL3()) { + gl.getGL2GL3().glBindVertexArray(id); + } + context.boundVertexArray = id; + } + + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()) { + updateBufferData(interleavedData); + } + + for (VertexBuffer vb : mesh.getBufferList().getArray()) { + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } + + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + } + + private void renderMeshVertexArray(Mesh mesh, int lod, int count) { + if (mesh.getId() == -1) { + updateVertexArray(mesh); + } else { + // TODO: Check if it was updated + } + + if (context.boundVertexArray != mesh.getId()) { + GL gl = GLContext.getCurrentGL(); + if (gl.isGL2GL3()) { + gl.getGL2GL3().glBindVertexArray(mesh.getId()); + } + context.boundVertexArray = mesh.getId(); + } + +// IntMap buffers = mesh.getBuffers(); + VertexBuffer indices; + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = mesh.getBuffer(Type.Index); + } + if (indices != null) { + drawTriangleList(indices, mesh, count); + } else { + drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + private void renderMeshDefault(Mesh mesh, int lod, int count) { + VertexBuffer indices; + + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()) { + updateBufferData(interleavedData); + } + +// IntMap buffers = mesh.getBuffers(); + SafeArrayList buffersList = mesh.getBufferList(); + + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = mesh.getBuffer(Type.Index); + } + +// for (Entry entry : buffers) { +// VertexBuffer vb = entry.getValue(); + for (VertexBuffer vb : mesh.getBufferList().getArray()) { + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } + + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + + if (indices != null) { + drawTriangleList(indices, mesh, count); + } else { + drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + public void renderMesh(Mesh mesh, int lod, int count) { + if (mesh.getVertexCount() == 0) { + return; + } + + GL gl = GLContext.getCurrentGL(); + if (context.pointSprite && mesh.getMode() != Mode.Points) { + // XXX: Hack, disable point sprite mode if mesh not in point mode + if (context.boundTextures[0] != null) { + if (context.boundTextureUnit != 0) { + gl.glActiveTexture(GL.GL_TEXTURE0); + context.boundTextureUnit = 0; + } + if (gl.isGL2ES1()) { + gl.glDisable(GL2ES1.GL_POINT_SPRITE); + } + if (gl.isGL2GL3()) { + gl.glDisable(GL2GL3.GL_VERTEX_PROGRAM_POINT_SIZE); + } + context.pointSprite = false; + } + } + + if (context.pointSize != mesh.getPointSize()) { + if (gl.isGL2GL3()) { + gl.getGL2GL3().glPointSize(mesh.getPointSize()); + } + context.pointSize = mesh.getPointSize(); + } + if (context.lineWidth != mesh.getLineWidth()) { + gl.glLineWidth(mesh.getLineWidth()); + context.lineWidth = mesh.getLineWidth(); + } + + statistics.onMeshDrawn(mesh, lod); +// if (gl.isExtensionAvailable("GL_ARB_vertex_array_object")){ +// renderMeshVertexArray(mesh, lod, count); +// }else{ + renderMeshDefault(mesh, lod, count); +// } + } +} diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java new file mode 100644 index 000000000..9e26678d8 --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java @@ -0,0 +1,502 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.renderer.jogl; + +import com.jme3.renderer.RendererException; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +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 javax.media.opengl.GLContext; + +public class TextureUtil { + + private static boolean abgrToRgbaConversionEnabled = false; + + 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 class GLImageFormat { + + int internalFormat; + int format; + int dataType; + boolean compressed; + + public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed) { + this.internalFormat = internalFormat; + this.format = format; + this.dataType = dataType; + this.compressed = compressed; + } + } + + private static final GLImageFormat[] formatToGL = new GLImageFormat[Format.values().length]; + + private static void setFormat(Format format, int glInternalFormat, int glFormat, int glDataType, boolean glCompressed){ + formatToGL[format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType, glCompressed); + } + + static { + // Alpha formats + setFormat(Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE, false); + setFormat(Format.Alpha16, GL2.GL_ALPHA16, GL.GL_ALPHA, GL.GL_UNSIGNED_SHORT, false); + + // Luminance formats + setFormat(Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE, false); + setFormat(Format.Luminance16, GL2.GL_LUMINANCE16, GL.GL_LUMINANCE, GL.GL_UNSIGNED_SHORT, false); + setFormat(Format.Luminance16F, GL2.GL_LUMINANCE16F, GL.GL_LUMINANCE, GL.GL_HALF_FLOAT, false); + setFormat(Format.Luminance32F, GL2.GL_LUMINANCE32F, GL.GL_LUMINANCE, GL.GL_FLOAT, false); + + // Luminance alpha formats + setFormat(Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE, false); + setFormat(Format.Luminance16Alpha16, GL2.GL_LUMINANCE16_ALPHA16, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_SHORT, false); + setFormat(Format.Luminance16FAlpha16F, GL2.GL_LUMINANCE_ALPHA16F, GL.GL_LUMINANCE_ALPHA, GL.GL_HALF_FLOAT, false); + + // Depth formats + setFormat(Format.Depth, GL2ES2.GL_DEPTH_COMPONENT, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE, false); + setFormat(Format.Depth16, GL.GL_DEPTH_COMPONENT16, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_SHORT, false); + setFormat(Format.Depth24, GL.GL_DEPTH_COMPONENT24, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, false); + setFormat(Format.Depth32, GL.GL_DEPTH_COMPONENT32, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, false); + setFormat(Format.Depth32F, GL2GL3.GL_DEPTH_COMPONENT32F, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_FLOAT, false); + + // Depth stencil formats + setFormat(Format.Depth24Stencil8, GL.GL_DEPTH24_STENCIL8, GL.GL_DEPTH_STENCIL, GL.GL_UNSIGNED_INT_24_8, false); + + // RGB formats + setFormat(Format.BGR8, GL.GL_RGB8, GL2GL3.GL_BGR, GL.GL_UNSIGNED_BYTE, false); + setFormat(Format.RGB8, GL.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, false); +// setFormat(Format.RGB10, GL11.GL_RGB10, GL11.GL_RGB, GL12.GL_UNSIGNED_INT_10_10_10_2, false); + setFormat(Format.RGB16, GL2GL3.GL_RGB16, GL.GL_RGB, GL.GL_UNSIGNED_SHORT, false); + setFormat(Format.RGB16F, GL2ES2.GL_RGB16F, GL.GL_RGB, GL.GL_HALF_FLOAT, false); + setFormat(Format.RGB32F, GL.GL_RGB32F, GL.GL_RGB, GL.GL_FLOAT, false); + + // Special RGB formats + setFormat(Format.RGB111110F, GL.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_UNSIGNED_INT_10F_11F_11F_REV, false); + setFormat(Format.RGB9E5, GL2GL3.GL_RGB9_E5, GL.GL_RGB, GL2GL3.GL_UNSIGNED_INT_5_9_9_9_REV, false); + setFormat(Format.RGB16F_to_RGB111110F, GL.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_HALF_FLOAT, false); + setFormat(Format.RGB16F_to_RGB9E5, GL2.GL_RGB9_E5, GL.GL_RGB, GL.GL_HALF_FLOAT, false); + + // RGBA formats + setFormat(Format.ABGR8, GL.GL_RGBA8, GL2.GL_ABGR_EXT, GL.GL_UNSIGNED_BYTE, false); + setFormat(Format.RGB5A1, GL.GL_RGB5_A1, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1, false); + setFormat(Format.ARGB4444, GL.GL_RGBA4, GL2.GL_ABGR_EXT, GL.GL_UNSIGNED_SHORT_4_4_4_4, false); + setFormat(Format.RGBA8, GL.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false); + setFormat(Format.RGBA16, GL2GL3.GL_RGBA16, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT, false); // might be incorrect + setFormat(Format.RGBA16F, GL2ES2.GL_RGBA16F, GL.GL_RGBA, GL.GL_HALF_FLOAT, false); + setFormat(Format.RGBA32F, GL.GL_RGBA32F, GL.GL_RGBA, GL.GL_FLOAT, false); + + // DXT formats + setFormat(Format.DXT1, GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, true); + setFormat(Format.DXT1A, GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); + setFormat(Format.DXT3, GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); + setFormat(Format.DXT5, GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); + + // LTC/LATC/3Dc formats + setFormat(Format.LTC, GL2.GL_COMPRESSED_LUMINANCE_LATC1_EXT, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE, true); + setFormat(Format.LATC, GL2.GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE, true); + } + + public static GLImageFormat getImageFormat(Format fmt){ + GL gl = GLContext.getCurrentGL(); + switch (fmt){ + case ABGR8: + if (!gl.isExtensionAvailable("GL_EXT_abgr") && !abgrToRgbaConversionEnabled) { + setFormat(Format.ABGR8, GL.GL_RGBA, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false); + abgrToRgbaConversionEnabled = true; + } + break; + case BGR8: + if (!gl.isExtensionAvailable("GL_VERSION_1_2") && !gl.isExtensionAvailable("EXT_bgra")){ + return null; + } + break; + case DXT1: + case DXT1A: + case DXT3: + case DXT5: + if (!gl.isExtensionAvailable("GL_EXT_texture_compression_s3tc")) { + return null; + } + break; + case Depth: + case Depth16: + case Depth24: + case Depth32: + if (!gl.isExtensionAvailable("GL_VERSION_1_4") && !gl.isExtensionAvailable("ARB_depth_texture")){ + return null; + } + break; + case Depth24Stencil8: + if (!gl.isExtensionAvailable("GL_VERSION_3_0")){ + return null; + } + break; + case Luminance16F: + case Luminance16FAlpha16F: + case Luminance32F: + if (!gl.isExtensionAvailable("GL_ARB_texture_float")){ + return null; + } + break; + case RGB16F: + case RGB32F: + case RGBA16F: + case RGBA32F: + if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_ARB_texture_float")){ + return null; + } + break; + case Depth32F: + if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_NV_depth_buffer_float")){ + return null; + } + break; + case LATC: + case LTC: + if (!gl.isExtensionAvailable("GL_EXT_texture_compression_latc")){ + return null; + } + break; + case RGB9E5: + case RGB16F_to_RGB9E5: + if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_EXT_texture_shared_exponent")){ + return null; + } + break; + case RGB111110F: + case RGB16F_to_RGB111110F: + if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_EXT_packed_float")){ + return null; + } + break; + } + return formatToGL[fmt.ordinal()]; + } + + public static GLImageFormat getImageFormatWithError(Format fmt) { + GLImageFormat glFmt = getImageFormat(fmt); + if (glFmt == null) { + throw new RendererException("Image format '" + fmt + "' is unsupported by the video hardware."); + } + return glFmt; + } + + public static void uploadTexture(Image image, + int target, + int index, + int border){ + GL gl = GLContext.getCurrentGL(); + Image.Format fmt = image.getFormat(); + GLImageFormat glFmt = getImageFormatWithError(fmt); + + ByteBuffer data; + if (index >= 0 && image.getData() != null && image.getData().size() > 0){ + data = image.getData(index); + }else{ + data = null; + } + + int width = image.getWidth(); + int height = image.getHeight(); + int depth = image.getDepth(); + + if (data != null) { + if (abgrToRgbaConversionEnabled) { + convertABGRtoRGBA(data); + } + gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); + } + + int[] mipSizes = image.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 = image.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 (glFmt.compressed && data != null){ + if (target == GL2ES2.GL_TEXTURE_3D){ + gl.getGL2ES2().glCompressedTexImage3D(target, + i, + glFmt.internalFormat, + mipWidth, + mipHeight, + mipDepth, + data.remaining(), + border, + data); + }else{ + //all other targets use 2D: array, cubemap, 2d + gl.glCompressedTexImage2D(target, + i, + glFmt.internalFormat, + mipWidth, + mipHeight, + data.remaining(), + border, + data); + } + }else{ + if (target == GL2ES2.GL_TEXTURE_3D){ + gl.getGL2ES2().glTexImage3D(target, + i, + glFmt.internalFormat, + mipWidth, + mipHeight, + mipDepth, + border, + glFmt.format, + glFmt.dataType, + data); + }else if (target == GL.GL_TEXTURE_2D_ARRAY){ + // prepare data for 2D array + // or upload slice + if (index == -1){ + gl.getGL2ES2().glTexImage3D(target, + 0, + glFmt.internalFormat, + mipWidth, + mipHeight, + image.getData().size(), //# of slices + border, + glFmt.format, + glFmt.dataType, + data); + }else{ + gl.getGL2ES2().glTexSubImage3D(target, + i, // level + 0, // xoffset + 0, // yoffset + index, // zoffset + width, // width + height, // height + 1, // depth + glFmt.format, + glFmt.dataType, + data); + } + }else{ + if (subtex){ + if (samples > 1){ + throw new IllegalStateException("Cannot update multisample textures"); + } + + gl.glTexSubImage2D(target, + i, + 0, 0, + mipWidth, mipHeight, + glFmt.format, + glFmt.dataType, + data); + }else{ + if (samples > 1){ + if (gl.isGL2GL3()) { + gl.getGL3().glTexImage2DMultisample(target, + samples, + glFmt.internalFormat, + mipWidth, + mipHeight, + true); + } + } else { + gl.glTexImage2D(target, + i, + glFmt.internalFormat, + mipWidth, + mipHeight, + border, + glFmt.format, + glFmt.dataType, + data); + } + } + } + } + + pos += mipSizes[i]; + } + } + + private static void convertABGRtoRGBA(ByteBuffer buffer) { + + for (int i = 0; i < buffer.capacity(); i++) { + + int a = buffer.get(i++); + int b = buffer.get(i++); + int g = buffer.get(i++); + int r = buffer.get(i); + + buffer.put(i - 3, (byte) r); + buffer.put(i - 2, (byte) g); + buffer.put(i - 1, (byte) b); + buffer.put(i, (byte) a); + + } + + } + + /** + * Update the texture currently bound to target at with data from the given Image at position x and y. The parameter + * index is used as the zoffset in case a 3d texture or texture 2d array is being updated. + * + * @param image Image with the source data (this data will be put into the texture) + * @param target the target texture + * @param index the mipmap level to update + * @param x the x position where to put the image in the texture + * @param y the y position where to put the image in the texture + */ + public static void uploadSubTexture( + Image image, + int target, + int index, + int x, + int y) { + GL gl = GLContext.getCurrentGL(); + Image.Format fmt = image.getFormat(); + GLImageFormat glFmt = getImageFormatWithError(fmt); + + ByteBuffer data = null; + if (index >= 0 && image.getData() != null && image.getData().size() > 0) { + data = image.getData(index); + } + + int width = image.getWidth(); + int height = image.getHeight(); + int depth = image.getDepth(); + + if (data != null) { + gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); + } + + int[] mipSizes = image.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 }; + } + } + + int samples = image.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]); + } + + // to remove the cumbersome if/then/else stuff below we'll update the pos right here and use continue after each + // gl*Image call in an attempt to unclutter things a bit + pos += mipSizes[i]; + + int glFmtInternal = glFmt.internalFormat; + int glFmtFormat = glFmt.format; + int glFmtDataType = glFmt.dataType; + + if (glFmt.compressed && data != null){ + if (target == GL2ES2.GL_TEXTURE_3D){ + gl.getGL2ES2().glCompressedTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtInternal, data.limit(), data); + continue; + } + + // all other targets use 2D: array, cubemap, 2d + gl.getGL2ES2().glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtInternal, data.limit(), data); + continue; + } + + if (target == GL2ES2.GL_TEXTURE_3D){ + gl.getGL2ES2().glTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtFormat, glFmtDataType, data); + continue; + } + + if (samples > 1){ + throw new IllegalStateException("Cannot update multisample textures"); + } + + gl.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtFormat, glFmtDataType, data); + continue; + } + } +} diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java new file mode 100644 index 000000000..a3fb53767 --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.jogl; + +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; +import com.jme3.input.awt.AwtKeyInput; +import com.jme3.input.awt.AwtMouseInput; +import com.jme3.renderer.jogl.JoglRenderer; +import com.jogamp.opengl.util.Animator; +import com.jogamp.opengl.util.AnimatorBase; +import com.jogamp.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.DebugGL2; +import javax.media.opengl.DebugGL3; +import javax.media.opengl.DebugGL3bc; +import javax.media.opengl.DebugGL4; +import javax.media.opengl.DebugGL4bc; +import javax.media.opengl.DebugGLES1; +import javax.media.opengl.DebugGLES2; +import javax.media.opengl.GL; +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLEventListener; +import javax.media.opengl.GLProfile; +import javax.media.opengl.GLRunnable; +import javax.media.opengl.awt.GLCanvas; + +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; + + protected void initGLCanvas() { + device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + + //FIXME use the settings to know whether to use the max programmable profile + //then call GLProfile.getMaxProgrammable(true); + GLCapabilities caps = new GLCapabilities(GLProfile.getMaxFixedFunc(true)); + 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(); + } + }; + canvas.invoke(false, new GLRunnable() { + public boolean run(GLAutoDrawable glad) { + canvas.getGL().setSwapInterval(settings.isVSync() ? 1 : 0); + return true; + } + }); + canvas.setFocusable(true); + canvas.requestFocus(); + canvas.setSize(settings.getWidth(), settings.getHeight()); + canvas.setIgnoreRepaint(true); + canvas.addGLEventListener(this); + + if (settings.getBoolean("GraphicsDebug")) { + canvas.invoke(false, new GLRunnable() { + public boolean run(GLAutoDrawable glad) { + GL gl = glad.getGL(); + if (gl.isGLES()) { + if (gl.isGLES1()) { + glad.setGL(new DebugGLES1(gl.getGLES1())); + } else { + if (gl.isGLES2()) { + glad.setGL(new DebugGLES2(gl.getGLES2())); + } else { + // TODO ES3 + } + } + } else { + if (gl.isGL4bc()) { + glad.setGL(new DebugGL4bc(gl.getGL4bc())); + } else { + if (gl.isGL4()) { + glad.setGL(new DebugGL4(gl.getGL4())); + } else { + if (gl.isGL3bc()) { + glad.setGL(new DebugGL3bc(gl.getGL3bc())); + } else { + if (gl.isGL3()) { + glad.setGL(new DebugGL3(gl.getGL3())); + } else { + if (gl.isGL2()) { + glad.setGL(new DebugGL2(gl.getGL2())); + } + } + } + } + } + } + return true; + } + }); + } + + renderer = new JoglRenderer(); + } + + protected void startGLCanvas() { + if (frameRate > 0) { + animator = new FPSAnimator(canvas, frameRate); + } + else { + animator = new Animator(); + animator.add(canvas); + ((Animator) animator).setRunAsFastAsPossible(true); + } + + animator.start(); + wasAnimating = true; + + //FIXME not sure it is the best place to do that + renderable.set(true); + } + + protected void onCanvasAdded() { + } + + protected void onCanvasRemoved() { + } + + @Override + public KeyInput getKeyInput() { + if (keyInput == null) { + keyInput = new AwtKeyInput(); + ((AwtKeyInput)keyInput).setInputSource(canvas); + } + return keyInput; + } + + @Override + public MouseInput getMouseInput() { + if (mouseInput == null) { + mouseInput = new AwtMouseInput(); + ((AwtMouseInput)mouseInput).setInputSource(canvas); + } + return mouseInput; + } + + public TouchInput getTouchInput() { + return null; + } + + 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/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglCanvas.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglCanvas.java new file mode 100644 index 000000000..1e9b0db25 --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglCanvas.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.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.fine("Display created."); + + renderer.initialize(); + listener.initialize(); + } + + public void display(GLAutoDrawable glad) { + if (!created.get() && renderer != null){ + listener.destroy(); + logger.fine("Canvas destroyed."); + super.internalDestroy(); + return; + } + + int newWidth = Math.max(canvas.getWidth(), 1); + int newHeight = Math.max(canvas.getHeight(), 1); + if (width != newWidth || height != newHeight) { + width = newWidth; + height = newHeight; + if (listener != null) { + listener.reshape(width, height); + } + } + + 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/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java new file mode 100644 index 000000000..9e852c746 --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.jogl; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.jogl.JoglRenderer; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext; +import com.jme3.system.NanoTimer; +import com.jme3.system.SystemListener; +import com.jme3.system.Timer; +import java.nio.IntBuffer; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.media.opengl.GL; +import javax.media.opengl.GL2GL3; +import javax.media.opengl.GLContext; + +public abstract class JoglContext implements JmeContext { + + protected AtomicBoolean created = new AtomicBoolean(false); + protected AtomicBoolean renderable = new AtomicBoolean(false); + protected final Object createdLock = new Object(); + + protected AppSettings settings = new AppSettings(true); + protected JoglRenderer renderer; + protected Timer timer; + protected SystemListener listener; + + protected KeyInput keyInput; + protected MouseInput mouseInput; + protected JoyInput joyInput; + + public void setSystemListener(SystemListener listener){ + this.listener = listener; + } + + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + } + + public boolean isRenderable(){ + return renderable.get(); + } + + public AppSettings getSettings() { + return settings; + } + + public Renderer getRenderer() { + return renderer; + } + + public MouseInput getMouseInput() { + return mouseInput; + } + + public KeyInput getKeyInput() { + return keyInput; + } + + public JoyInput getJoyInput() { + return joyInput; + } + + 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(); + } + } + + protected void internalDestroy() { + renderer = null; + timer = null; + renderable.set(false); + synchronized (createdLock){ + created.set(false); + createdLock.notifyAll(); + } + } + + protected int determineMaxSamples(int requestedSamples) { + GL gl = GLContext.getCurrentGL(); + if (gl.hasFullFBOSupport()) { + return gl.getMaxRenderbufferSamples(); + } else { + if (gl.isExtensionAvailable("GL_ARB_framebuffer_object") + || gl.isExtensionAvailable("GL_EXT_framebuffer_multisample")) { + IntBuffer intBuf1 = IntBuffer.allocate(1); + gl.glGetIntegerv(GL2GL3.GL_MAX_SAMPLES, intBuf1); + return intBuf1.get(0); + } else { + return Integer.MAX_VALUE; + } + } + } + + protected int getNumSamplesToUse() { + int samples = 0; + if (settings.getSamples() > 1){ + samples = settings.getSamples(); + int supportedSamples = determineMaxSamples(samples); + if (supportedSamples < samples) { + samples = supportedSamples; + } + } + return samples; + } +} diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglDisplay.java new file mode 100644 index 000000000..e095251f2 --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglDisplay.java @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.jogl; + +import com.jme3.system.AppSettings; +import java.awt.Dimension; +import java.awt.DisplayMode; +import java.awt.Frame; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Toolkit; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.lang.reflect.InvocationTargetException; +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 volatile boolean wasInited = false; + protected Frame frame; + + public Type getType() { + return Type.Display; + } + + protected void createGLFrame(){ + if (useAwt){ + frame = new Frame(settings.getTitle()); + }else{ + frame = new JFrame(settings.getTitle()); + } + frame.setResizable(false); + frame.add(canvas); + + applySettings(settings); + + // Make the window visible to realize the OpenGL surface. + frame.setVisible(true); + + canvas.setVisible(true); + + //this is the earliest safe opportunity to get the context + //final GLContext context = canvas.getContext(); + + /*canvas.invoke(true, new GLRunnable() { + @Override + public boolean run(GLAutoDrawable glAutoDrawable) { + context.makeCurrent(); + try { + startGLCanvas(); + } + finally { + context.release(); + } + return true; + } + });*/ + } + + protected void applySettings(AppSettings settings){ + final boolean isDisplayModeModified; + final GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + // Get the current display mode + final DisplayMode previousDisplayMode = gd.getDisplayMode(); + // Handle full screen mode if requested. + if (settings.isFullscreen()) { + frame.setUndecorated(true); + // Check if the full-screen mode is supported by the OS + boolean isFullScreenSupported = gd.isFullScreenSupported(); + if (isFullScreenSupported) { + gd.setFullScreenWindow(frame); + // Check if display mode changes are supported by the OS + if (gd.isDisplayChangeSupported()) { + // Get all available display modes + final DisplayMode[] displayModes = gd.getDisplayModes(); + DisplayMode multiBitsDepthSupportedDisplayMode = null; + DisplayMode refreshRateUnknownDisplayMode = null; + DisplayMode multiBitsDepthSupportedAndRefreshRateUnknownDisplayMode = null; + DisplayMode matchingDisplayMode = null; + DisplayMode currentDisplayMode; + // Look for the display mode that matches with our parameters + // Look for some display modes that are close to these parameters + // and that could be used as substitutes + // On some machines, the refresh rate is unknown and/or multi bit + // depths are supported. If you try to force a particular refresh + // rate or a bit depth, you might find no available display mode + // that matches exactly with your parameters + for (int i = 0; i < displayModes.length && matchingDisplayMode == null; i++) { + currentDisplayMode = displayModes[i]; + if (currentDisplayMode.getWidth() == settings.getWidth() + && currentDisplayMode.getHeight() == settings.getHeight()) { + if (currentDisplayMode.getBitDepth() == settings.getBitsPerPixel()) { + if (currentDisplayMode.getRefreshRate() == settings.getFrequency()) { + matchingDisplayMode = currentDisplayMode; + } else if (currentDisplayMode.getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN) { + refreshRateUnknownDisplayMode = currentDisplayMode; + } + } else if (currentDisplayMode.getBitDepth() == DisplayMode.BIT_DEPTH_MULTI) { + if (currentDisplayMode.getRefreshRate() == settings.getFrequency()) { + multiBitsDepthSupportedDisplayMode = currentDisplayMode; + } else if (currentDisplayMode.getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN) { + multiBitsDepthSupportedAndRefreshRateUnknownDisplayMode = currentDisplayMode; + } + } + } + } + DisplayMode nextDisplayMode = null; + if (matchingDisplayMode != null) { + nextDisplayMode = matchingDisplayMode; + } else if (multiBitsDepthSupportedDisplayMode != null) { + nextDisplayMode = multiBitsDepthSupportedDisplayMode; + } else if (refreshRateUnknownDisplayMode != null) { + nextDisplayMode = refreshRateUnknownDisplayMode; + } else if (multiBitsDepthSupportedAndRefreshRateUnknownDisplayMode != null) { + nextDisplayMode = multiBitsDepthSupportedAndRefreshRateUnknownDisplayMode; + } else { + isFullScreenSupported = false; + } + // If we have found a display mode that approximatively matches + // with the input parameters, use it + if (nextDisplayMode != null) { + gd.setDisplayMode(nextDisplayMode); + isDisplayModeModified = true; + } else { + isDisplayModeModified = false; + } + } else { + isDisplayModeModified = false; + // Resize the canvas if the display mode cannot be changed + // and the screen size is not equal to the canvas size + final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + if (screenSize.width != settings.getWidth() || screenSize.height != settings.getHeight()) { + canvas.setSize(screenSize); + } + } + } else { + isDisplayModeModified = false; + } + + // Software windowed full-screen mode + if (!isFullScreenSupported) { + final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + // Resize the canvas + canvas.setSize(screenSize); + // Resize the frame so that it occupies the whole screen + frame.setSize(screenSize); + // Set its location at the top left corner + frame.setLocation(0, 0); + } + } + // Otherwise, center the window on the screen. + else { + isDisplayModeModified = false; + frame.pack(); + + int x, y; + x = (Toolkit.getDefaultToolkit().getScreenSize().width - settings.getWidth()) / 2; + y = (Toolkit.getDefaultToolkit().getScreenSize().height - settings.getHeight()) / 2; + frame.setLocation(x, y); + } + + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent evt) { + // If required, restore the previous display mode + if (isDisplayModeModified) { + gd.setDisplayMode(previousDisplayMode); + } + // If required, get back to the windowed mode + if (gd.getFullScreenWindow() == frame) { + gd.setFullScreenWindow(null); + } + windowCloseRequest.set(true); + } + @Override + public void windowActivated(WindowEvent evt) { + active.set(true); + } + + @Override + public void windowDeactivated(WindowEvent evt) { + active.set(false); + } + }); + + logger.log(Level.FINE, "Selected display mode: {0}x{1}x{2} @{3}", + new Object[]{gd.getDisplayMode().getWidth(), + gd.getDisplayMode().getHeight(), + gd.getDisplayMode().getBitDepth(), + gd.getDisplayMode().getRefreshRate()}); + } + + private void initInEDT(){ + initGLCanvas(); + + createGLFrame(); + + startGLCanvas(); + } + + public void init(GLAutoDrawable drawable){ + // prevent initializing twice on restart + if (!wasInited){ + wasInited = true; + + canvas.requestFocus(); + + super.internalCreate(); + logger.fine("Display created."); + + renderer.initialize(); + listener.initialize(); + } + } + + public void create(boolean waitFor){ + if (SwingUtilities.isEventDispatchThread()) { + initInEDT(); + } else { + 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.fine("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/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java new file mode 100644 index 000000000..c8ce3dc81 --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.jogl; + +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; +import com.jme3.input.jogl.NewtKeyInput; +import com.jme3.input.jogl.NewtMouseInput; +import com.jme3.renderer.jogl.JoglRenderer; +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.opengl.util.Animator; +import com.jogamp.opengl.util.AnimatorBase; +import com.jogamp.opengl.util.FPSAnimator; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; +import javax.media.opengl.DebugGL2; +import javax.media.opengl.DebugGL3; +import javax.media.opengl.DebugGL3bc; +import javax.media.opengl.DebugGL4; +import javax.media.opengl.DebugGL4bc; +import javax.media.opengl.DebugGLES1; +import javax.media.opengl.DebugGLES2; +import javax.media.opengl.GL; +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLEventListener; +import javax.media.opengl.GLProfile; +import javax.media.opengl.GLRunnable; + +public abstract class JoglNewtAbstractDisplay extends JoglContext implements GLEventListener { + + private static final Logger logger = Logger.getLogger(JoglNewtAbstractDisplay.class.getName()); + + protected GLWindow 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; + + protected void initGLCanvas() { + //FIXME use the settings to know whether to use the max programmable profile + //then call GLProfile.getMaxProgrammable(true); + //FIXME use the default profile only on embedded devices + 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 = GLWindow.create(caps); + canvas.invoke(false, new GLRunnable() { + public boolean run(GLAutoDrawable glad) { + canvas.getGL().setSwapInterval(settings.isVSync() ? 1 : 0); + return true; + } + }); + canvas.requestFocus(); + canvas.setSize(settings.getWidth(), settings.getHeight()); + canvas.addGLEventListener(this); + + if (settings.getBoolean("GraphicsDebug")) { + canvas.invoke(false, new GLRunnable() { + public boolean run(GLAutoDrawable glad) { + GL gl = glad.getGL(); + if (gl.isGLES()) { + if (gl.isGLES1()) { + glad.setGL(new DebugGLES1(gl.getGLES1())); + } else { + if (gl.isGLES2()) { + glad.setGL(new DebugGLES2(gl.getGLES2())); + } else { + // TODO ES3 + } + } + } else { + if (gl.isGL4bc()) { + glad.setGL(new DebugGL4bc(gl.getGL4bc())); + } else { + if (gl.isGL4()) { + glad.setGL(new DebugGL4(gl.getGL4())); + } else { + if (gl.isGL3bc()) { + glad.setGL(new DebugGL3bc(gl.getGL3bc())); + } else { + if (gl.isGL3()) { + glad.setGL(new DebugGL3(gl.getGL3())); + } else { + if (gl.isGL2()) { + glad.setGL(new DebugGL2(gl.getGL2())); + } + } + } + } + } + } + return true; + } + }); + } + + renderer = new JoglRenderer(); + } + + protected void startGLCanvas() { + if (frameRate > 0) { + animator = new FPSAnimator(canvas, frameRate); + } + else { + animator = new Animator(); + animator.add(canvas); + ((Animator) animator).setRunAsFastAsPossible(true); + } + + animator.start(); + wasAnimating = true; + + //FIXME not sure it is the best place to do that + renderable.set(true); + } + + protected void onCanvasAdded() { + } + + protected void onCanvasRemoved() { + } + + @Override + public KeyInput getKeyInput() { + if (keyInput == null) { + keyInput = new NewtKeyInput(); + ((NewtKeyInput)keyInput).setInputSource(canvas); + } + return keyInput; + } + + @Override + public MouseInput getMouseInput() { + if (mouseInput == null) { + mouseInput = new NewtMouseInput(); + ((NewtMouseInput)mouseInput).setInputSource(canvas); + } + return mouseInput; + } + + public TouchInput getTouchInput() { + return null; + } + + 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/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtCanvas.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtCanvas.java new file mode 100644 index 000000000..6b6824938 --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtCanvas.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.jogl; + +import com.jme3.system.JmeCanvasContext; +import com.jogamp.newt.awt.NewtCanvasAWT; +import java.util.logging.Logger; +import javax.media.opengl.GLAutoDrawable; + +public class JoglNewtCanvas extends JoglNewtAbstractDisplay implements JmeCanvasContext { + + private static final Logger logger = Logger.getLogger(JoglNewtCanvas.class.getName()); + private int width, height; + + private NewtCanvasAWT newtAwtCanvas; + + public JoglNewtCanvas(){ + super(); + initGLCanvas(); + } + + @Override + protected final void initGLCanvas() { + super.initGLCanvas(); + newtAwtCanvas = new NewtCanvasAWT(canvas) { + @Override + public void addNotify() { + super.addNotify(); + onCanvasAdded(); + } + + @Override + public void removeNotify() { + onCanvasRemoved(); + super.removeNotify(); + } + }; + } + + 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.fine("Display created."); + + renderer.initialize(); + listener.initialize(); + } + + public void display(GLAutoDrawable glad) { + if (!created.get() && renderer != null){ + listener.destroy(); + logger.fine("Canvas destroyed."); + super.internalDestroy(); + return; + } + + int newWidth = Math.max(canvas.getWidth(), 1); + int newHeight = Math.max(canvas.getHeight(), 1); + if (width != newWidth || height != newHeight) { + width = newWidth; + height = newHeight; + if (listener != null) { + listener.reshape(width, height); + } + } + + boolean flush = autoFlush.get(); + if (flush && !wasAnimating){ + animator.start(); + wasAnimating = true; + }else if (!flush && wasAnimating){ + animator.stop(); + wasAnimating = false; + } + + listener.update(); + renderer.onFrame(); + + } + + @Override + public NewtCanvasAWT getCanvas() { + return newtAwtCanvas; + } + + @Override + public void dispose(GLAutoDrawable arg0) { + } + +} diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtDisplay.java new file mode 100644 index 000000000..0eb2d842e --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtDisplay.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.jogl; + +import com.jme3.system.AppSettings; +import com.jogamp.newt.MonitorMode; +import com.jogamp.newt.Screen; +import com.jogamp.newt.event.WindowAdapter; +import com.jogamp.newt.event.WindowEvent; +import com.jogamp.newt.util.MonitorModeUtil; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.media.nativewindow.util.Dimension; +import javax.media.opengl.GLAutoDrawable; + +public class JoglNewtDisplay extends JoglNewtAbstractDisplay { + + private static final Logger logger = Logger.getLogger(JoglNewtDisplay.class.getName()); + + protected AtomicBoolean windowCloseRequest = new AtomicBoolean(false); + protected AtomicBoolean needClose = new AtomicBoolean(false); + protected AtomicBoolean needRestart = new AtomicBoolean(false); + protected volatile boolean wasInited = false; + + public Type getType() { + return Type.Display; + } + + protected void createGLFrame(){ + canvas.setTitle(settings.getTitle()); + + applySettings(settings); + + // Make the window visible to realize the OpenGL surface. + canvas.setVisible(true); + + //this is the earliest safe opportunity to get the context + //final GLContext context = canvas.getContext(); + + /*canvas.invoke(true, new GLRunnable() { + @Override + public boolean run(GLAutoDrawable glAutoDrawable) { + context.makeCurrent(); + try { + startGLCanvas(); + } + finally { + context.release(); + } + return true; + } + });*/ + } + + protected void applySettings(AppSettings settings){ + active.set(true); + canvas.addWindowListener(new WindowAdapter() { + @Override + public void windowDestroyNotify(WindowEvent e) { + windowCloseRequest.set(true); + } + + @Override + public void windowGainedFocus(WindowEvent e) { + active.set(true); + } + + @Override + public void windowLostFocus(WindowEvent e) { + active.set(false); + } + }); + canvas.setSize(settings.getWidth(), settings.getHeight()); + canvas.setUndecorated(settings.isFullscreen()); + canvas.setFullscreen(settings.isFullscreen()); + + /** + * uses the filtering relying on resolution with the size to fetch only + * the screen mode matching with the current resolution + */ + Screen screen = canvas.getScreen(); + /** + * The creation of native resources is lazy in JogAmp, i.e they are + * created only when they are used for the first time. When the GLWindow + * is not yet visible, its screen might have been unused for now and + * then its native counterpart has not yet been created. That's why + * forcing the creation of this resource is necessary + */ + screen.addReference(); + if (settings.isFullscreen()) { + List screenModes = canvas.getMainMonitor().getSupportedModes(); + //the resolution is provided by the user + Dimension dimension = new Dimension(settings.getWidth(), settings.getHeight()); + screenModes = MonitorModeUtil.filterByResolution(screenModes, dimension); + screenModes = MonitorModeUtil.getHighestAvailableBpp(screenModes); + if (settings.getFrequency() > 0) { + screenModes = MonitorModeUtil.filterByRate(screenModes, settings.getFrequency()); + } else { + screenModes = MonitorModeUtil.getHighestAvailableRate(screenModes); + } + canvas.getMainMonitor().setCurrentMode(screenModes.get(0)); + } + + MonitorMode currentScreenMode = canvas.getMainMonitor().getCurrentMode(); + logger.log(Level.FINE, "Selected display mode: {0}x{1}x{2} @{3}", + new Object[]{currentScreenMode.getRotatedWidth(), + currentScreenMode.getRotatedHeight(), + currentScreenMode.getSurfaceSize().getBitsPerPixel(), + currentScreenMode.getRefreshRate()}); + } + + private void privateInit(){ + initGLCanvas(); + + createGLFrame(); + + startGLCanvas(); + } + + public void init(GLAutoDrawable drawable){ + // prevent initializing twice on restart + if (!wasInited){ + wasInited = true; + + canvas.requestFocus(); + + super.internalCreate(); + logger.fine("Display created."); + + renderer.initialize(); + listener.initialize(); + } + } + + public void create(boolean waitFor){ + privateInit(); + } + + 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 (canvas != null) { + canvas.setTitle(title); + } + } + + /** + * Callback. + */ + public void display(GLAutoDrawable drawable) { + if (needClose.get()) { + listener.destroy(); + animator.stop(); + if (settings.isFullscreen()) { + canvas.setFullscreen(false); + } + canvas.destroy(); + logger.fine("Display destroyed."); + super.internalDestroy(); + return; + } + + if (windowCloseRequest.get()){ + listener.requestClose(false); + windowCloseRequest.set(false); + } + + if (needRestart.getAndSet(false)){ + // for restarting contexts + if (canvas.isVisible()){ + animator.stop(); + canvas.destroy(); + 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/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java new file mode 100644 index 000000000..7f10c7161 --- /dev/null +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system.jogl; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; +import com.jme3.input.dummy.DummyKeyInput; +import com.jme3.input.dummy.DummyMouseInput; +import com.jogamp.newt.NewtVersion; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.media.opengl.GL; +import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLContext; +import javax.media.opengl.GLDrawableFactory; +import javax.media.opengl.GLOffscreenAutoDrawable; +import javax.media.opengl.GLProfile; + + +public class JoglOffscreenBuffer extends JoglContext implements Runnable { + + private static final Logger logger = Logger.getLogger(JoglOffscreenBuffer.class.getName()); + private GLOffscreenAutoDrawable offscreenDrawable; + protected AtomicBoolean needClose = new AtomicBoolean(false); + private int width; + private int height; + private GLCapabilities caps; + + protected void initInThread(){ + GL gl = GLContext.getCurrentGL(); + if (!gl.hasFullFBOSupport()){ + logger.severe("Offscreen surfaces are not supported."); + return; + } + + int samples = getNumSamplesToUse(); + caps = new GLCapabilities(GLProfile.getMaxFixedFunc(true)); + caps.setHardwareAccelerated(true); + caps.setDoubleBuffered(true); + caps.setStencilBits(settings.getStencilBits()); + caps.setDepthBits(settings.getDepthBits()); + caps.setOnscreen(false); + caps.setSampleBuffers(true); + caps.setNumSamples(samples); + + offscreenDrawable = GLDrawableFactory.getFactory(GLProfile.getMaxFixedFunc(true)).createOffscreenAutoDrawable(null, caps, null, width, height, null); + + offscreenDrawable.display(); + + renderable.set(true); + + logger.fine("Offscreen buffer created."); + + super.internalCreate(); + listener.initialize(); + } + + protected boolean checkGLError(){ + //FIXME + // NOTE: Always return true since this is used in an "assert" statement + return true; + } + + protected void runLoop(){ + if (!created.get()) { + throw new IllegalStateException(); + } + + listener.update(); + checkGLError(); + + renderer.onFrame(); + + int frameRate = settings.getFrameRate(); + if (frameRate >= 1) { + //FIXME + } + } + + protected void deinitInThread(){ + renderable.set(false); + + listener.destroy(); + renderer.cleanup(); + offscreenDrawable.destroy(); + logger.fine("Offscreen buffer destroyed."); + + super.internalDestroy(); + } + + public void run(){ + logger.log(Level.FINE, "Using JOGL {0}", NewtVersion.getInstance().getImplementationVersion()); + 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, "JOGL Renderer Thread").start(); + if (waitFor) { + waitFor(true); + } + } + + public void restart() { + } + + public void setAutoFlushFrames(boolean enabled){ + } + + public Type getType() { + return Type.OffscreenSurface; + } + + @Override + public MouseInput getMouseInput() { + return new DummyMouseInput(); + } + + @Override + public KeyInput getKeyInput() { + return new DummyKeyInput(); + } + + @Override + public JoyInput getJoyInput() { + return null; + } + + public TouchInput getTouchInput() { + return null; + } + + public void setTitle(String title) { + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglAudioRenderer.java b/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglAudioRenderer.java new file mode 100644 index 000000000..dc8628975 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglAudioRenderer.java @@ -0,0 +1,1080 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio.lwjgl; + +import com.jme3.audio.AudioSource.Status; +import com.jme3.audio.*; +import com.jme3.math.Vector3f; +import com.jme3.util.BufferUtils; +import com.jme3.util.NativeObjectManager; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.lwjgl.LWJGLException; +import static org.lwjgl.openal.AL10.*; +import org.lwjgl.openal.*; + +public class LwjglAudioRenderer implements AudioRenderer, Runnable { + + private static final Logger logger = Logger.getLogger(LwjglAudioRenderer.class.getName()); + private final NativeObjectManager objManager = new NativeObjectManager(); + // When multiplied by STREAMING_BUFFER_COUNT, will equal 44100 * 2 * 2 + // which is exactly 1 second of audio. + private static final int BUFFER_SIZE = 35280; + private static final int STREAMING_BUFFER_COUNT = 5; + private final static int MAX_NUM_CHANNELS = 64; + private IntBuffer ib = BufferUtils.createIntBuffer(1); + private final FloatBuffer fb = BufferUtils.createVector3Buffer(2); + private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE); + private final byte[] arrayBuf = new byte[BUFFER_SIZE]; + private int[] channels; + private AudioSource[] 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; + // Update audio 20 times per second + private static final float UPDATE_RATE = 0.05f; + private final Thread audioThread = new Thread(this, "jME3 Audio Thread"); + private final AtomicBoolean threadLock = new AtomicBoolean(false); + + public LwjglAudioRenderer() { + } + + public void initialize() { + if (!audioThread.isAlive()) { + audioThread.setDaemon(true); + audioThread.setPriority(Thread.NORM_PRIORITY + 1); + audioThread.start(); + } else { + throw new IllegalStateException("Initialize already called"); + } + } + + private void checkDead() { + if (audioThread.getState() == Thread.State.TERMINATED) { + throw new IllegalStateException("Audio thread is terminated"); + } + } + + public void run() { + initInThread(); + synchronized (threadLock) { + threadLock.set(true); + threadLock.notifyAll(); + } + + long updateRateNanos = (long) (UPDATE_RATE * 1000000000); + mainloop: + while (true) { + long startTime = System.nanoTime(); + + if (Thread.interrupted()) { + break; + } + + synchronized (threadLock) { + updateInThread(UPDATE_RATE); + } + + long endTime = System.nanoTime(); + long diffTime = endTime - startTime; + + if (diffTime < updateRateNanos) { + long desiredEndTime = startTime + updateRateNanos; + while (System.nanoTime() < desiredEndTime) { + try { + Thread.sleep(1); + } catch (InterruptedException ex) { + break mainloop; + } + } + } + } + + synchronized (threadLock) { + cleanupInThread(); + } + } + + public void initInThread() { + try { + if (!AL.isCreated()) { + AL.create(); + } + } catch (OpenALException ex) { + logger.log(Level.SEVERE, "Failed to load audio library", ex); + audioDisabled = true; + return; + } catch (LWJGLException ex) { + logger.log(Level.SEVERE, "Failed to load audio library", ex); + audioDisabled = true; + return; + } catch (UnsatisfiedLinkError ex) { + logger.log(Level.SEVERE, "Failed to load audio library", ex); + audioDisabled = true; + return; + } + + ALCdevice device = AL.getDevice(); + String deviceName = ALC10.alcGetString(device, ALC10.ALC_DEVICE_SPECIFIER); + + logger.log(Level.INFO, "Audio Device: {0}", deviceName); + logger.log(Level.INFO, "Audio Vendor: {0}", alGetString(AL_VENDOR)); + logger.log(Level.INFO, "Audio Renderer: {0}", alGetString(AL_RENDERER)); + logger.log(Level.INFO, "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 AudioSource[channels.length]; + + logger.log(Level.INFO, "AudioRenderer supports {0} channels", channels.length); + + supportEfx = ALC10.alcIsExtensionPresent(device, "ALC_EXT_EFX"); + if (supportEfx) { + ib.position(0).limit(1); + ALC10.alcGetInteger(device, EFX10.ALC_EFX_MAJOR_VERSION, ib); + int major = ib.get(0); + ib.position(0).limit(1); + ALC10.alcGetInteger(device, EFX10.ALC_EFX_MINOR_VERSION, ib); + int minor = ib.get(0); + logger.log(Level.INFO, "Audio effect extension version: {0}.{1}", new Object[]{major, minor}); + + ALC10.alcGetInteger(device, EFX10.ALC_MAX_AUXILIARY_SENDS, ib); + auxSends = ib.get(0); + logger.log(Level.INFO, "Audio max auxilary sends: {0}", auxSends); + + // create slot + ib.position(0).limit(1); + EFX10.alGenAuxiliaryEffectSlots(ib); + reverbFxSlot = ib.get(0); + + // create effect + ib.position(0).limit(1); + EFX10.alGenEffects(ib); + reverbFx = ib.get(0); + EFX10.alEffecti(reverbFx, EFX10.AL_EFFECT_TYPE, EFX10.AL_EFFECT_REVERB); + + // attach reverb effect to effect slot + EFX10.alAuxiliaryEffectSloti(reverbFxSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbFx); + } else { + logger.log(Level.WARNING, "OpenAL EFX not available! Audio effects won't work."); + } + } + + public void cleanupInThread() { + if (audioDisabled) { + AL.destroy(); + return; + } + + // stop any playing channels + for (int i = 0; i < chanSrcs.length; i++) { + if (chanSrcs[i] != null) { + clearChannel(i); + } + } + + // delete channel-based sources + ib.clear(); + ib.put(channels); + ib.flip(); + alDeleteSources(ib); + + // delete audio buffers and filters + objManager.deleteAllObjects(this); + + if (supportEfx) { + ib.position(0).limit(1); + ib.put(0, reverbFx); + EFX10.alDeleteEffects(ib); + + // If this is not allocated, why is it deleted? + // Commented out to fix native crash in OpenAL. + ib.position(0).limit(1); + ib.put(0, reverbFxSlot); + EFX10.alDeleteAuxiliaryEffectSlots(ib); + } + + AL.destroy(); + } + + public void cleanup() { + // kill audio thread + if (audioThread.isAlive()) { + audioThread.interrupt(); + } + } + + private void updateFilter(Filter f) { + int id = f.getId(); + if (id == -1) { + ib.position(0).limit(1); + EFX10.alGenFilters(ib); + id = ib.get(0); + f.setId(id); + + objManager.registerObject(f); + } + + if (f instanceof LowPassFilter) { + LowPassFilter lpf = (LowPassFilter) f; + EFX10.alFilteri(id, EFX10.AL_FILTER_TYPE, EFX10.AL_FILTER_LOWPASS); + EFX10.alFilterf(id, EFX10.AL_LOWPASS_GAIN, lpf.getVolume()); + EFX10.alFilterf(id, EFX10.AL_LOWPASS_GAINHF, lpf.getHighFreqVolume()); + } else { + throw new UnsupportedOperationException("Filter type unsupported: " + + f.getClass().getName()); + } + + f.clearUpdateNeeded(); + } + + public void updateSourceParam(AudioSource src, AudioParam param) { + checkDead(); + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled) { + return; + } + + // There is a race condition in AudioSource that can + // cause this to be called for a node that has been + // detached from its channel. For example, setVolume() + // called from the render thread may see that that AudioSource + // still has a channel value but the audio thread may + // clear that channel before setVolume() gets to call + // updateSourceParam() (because the audio stopped playing + // on its own right as the volume was set). In this case, + // it should be safe to just ignore the update + if (src.getChannel() < 0) { + return; + } + + assert src.getChannel() >= 0; + + int id = channels[src.getChannel()]; + switch (param) { + case Position: + if (!src.isPositional()) { + return; + } + + Vector3f pos = src.getPosition(); + alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z); + break; + case Velocity: + if (!src.isPositional()) { + return; + } + + Vector3f vel = src.getVelocity(); + alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z); + break; + case MaxDistance: + if (!src.isPositional()) { + return; + } + + alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance()); + break; + case RefDistance: + if (!src.isPositional()) { + return; + } + + alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance()); + break; + case ReverbFilter: + if (!supportEfx || !src.isPositional() || !src.isReverbEnabled()) { + return; + } + + int filter = EFX10.AL_FILTER_NULL; + if (src.getReverbFilter() != null) { + Filter f = src.getReverbFilter(); + if (f.isUpdateNeeded()) { + updateFilter(f); + } + filter = f.getId(); + } + AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); + break; + case ReverbEnabled: + if (!supportEfx || !src.isPositional()) { + return; + } + + if (src.isReverbEnabled()) { + updateSourceParam(src, AudioParam.ReverbFilter); + } else { + AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL); + } + break; + case IsPositional: + if (!src.isPositional()) { + // Play in headspace + alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(id, AL_POSITION, 0, 0, 0); + alSource3f(id, AL_VELOCITY, 0, 0, 0); + + // Disable reverb + AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL); + } else { + alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE); + updateSourceParam(src, AudioParam.Position); + updateSourceParam(src, AudioParam.Velocity); + updateSourceParam(src, AudioParam.MaxDistance); + updateSourceParam(src, AudioParam.RefDistance); + updateSourceParam(src, AudioParam.ReverbEnabled); + } + break; + case Direction: + if (!src.isDirectional()) { + return; + } + + Vector3f dir = src.getDirection(); + alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z); + break; + case InnerAngle: + if (!src.isDirectional()) { + return; + } + + alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle()); + break; + case OuterAngle: + if (!src.isDirectional()) { + return; + } + + alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); + break; + case IsDirectional: + if (src.isDirectional()) { + updateSourceParam(src, AudioParam.Direction); + updateSourceParam(src, AudioParam.InnerAngle); + updateSourceParam(src, AudioParam.OuterAngle); + alSourcef(id, AL_CONE_OUTER_GAIN, 0); + } else { + alSourcef(id, AL_CONE_INNER_ANGLE, 360); + alSourcef(id, AL_CONE_OUTER_ANGLE, 360); + alSourcef(id, AL_CONE_OUTER_GAIN, 1f); + } + break; + case DryFilter: + if (!supportEfx) { + return; + } + + if (src.getDryFilter() != null) { + Filter f = src.getDryFilter(); + if (f.isUpdateNeeded()) { + updateFilter(f); + + // NOTE: must re-attach filter for changes to apply. + alSourcei(id, EFX10.AL_DIRECT_FILTER, f.getId()); + } + } else { + alSourcei(id, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL); + } + break; + case Looping: + if (src.isLooping()) { + if (!(src.getAudioData() instanceof AudioStream)) { + alSourcei(id, AL_LOOPING, AL_TRUE); + } + } else { + alSourcei(id, AL_LOOPING, AL_FALSE); + } + break; + case Volume: + alSourcef(id, AL_GAIN, src.getVolume()); + break; + case Pitch: + alSourcef(id, AL_PITCH, src.getPitch()); + break; + } + } + } + + private void setSourceParams(int id, AudioSource src, boolean forceNonLoop) { + if (src.isPositional()) { + Vector3f pos = src.getPosition(); + Vector3f vel = src.getVelocity(); + alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z); + alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z); + alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance()); + alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance()); + alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE); + + if (src.isReverbEnabled() && supportEfx) { + int filter = EFX10.AL_FILTER_NULL; + if (src.getReverbFilter() != null) { + Filter f = src.getReverbFilter(); + if (f.isUpdateNeeded()) { + updateFilter(f); + } + filter = f.getId(); + } + AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); + } + } else { + // play in headspace + alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(id, AL_POSITION, 0, 0, 0); + alSource3f(id, AL_VELOCITY, 0, 0, 0); + } + + if (src.getDryFilter() != null && supportEfx) { + Filter f = src.getDryFilter(); + if (f.isUpdateNeeded()) { + updateFilter(f); + + // NOTE: must re-attach filter for changes to apply. + alSourcei(id, EFX10.AL_DIRECT_FILTER, f.getId()); + } + } + + if (forceNonLoop) { + alSourcei(id, AL_LOOPING, AL_FALSE); + } else { + alSourcei(id, AL_LOOPING, src.isLooping() ? AL_TRUE : AL_FALSE); + } + alSourcef(id, AL_GAIN, src.getVolume()); + alSourcef(id, AL_PITCH, src.getPitch()); + alSourcef(id, AL11.AL_SEC_OFFSET, src.getTimeOffset()); + + if (src.isDirectional()) { + Vector3f dir = src.getDirection(); + alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z); + alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle()); + alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); + alSourcef(id, AL_CONE_OUTER_GAIN, 0); + } else { + alSourcef(id, AL_CONE_INNER_ANGLE, 360); + alSourcef(id, AL_CONE_OUTER_ANGLE, 360); + alSourcef(id, AL_CONE_OUTER_GAIN, 1f); + } + } + + public void updateListenerParam(Listener listener, ListenerParam param) { + checkDead(); + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled) { + return; + } + + switch (param) { + case Position: + Vector3f pos = listener.getLocation(); + alListener3f(AL_POSITION, pos.x, pos.y, pos.z); + break; + case Rotation: + Vector3f dir = listener.getDirection(); + Vector3f up = listener.getUp(); + fb.rewind(); + fb.put(dir.x).put(dir.y).put(dir.z); + fb.put(up.x).put(up.y).put(up.z); + fb.flip(); + alListener(AL_ORIENTATION, fb); + break; + case Velocity: + Vector3f vel = listener.getVelocity(); + alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); + break; + case Volume: + alListenerf(AL_GAIN, listener.getVolume()); + break; + } + } + } + + private void setListenerParams(Listener listener) { + Vector3f pos = listener.getLocation(); + Vector3f vel = listener.getVelocity(); + Vector3f dir = listener.getDirection(); + Vector3f up = listener.getUp(); + + alListener3f(AL_POSITION, pos.x, pos.y, pos.z); + alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); + fb.rewind(); + fb.put(dir.x).put(dir.y).put(dir.z); + fb.put(up.x).put(up.y).put(up.z); + fb.flip(); + alListener(AL_ORIENTATION, fb); + alListenerf(AL_GAIN, listener.getVolume()); + } + + private int newChannel() { + if (freeChans.size() > 0) { + return freeChans.remove(0); + } else if (nextChan < channels.length) { + return nextChan++; + } else { + return -1; + } + } + + private void freeChannel(int index) { + if (index == nextChan - 1) { + nextChan--; + } else { + freeChans.add(index); + } + } + + public void setEnvironment(Environment env) { + checkDead(); + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled || !supportEfx) { + return; + } + + EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DENSITY, env.getDensity()); + EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DIFFUSION, env.getDiffusion()); + EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_GAIN, env.getGain()); + EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_GAINHF, env.getGainHf()); + EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DECAY_TIME, env.getDecayTime()); + EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_DECAY_HFRATIO, env.getDecayHFRatio()); + EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_REFLECTIONS_GAIN, env.getReflectGain()); + EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_REFLECTIONS_DELAY, env.getReflectDelay()); + EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_LATE_REVERB_GAIN, env.getLateReverbGain()); + EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_LATE_REVERB_DELAY, env.getLateReverbDelay()); + EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_AIR_ABSORPTION_GAINHF, env.getAirAbsorbGainHf()); + EFX10.alEffectf(reverbFx, EFX10.AL_REVERB_ROOM_ROLLOFF_FACTOR, env.getRoomRolloffFactor()); + + // attach effect to slot + EFX10.alAuxiliaryEffectSloti(reverbFxSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbFx); + } + } + + private boolean fillBuffer(AudioStream stream, int id) { + int size = 0; + int result; + + while (size < arrayBuf.length) { + result = stream.readSamples(arrayBuf, size, arrayBuf.length - size); + + if (result > 0) { + size += result; + } else { + break; + } + } + + if (size == 0) { + return false; + } + + nativeBuf.clear(); + nativeBuf.put(arrayBuf, 0, size); + nativeBuf.flip(); + + alBufferData(id, convertFormat(stream), nativeBuf, stream.getSampleRate()); + + return true; + } + + private boolean fillStreamingSource(int sourceId, AudioStream stream) { + if (!stream.isOpen()) { + return false; + } + + boolean active = true; + int processed = alGetSourcei(sourceId, AL_BUFFERS_PROCESSED); + +// while((processed--) != 0){ + if (processed > 0) { + int buffer; + + ib.position(0).limit(1); + alSourceUnqueueBuffers(sourceId, ib); + buffer = ib.get(0); + + active = fillBuffer(stream, buffer); + + ib.position(0).limit(1); + ib.put(0, buffer); + alSourceQueueBuffers(sourceId, ib); + } + + if (!active && stream.isOpen()) { + stream.close(); + } + + return active; + } + + private boolean attachStreamToSource(int sourceId, AudioStream stream) { + boolean active = true; + for (int id : stream.getIds()) { + active = fillBuffer(stream, id); + ib.position(0).limit(1); + ib.put(id).flip(); + alSourceQueueBuffers(sourceId, ib); + } + return active; + } + + private boolean attachBufferToSource(int sourceId, AudioBuffer buffer) { + alSourcei(sourceId, AL_BUFFER, buffer.getId()); + return true; + } + + private boolean attachAudioToSource(int sourceId, AudioData data) { + if (data instanceof AudioBuffer) { + return attachBufferToSource(sourceId, (AudioBuffer) data); + } else if (data instanceof AudioStream) { + return attachStreamToSource(sourceId, (AudioStream) data); + } + throw new UnsupportedOperationException(); + } + + private void clearChannel(int index) { + // make room at this channel + if (chanSrcs[index] != null) { + AudioSource src = chanSrcs[index]; + + int sourceId = channels[index]; + alSourceStop(sourceId); + + if (src.getAudioData() instanceof AudioStream) { + AudioStream str = (AudioStream) src.getAudioData(); + ib.position(0).limit(STREAMING_BUFFER_COUNT); + ib.put(str.getIds()).flip(); + alSourceUnqueueBuffers(sourceId, ib); + } else if (src.getAudioData() instanceof AudioBuffer) { + alSourcei(sourceId, AL_BUFFER, 0); + } + + if (src.getDryFilter() != null && supportEfx) { + // detach filter + alSourcei(sourceId, EFX10.AL_DIRECT_FILTER, EFX10.AL_FILTER_NULL); + } + if (src.isPositional()) { + AudioSource pas = (AudioSource) src; + if (pas.isReverbEnabled() && supportEfx) { + AL11.alSource3i(sourceId, EFX10.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX10.AL_FILTER_NULL); + } + } + + chanSrcs[index] = null; + } + } + + public void update(float tpf) { + // does nothing + } + + public void updateInThread(float tpf) { + if (audioDisabled) { + return; + } + + for (int i = 0; i < channels.length; i++) { + AudioSource src = chanSrcs[i]; + if (src == null) { + continue; + } + + int sourceId = channels[i]; + + // is the source bound to this channel + // if false, it's an instanced playback + boolean boundSource = i == src.getChannel(); + + // source's data is streaming + boolean streaming = src.getAudioData() instanceof AudioStream; + + // only buffered sources can be bound + assert (boundSource && streaming) || (!streaming); + + int state = alGetSourcei(sourceId, AL_SOURCE_STATE); + boolean wantPlaying = src.getStatus() == Status.Playing; + boolean stopped = state == AL_STOPPED; + + if (streaming && wantPlaying) { + AudioStream stream = (AudioStream) src.getAudioData(); + if (stream.isOpen()) { + fillStreamingSource(sourceId, stream); + if (stopped) { + alSourcePlay(sourceId); + } + } else { + if (stopped) { + // became inactive + src.setStatus(Status.Stopped); + src.setChannel(-1); + clearChannel(i); + freeChannel(i); + + // And free the audio since it cannot be + // played again anyway. + deleteAudioData(stream); + } + } + } else if (!streaming) { + boolean paused = state == AL_PAUSED; + + // make sure OAL pause state & source state coincide + assert (src.getStatus() == Status.Paused && paused) || (!paused); + + if (stopped) { + if (boundSource) { + src.setStatus(Status.Stopped); + src.setChannel(-1); + } + clearChannel(i); + freeChannel(i); + } + } + } + + // Delete any unused objects. + objManager.deleteUnused(this); + } + + public void setListener(Listener listener) { + checkDead(); + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled) { + return; + } + + if (this.listener != null) { + // previous listener no longer associated with current + // renderer + this.listener.setRenderer(null); + } + + this.listener = listener; + this.listener.setRenderer(this); + setListenerParams(listener); + } + } + + public void playSourceInstance(AudioSource 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(AudioSource src) { + checkDead(); + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled) { + return; + } + + //assert src.getStatus() == Status.Stopped || src.getChannel() == -1; + + if (src.getStatus() == Status.Playing) { + return; + } else if (src.getStatus() == Status.Stopped) { + + // allocate channel to this source + int index = newChannel(); + if (index == -1) { + logger.log(Level.WARNING, "No channel available to play {0}", src); + return; + } + clearChannel(index); + src.setChannel(index); + + AudioData data = src.getAudioData(); + if (data.isUpdateNeeded()) { + updateAudioData(data); + } + + chanSrcs[index] = src; + setSourceParams(channels[index], src, false); + attachAudioToSource(channels[index], data); + } + + alSourcePlay(channels[src.getChannel()]); + src.setStatus(Status.Playing); + } + } + + public void pauseSource(AudioSource 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(AudioSource src) { + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled) { + return; + } + + if (src.getStatus() != Status.Stopped) { + int chan = src.getChannel(); + assert chan != -1; // if it's not stopped, must have id + + src.setStatus(Status.Stopped); + src.setChannel(-1); + clearChannel(chan); + freeChannel(chan); + + if (src.getAudioData() instanceof AudioStream) { + AudioStream stream = (AudioStream) src.getAudioData(); + if (stream.isOpen()) { + stream.close(); + } + + // And free the audio since it cannot be + // played again anyway. + deleteAudioData(src.getAudioData()); + } + } + } + } + + private int convertFormat(AudioData ad) { + switch (ad.getBitsPerSample()) { + case 8: + if (ad.getChannels() == 1) { + return AL_FORMAT_MONO8; + } else if (ad.getChannels() == 2) { + return AL_FORMAT_STEREO8; + } + + break; + case 16: + if (ad.getChannels() == 1) { + return AL_FORMAT_MONO16; + } else { + return AL_FORMAT_STEREO16; + } + } + throw new UnsupportedOperationException("Unsupported channels/bits combination: " + + "bits=" + ad.getBitsPerSample() + ", channels=" + ad.getChannels()); + } + + private void updateAudioBuffer(AudioBuffer ab) { + int id = ab.getId(); + if (ab.getId() == -1) { + ib.position(0).limit(1); + alGenBuffers(ib); + id = ib.get(0); + ab.setId(id); + + objManager.registerObject(ab); + } + + ab.getData().clear(); + alBufferData(id, convertFormat(ab), ab.getData(), ab.getSampleRate()); + ab.clearUpdateNeeded(); + } + + private void updateAudioStream(AudioStream as) { + if (as.getIds() != null) { + deleteAudioData(as); + } + + int[] ids = new int[STREAMING_BUFFER_COUNT]; + ib.position(0).limit(STREAMING_BUFFER_COUNT); + alGenBuffers(ib); + ib.position(0).limit(STREAMING_BUFFER_COUNT); + ib.get(ids); + + // Not registered with object manager. + // AudioStreams can be handled without object manager + // since their lifecycle is known to the audio renderer. + + as.setIds(ids); + as.clearUpdateNeeded(); + } + + private void updateAudioData(AudioData ad) { + if (ad instanceof AudioBuffer) { + updateAudioBuffer((AudioBuffer) ad); + } else if (ad instanceof AudioStream) { + updateAudioStream((AudioStream) ad); + } + } + + public void deleteFilter(Filter filter) { + int id = filter.getId(); + if (id != -1) { + EFX10.alDeleteFilters(id); + } + } + + public void deleteAudioData(AudioData ad) { + synchronized (threadLock) { + while (!threadLock.get()) { + try { + threadLock.wait(); + } catch (InterruptedException ex) { + } + } + if (audioDisabled) { + return; + } + + if (ad instanceof AudioBuffer) { + AudioBuffer ab = (AudioBuffer) ad; + int id = ab.getId(); + if (id != -1) { + ib.put(0, id); + ib.position(0).limit(1); + alDeleteBuffers(ib); + ab.resetObject(); + } + } else if (ad instanceof AudioStream) { + AudioStream as = (AudioStream) ad; + int[] ids = as.getIds(); + if (ids != null) { + ib.clear(); + ib.put(ids).flip(); + alDeleteBuffers(ib); + as.resetObject(); + } + } + } + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/JInputJoyInput.java b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/JInputJoyInput.java new file mode 100644 index 000000000..606ebec87 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/JInputJoyInput.java @@ -0,0 +1,310 @@ +package com.jme3.input.lwjgl; + +import com.jme3.input.AbstractJoystick; +import com.jme3.input.DefaultJoystickAxis; +import com.jme3.input.DefaultJoystickButton; +import com.jme3.input.InputManager; +import com.jme3.input.JoyInput; +import com.jme3.input.Joystick; +import com.jme3.input.JoystickAxis; +import com.jme3.input.JoystickButton; +import com.jme3.input.JoystickCompatibilityMappings; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.JoyAxisEvent; +import com.jme3.input.event.JoyButtonEvent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import net.java.games.input.*; +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; + +public class JInputJoyInput implements JoyInput { + + private static final Logger logger = Logger.getLogger(InputManager.class.getName()); + + private boolean inited = false; + private JInputJoystick[] joysticks; + private RawInputListener listener; + + private Map joystickIndex = new HashMap(); + + public void setJoyRumble(int joyId, float amount){ + + if( joyId >= joysticks.length ) + throw new IllegalArgumentException(); + + Controller c = joysticks[joyId].controller; + for (Rumbler r : c.getRumblers()){ + r.rumble(amount); + } + } + + public Joystick[] loadJoysticks(InputManager inputManager){ + ControllerEnvironment ce = + ControllerEnvironment.getDefaultEnvironment(); + + Controller[] cs = ce.getControllers(); + + List list = new ArrayList(); + for( Controller c : ce.getControllers() ) { + if (c.getType() == Controller.Type.KEYBOARD + || c.getType() == Controller.Type.MOUSE) + continue; + + logger.log(Level.FINE, "Attempting to create joystick for: \"{0}\"", c); + + // Try to create it like a joystick + JInputJoystick stick = new JInputJoystick(inputManager, this, c, list.size(), + c.getName()); + for( Component comp : c.getComponents() ) { + stick.addComponent(comp); + } + + // If it has no axes then we'll assume it's not + // a joystick + if( stick.getAxisCount() == 0 ) { + logger.log(Level.FINE, "Not a joystick: {0}", c); + continue; + } + + joystickIndex.put(c, stick); + list.add(stick); + } + + joysticks = list.toArray( new JInputJoystick[list.size()] ); + + 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]; + + JInputJoystick stick = joystickIndex.get(c); + if( stick == null ) + continue; + + if( !c.poll() ) + continue; + + int joyId = stick.getJoyId(); + + 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(stick.povX, x); + JoyAxisEvent evt2 = new JoyAxisEvent(stick.povY, y); + listener.onJoyAxisEvent(evt1); + listener.onJoyAxisEvent(evt2); + }else if (id instanceof Axis){ + float value = e.getValue(); + + JoystickAxis axis = stick.axisIndex.get(e.getComponent()); + JoyAxisEvent evt = new JoyAxisEvent(axis, value); + listener.onJoyAxisEvent(evt); + }else if (id instanceof Button){ + + JoystickButton button = stick.buttonIndex.get(e.getComponent()); + JoyButtonEvent evt = new JoyButtonEvent(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; + } + + protected class JInputJoystick extends AbstractJoystick { + + private JoystickAxis nullAxis; + private Controller controller; + private JoystickAxis xAxis; + private JoystickAxis yAxis; + private JoystickAxis povX; + private JoystickAxis povY; + private Map axisIndex = new HashMap(); + private Map buttonIndex = new HashMap(); + + public JInputJoystick( InputManager inputManager, JoyInput joyInput, Controller controller, + int joyId, String name ) { + super( inputManager, joyInput, joyId, name ); + + this.controller = controller; + + this.nullAxis = new DefaultJoystickAxis( getInputManager(), this, -1, + "Null", "null", false, false, 0 ); + this.xAxis = nullAxis; + this.yAxis = nullAxis; + this.povX = nullAxis; + this.povY = nullAxis; + } + + protected void addComponent( Component comp ) { + + Identifier id = comp.getIdentifier(); + if( id instanceof Button ) { + addButton(comp); + } else if( id instanceof Axis ) { + addAxis(comp); + } else { + logger.log(Level.FINE, "Ignoring: \"{0}\"", comp); + } + } + + protected void addButton( Component comp ) { + + logger.log(Level.FINE, "Adding button: \"{0}\" id:" + comp.getIdentifier(), comp); + + Identifier id = comp.getIdentifier(); + if( !(id instanceof Button) ) { + throw new IllegalArgumentException( "Component is not an axis:" + comp ); + } + + String name = comp.getName(); + String original = id.getName(); + String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original ); + if( name != original ) { + logger.log(Level.FINE, "Remapped:" + original + " to:" + logicalId); + } + + JoystickButton button = new DefaultJoystickButton( getInputManager(), this, getButtonCount(), + name, logicalId ); + addButton(button); + buttonIndex.put( comp, button ); + } + + protected void addAxis( Component comp ) { + + logger.log(Level.FINE, "Adding axis: \"{0}\" id:" + comp.getIdentifier(), comp ); + + Identifier id = comp.getIdentifier(); + if( !(id instanceof Axis) ) { + throw new IllegalArgumentException( "Component is not an axis:" + comp ); + } + + String name = comp.getName(); + String original = id.getName(); + String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original ); + if( name != original ) { + logger.log(Level.FINE, "Remapped:" + original + " to:" + logicalId); + } + + JoystickAxis axis = new DefaultJoystickAxis( getInputManager(), + this, getAxisCount(), name, logicalId, + comp.isAnalog(), comp.isRelative(), + comp.getDeadZone() ); + addAxis(axis); + axisIndex.put( comp, axis ); + + // Support the X/Y axis indexes + if( id == Axis.X ) { + xAxis = axis; + } else if( id == Axis.Y ) { + yAxis = axis; + } else if( id == Axis.POV ) { + + // Add two fake axes for the JME provided convenience + // axes: AXIS_POV_X, AXIS_POV_Y + povX = new DefaultJoystickAxis( getInputManager(), + this, getAxisCount(), JoystickAxis.POV_X, + id.getName() + "_x", + comp.isAnalog(), comp.isRelative(), comp.getDeadZone() ); + logger.log(Level.FINE, "Adding axis: \"{0}\" id:" + id.getName() + "_x", povX.getName() ); + addAxis(povX); + povY = new DefaultJoystickAxis( getInputManager(), + this, getAxisCount(), JoystickAxis.POV_Y, + id.getName() + "_y", + comp.isAnalog(), comp.isRelative(), comp.getDeadZone() ); + logger.log(Level.FINE, "Adding axis: \"{0}\" id:" + id.getName() + "_y", povY.getName() ); + addAxis(povY); + } + + } + + @Override + public JoystickAxis getXAxis() { + return xAxis; + } + + @Override + public JoystickAxis getYAxis() { + return yAxis; + } + + @Override + public JoystickAxis getPovXAxis() { + return povX; + } + + @Override + public JoystickAxis getPovYAxis() { + return povY; + } + + @Override + public int getXAxisIndex(){ + return xAxis.getAxisId(); + } + + @Override + public int getYAxisIndex(){ + return yAxis.getAxisId(); + } + } +} + + + diff --git a/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglKeyInput.java b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglKeyInput.java new file mode 100644 index 000000000..e7b9f9268 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglKeyInput.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.lwjgl; + +import com.jme3.input.KeyInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.system.lwjgl.LwjglAbstractDisplay; +import com.jme3.system.lwjgl.LwjglTimer; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.lwjgl.LWJGLException; +import org.lwjgl.Sys; +import org.lwjgl.input.Keyboard; + +public class LwjglKeyInput implements KeyInput { + + private static final Logger logger = Logger.getLogger(LwjglKeyInput.class.getName()); + + private LwjglAbstractDisplay context; + + private RawInputListener listener; + + public LwjglKeyInput(LwjglAbstractDisplay context){ + this.context = context; + } + + public void initialize() { + if (!context.isRenderable()) + return; + + try { + Keyboard.create(); + Keyboard.enableRepeatEvents(true); + logger.fine("Keyboard created."); + } catch (LWJGLException ex) { + logger.log(Level.SEVERE, "Error while creating keyboard.", ex); + } + } + + public int getKeyCount(){ + return Keyboard.KEYBOARD_SIZE; + } + + public void update() { + if (!context.isRenderable()) + return; + + Keyboard.poll(); + while (Keyboard.next()){ + int keyCode = Keyboard.getEventKey(); + char keyChar = Keyboard.getEventCharacter(); + boolean pressed = Keyboard.getEventKeyState(); + boolean down = Keyboard.isRepeatEvent(); + long time = Keyboard.getEventNanoseconds(); + KeyInputEvent evt = new KeyInputEvent(keyCode, keyChar, pressed, down); + evt.setTime(time); + listener.onKeyEvent(evt); + } + } + + public void destroy() { + if (!context.isRenderable()) + return; + + Keyboard.destroy(); + logger.fine("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/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglMouseInput.java b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglMouseInput.java new file mode 100644 index 000000000..a91aab36e --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglMouseInput.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.input.lwjgl; + +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.MouseInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.system.lwjgl.LwjglAbstractDisplay; +import com.jme3.system.lwjgl.LwjglTimer; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.lwjgl.LWJGLException; +import org.lwjgl.Sys; +import org.lwjgl.input.Cursor; +import org.lwjgl.input.Mouse; + +public class LwjglMouseInput implements MouseInput { + + private static final Logger logger = Logger.getLogger(LwjglMouseInput.class.getName()); + + private LwjglAbstractDisplay context; + + private RawInputListener listener; + + private boolean supportHardwareCursor = false; + private boolean cursorVisible = true; + + private int curX, curY, curWheel; + + public LwjglMouseInput(LwjglAbstractDisplay context){ + this.context = context; + } + + public void initialize() { + if (!context.isRenderable()) + return; + + try { + Mouse.create(); + logger.fine("Mouse created."); + supportHardwareCursor = (Cursor.getCapabilities() & Cursor.CURSOR_ONE_BIT_TRANSPARENCY) != 0; + + // Recall state that was set before initialization + Mouse.setGrabbed(!cursorVisible); + } catch (LWJGLException ex) { + logger.log(Level.SEVERE, "Error while creating mouse", ex); + } + } + + public boolean isInitialized(){ + return Mouse.isCreated(); + } + + public int getButtonCount(){ + return Mouse.getButtonCount(); + } + + public void update() { + if (!context.isRenderable()) + return; + + while (Mouse.next()){ + int btn = Mouse.getEventButton(); + + int wheelDelta = Mouse.getEventDWheel(); + int xDelta = Mouse.getEventDX(); + int yDelta = Mouse.getEventDY(); + int x = Mouse.getX(); + int y = Mouse.getY(); + + curWheel += wheelDelta; + if (cursorVisible){ + xDelta = x - curX; + yDelta = y - curY; + curX = x; + curY = y; + }else{ + x = curX + xDelta; + y = curY + yDelta; + curX = x; + curY = y; + } + + if (xDelta != 0 || yDelta != 0 || wheelDelta != 0){ + MouseMotionEvent evt = new MouseMotionEvent(x, y, xDelta, yDelta, curWheel, wheelDelta); + evt.setTime(Mouse.getEventNanoseconds()); + listener.onMouseMotionEvent(evt); + } + if (btn != -1){ + MouseButtonEvent evt = new MouseButtonEvent(btn, + Mouse.getEventButtonState(), x, y); + evt.setTime(Mouse.getEventNanoseconds()); + listener.onMouseButtonEvent(evt); + } + } + } + + public void destroy() { + if (!context.isRenderable()) + return; + + Mouse.destroy(); + logger.fine("Mouse destroyed."); + } + + public void setCursorVisible(boolean visible){ + cursorVisible = visible; + if (!context.isRenderable()) + return; + + Mouse.setGrabbed(!visible); + } + + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + public long getInputTimeNanos() { + return Sys.getTime() * LwjglTimer.LWJGL_TIME_TO_NANOS; + } + + public void setNativeCursor(JmeCursor jmeCursor) { + try { + Cursor newCursor = null; + if (jmeCursor != null) { + newCursor = new Cursor( + jmeCursor.getWidth(), + jmeCursor.getHeight(), + jmeCursor.getXHotSpot(), + jmeCursor.getYHotSpot(), + jmeCursor.getNumImages(), + jmeCursor.getImagesData(), + jmeCursor.getImagesDelay()); + } + Mouse.setNativeCursor(newCursor); + } catch (LWJGLException ex) { + Logger.getLogger(LwjglMouseInput.class.getName()).log(Level.SEVERE, null, ex); + } + } + +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java new file mode 100644 index 000000000..c0bb17daa --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java @@ -0,0 +1,1197 @@ +package com.jme3.renderer.lwjgl; + +import static org.lwjgl.opengl.GL11.*; + +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.EnumSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jme3tools.converters.MipMapGenerator; + +import org.lwjgl.opengl.GL12; +import org.lwjgl.opengl.GL14; +import org.lwjgl.opengl.GLContext; + +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.material.FixedFuncBinding; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.GL1Renderer; +import com.jme3.renderer.RenderContext; +import com.jme3.renderer.RendererException; +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.NativeObjectManager; + +public class LwjglGL1Renderer implements GL1Renderer { + + private static final Logger logger = Logger.getLogger(LwjglRenderer.class.getName()); + private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); + private final StringBuilder stringBuf = new StringBuilder(250); + private final IntBuffer ib1 = BufferUtils.createIntBuffer(1); + private final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16); + private final FloatBuffer fb16 = BufferUtils.createFloatBuffer(16); + private final FloatBuffer fb4Null = BufferUtils.createFloatBuffer(4); + private final RenderContext context = new RenderContext(); + private final NativeObjectManager objManager = new NativeObjectManager(); + private final EnumSet caps = EnumSet.noneOf(Caps.class); + private int maxTexSize; + private int maxCubeTexSize; + private int maxVertCount; + private int maxTriCount; + private int maxLights; + private boolean gl12 = false; + private final Statistics statistics = new Statistics(); + private int vpX, vpY, vpW, vpH; + private int clipX, clipY, clipW, clipH; + + private Matrix4f worldMatrix = new Matrix4f(); + private Matrix4f viewMatrix = new Matrix4f(); + + private ArrayList lightList = new ArrayList(8); + private ColorRGBA materialAmbientColor = new ColorRGBA(); + private Vector3f tempVec = new Vector3f(); + + protected void updateNameBuffer() { + int len = stringBuf.length(); + + nameBuf.position(0); + nameBuf.limit(len); + for (int i = 0; i < len; i++) { + nameBuf.put((byte) stringBuf.charAt(i)); + } + + nameBuf.rewind(); + } + + public Statistics getStatistics() { + return statistics; + } + + public EnumSet getCaps() { + return caps; + } + + public void initialize() { + if (GLContext.getCapabilities().OpenGL12){ + gl12 = true; + } + + // Default values for certain GL state. + glShadeModel(GL_SMOOTH); + glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE); + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + + // Enable rescaling/normaling of normal vectors. + // Fixes lighting issues with scaled models. + if (gl12){ + glEnable(GL12.GL_RESCALE_NORMAL); + }else{ + glEnable(GL_NORMALIZE); + } + + if (GLContext.getCapabilities().GL_ARB_texture_non_power_of_two) { + caps.add(Caps.NonPowerOfTwoTextures); + } else { + logger.log(Level.WARNING, "Your graphics card does not " + + "support non-power-of-2 textures. " + + "Some features might not work."); + } + + maxLights = glGetInteger(GL_MAX_LIGHTS); + maxTexSize = glGetInteger(GL_MAX_TEXTURE_SIZE); + } + + public void invalidateState() { + context.reset(); + } + + public void resetGLObjects() { + logger.log(Level.FINE, "Reseting objects and invalidating state"); + objManager.resetObjects(); + statistics.clearMemory(); + invalidateState(); + } + + public void cleanup() { + logger.log(Level.FINE, "Deleting objects and invalidating state"); + objManager.deleteAllObjects(this); + statistics.clearMemory(); + invalidateState(); + } + + public void setDepthRange(float start, float end) { + glDepthRange(start, end); + } + + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + int bits = 0; + if (color) { + //See explanations of the depth below, we must enable color write to be able to clear the color buffer + if (context.colorWriteEnabled == false) { + glColorMask(true, true, true, true); + context.colorWriteEnabled = true; + } + bits = GL_COLOR_BUFFER_BIT; + } + if (depth) { + + //glClear(GL_DEPTH_BUFFER_BIT) seems to not work when glDepthMask is false + //here s some link on openl board + //http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=257223 + //if depth clear is requested, we enable the depthMask + if (context.depthWriteEnabled == false) { + glDepthMask(true); + context.depthWriteEnabled = true; + } + bits |= GL_DEPTH_BUFFER_BIT; + } + if (stencil) { + bits |= GL_STENCIL_BUFFER_BIT; + } + if (bits != 0) { + glClear(bits); + } + } + + public void setBackgroundColor(ColorRGBA color) { + glClearColor(color.r, color.g, color.b, color.a); + } + + private void setMaterialColor(int type, ColorRGBA color, ColorRGBA defaultColor) { + if (color != null){ + fb16.put(color.r).put(color.g).put(color.b).put(color.a).flip(); + }else{ + fb16.put(defaultColor.r).put(defaultColor.g).put(defaultColor.b).put(defaultColor.a).flip(); + } + glMaterial(GL_FRONT_AND_BACK, type, fb16); + } + + /** + * Applies fixed function bindings from the context to OpenGL + */ + private void applyFixedFuncBindings(boolean forLighting){ + if (forLighting) { + glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, context.shininess); + setMaterialColor(GL_AMBIENT, context.ambient, ColorRGBA.DarkGray); + setMaterialColor(GL_DIFFUSE, context.diffuse, ColorRGBA.White); + setMaterialColor(GL_SPECULAR, context.specular, ColorRGBA.Black); + + if (context.useVertexColor) { + glEnable(GL_COLOR_MATERIAL); + } else { + glDisable(GL_COLOR_MATERIAL); + } + } else { + // Ignore other values as they have no effect when + // GL_LIGHTING is disabled. + ColorRGBA color = context.color; + if (color != null) { + glColor4f(color.r, color.g, color.b, color.a); + } else { + glColor4f(1, 1, 1, 1); + } + } + if (context.alphaTestFallOff > 0f) { + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, context.alphaTestFallOff); + } else { + glDisable(GL_ALPHA_TEST); + } + } + + /** + * Reset fixed function bindings to default values. + */ + private void resetFixedFuncBindings(){ + context.alphaTestFallOff = 0f; // zero means disable alpha test! + context.color = null; + context.ambient = null; + context.diffuse = null; + context.specular = null; + context.shininess = 0; + context.useVertexColor = false; + } + + public void setFixedFuncBinding(FixedFuncBinding ffBinding, Object val) { + switch (ffBinding) { + case Color: + context.color = (ColorRGBA) val; + break; + case MaterialAmbient: + context.ambient = (ColorRGBA) val; + break; + case MaterialDiffuse: + context.diffuse = (ColorRGBA) val; + break; + case MaterialSpecular: + context.specular = (ColorRGBA) val; + break; + case MaterialShininess: + context.shininess = (Float) val; + break; + case UseVertexColor: + context.useVertexColor = (Boolean) val; + break; + case AlphaTestFallOff: + context.alphaTestFallOff = (Float) val; + break; + } + } + + public void applyRenderState(RenderState state) { + if (state.isWireframe() && !context.wireframe) { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + context.wireframe = true; + } else if (!state.isWireframe() && context.wireframe) { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + context.wireframe = false; + } + + if (state.isDepthTest() && !context.depthTestEnabled) { + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + context.depthTestEnabled = true; + } else if (!state.isDepthTest() && context.depthTestEnabled) { + glDisable(GL_DEPTH_TEST); + context.depthTestEnabled = false; + } + + if (state.isAlphaTest()) { + setFixedFuncBinding(FixedFuncBinding.AlphaTestFallOff, state.getAlphaFallOff()); + } else { + setFixedFuncBinding(FixedFuncBinding.AlphaTestFallOff, 0f); // disable it + } + + if (state.isDepthWrite() && !context.depthWriteEnabled) { + glDepthMask(true); + context.depthWriteEnabled = true; + } else if (!state.isDepthWrite() && context.depthWriteEnabled) { + glDepthMask(false); + context.depthWriteEnabled = false; + } + + if (state.isColorWrite() && !context.colorWriteEnabled) { + glColorMask(true, true, true, true); + context.colorWriteEnabled = true; + } else if (!state.isColorWrite() && context.colorWriteEnabled) { + glColorMask(false, false, false, false); + context.colorWriteEnabled = false; + } + + if (state.isPointSprite()) { + logger.log(Level.WARNING, "Point Sprite unsupported!"); + } + + if (state.isPolyOffset()) { + if (!context.polyOffsetEnabled) { + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + context.polyOffsetEnabled = true; + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } else { + if (state.getPolyOffsetFactor() != context.polyOffsetFactor + || state.getPolyOffsetUnits() != context.polyOffsetUnits) { + glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } + } + } else { + if (context.polyOffsetEnabled) { + glDisable(GL_POLYGON_OFFSET_FILL); + context.polyOffsetEnabled = false; + context.polyOffsetFactor = 0; + context.polyOffsetUnits = 0; + } + } + if (state.getFaceCullMode() != context.cullMode) { + if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) { + glDisable(GL_CULL_FACE); + } else { + glEnable(GL_CULL_FACE); + } + + switch (state.getFaceCullMode()) { + case Off: + break; + case Back: + glCullFace(GL_BACK); + break; + case Front: + glCullFace(GL_FRONT); + break; + case FrontAndBack: + glCullFace(GL_FRONT_AND_BACK); + break; + default: + throw new UnsupportedOperationException("Unrecognized face cull mode: " + + state.getFaceCullMode()); + } + + context.cullMode = state.getFaceCullMode(); + } + + if (state.getBlendMode() != context.blendMode) { + if (state.getBlendMode() == RenderState.BlendMode.Off) { + glDisable(GL_BLEND); + } else { + glEnable(GL_BLEND); + switch (state.getBlendMode()) { + case Off: + break; + case Additive: + glBlendFunc(GL_ONE, GL_ONE); + break; + case AlphaAdditive: + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + case Color: + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); + break; + case Alpha: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case PremultAlpha: + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + break; + case Modulate: + glBlendFunc(GL_DST_COLOR, GL_ZERO); + break; + case ModulateX2: + glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); + break; + default: + throw new UnsupportedOperationException("Unrecognized blend mode: " + + state.getBlendMode()); + } + } + + context.blendMode = state.getBlendMode(); + } + + if (state.isStencilTest()) { + throw new UnsupportedOperationException("OpenGL 1.1 doesn't support two sided stencil operations."); + } + + } + + public void setViewPort(int x, int y, int w, int h) { + if (x != vpX || vpY != y || vpW != w || vpH != h) { + glViewport(x, y, w, h); + vpX = x; + vpY = y; + vpW = w; + vpH = h; + } + } + + public void setClipRect(int x, int y, int width, int height) { + if (!context.clipRectEnabled) { + glEnable(GL_SCISSOR_TEST); + context.clipRectEnabled = true; + } + if (clipX != x || clipY != y || clipW != width || clipH != height) { + glScissor(x, y, width, height); + clipX = x; + clipY = y; + clipW = width; + clipH = height; + } + } + + public void clearClipRect() { + if (context.clipRectEnabled) { + glDisable(GL_SCISSOR_TEST); + context.clipRectEnabled = false; + + clipX = 0; + clipY = 0; + clipW = 0; + clipH = 0; + } + } + + public void onFrame() { + objManager.deleteUnused(this); +// statistics.clearFrame(); + } + + private FloatBuffer storeMatrix(Matrix4f matrix, FloatBuffer store) { + store.clear(); + matrix.fillFloatBuffer(store, true); + store.clear(); + return store; + } + + private void setModelView(Matrix4f modelMatrix, Matrix4f viewMatrix){ + if (context.matrixMode != GL_MODELVIEW) { + glMatrixMode(GL_MODELVIEW); + context.matrixMode = GL_MODELVIEW; + } + + glLoadMatrix(storeMatrix(viewMatrix, fb16)); + glMultMatrix(storeMatrix(modelMatrix, fb16)); + } + + private void setProjection(Matrix4f projMatrix){ + if (context.matrixMode != GL_PROJECTION) { + glMatrixMode(GL_PROJECTION); + context.matrixMode = GL_PROJECTION; + } + + glLoadMatrix(storeMatrix(projMatrix, fb16)); + } + + public void setWorldMatrix(Matrix4f worldMatrix) { + this.worldMatrix.set(worldMatrix); + } + + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { + this.viewMatrix.set(viewMatrix); + setProjection(projMatrix); + } + + public void setLighting(LightList list) { + // XXX: This is abuse of setLighting() to + // apply fixed function bindings + // and do other book keeping. + if (list == null || list.size() == 0){ + glDisable(GL_LIGHTING); + applyFixedFuncBindings(false); + setModelView(worldMatrix, viewMatrix); + return; + } + + // Number of lights set previously + int numLightsSetPrev = lightList.size(); + + // If more than maxLights are defined, they will be ignored. + // The GL1 renderer is not permitted to crash due to a + // GL1 limitation. It must render anything that the GL2 renderer + // can render (even incorrectly). + lightList.clear(); + materialAmbientColor.set(0, 0, 0, 0); + + for (int i = 0; i < list.size(); i++){ + Light l = list.get(i); + if (l.getType() == Light.Type.Ambient){ + // Gather + materialAmbientColor.addLocal(l.getColor()); + }else{ + // Add to list + lightList.add(l); + + // Once maximum lights reached, exit loop. + if (lightList.size() >= maxLights){ + break; + } + } + } + + applyFixedFuncBindings(true); + + glEnable(GL_LIGHTING); + + fb16.clear(); + fb16.put(materialAmbientColor.r) + .put(materialAmbientColor.g) + .put(materialAmbientColor.b) + .put(1).flip(); + + glLightModel(GL_LIGHT_MODEL_AMBIENT, fb16); + + if (context.matrixMode != GL_MODELVIEW) { + glMatrixMode(GL_MODELVIEW); + context.matrixMode = GL_MODELVIEW; + } + // Lights are already in world space, so just convert + // them to view space. + glLoadMatrix(storeMatrix(viewMatrix, fb16)); + + for (int i = 0; i < lightList.size(); i++){ + int glLightIndex = GL_LIGHT0 + i; + Light light = lightList.get(i); + Light.Type lightType = light.getType(); + ColorRGBA col = light.getColor(); + Vector3f pos; + + // Enable the light + glEnable(glLightIndex); + + // OGL spec states default value for light ambient is black + switch (lightType){ + case Directional: + DirectionalLight dLight = (DirectionalLight) light; + + fb16.clear(); + fb16.put(col.r).put(col.g).put(col.b).put(col.a).flip(); + glLight(glLightIndex, GL_DIFFUSE, fb16); + glLight(glLightIndex, GL_SPECULAR, fb16); + + pos = tempVec.set(dLight.getDirection()).negateLocal().normalizeLocal(); + fb16.clear(); + fb16.put(pos.x).put(pos.y).put(pos.z).put(0.0f).flip(); + glLight(glLightIndex, GL_POSITION, fb16); + glLightf(glLightIndex, GL_SPOT_CUTOFF, 180); + break; + case Point: + PointLight pLight = (PointLight) light; + + fb16.clear(); + fb16.put(col.r).put(col.g).put(col.b).put(col.a).flip(); + glLight(glLightIndex, GL_DIFFUSE, fb16); + glLight(glLightIndex, GL_SPECULAR, fb16); + + pos = pLight.getPosition(); + fb16.clear(); + fb16.put(pos.x).put(pos.y).put(pos.z).put(1.0f).flip(); + glLight(glLightIndex, GL_POSITION, fb16); + glLightf(glLightIndex, GL_SPOT_CUTOFF, 180); + + if (pLight.getRadius() > 0) { + // Note: this doesn't follow the same attenuation model + // as the one used in the lighting shader. + glLightf(glLightIndex, GL_CONSTANT_ATTENUATION, 1); + glLightf(glLightIndex, GL_LINEAR_ATTENUATION, pLight.getInvRadius() * 2); + glLightf(glLightIndex, GL_QUADRATIC_ATTENUATION, pLight.getInvRadius() * pLight.getInvRadius()); + }else{ + glLightf(glLightIndex, GL_CONSTANT_ATTENUATION, 1); + glLightf(glLightIndex, GL_LINEAR_ATTENUATION, 0); + glLightf(glLightIndex, GL_QUADRATIC_ATTENUATION, 0); + } + + break; + case Spot: + SpotLight sLight = (SpotLight) light; + + fb16.clear(); + fb16.put(col.r).put(col.g).put(col.b).put(col.a).flip(); + glLight(glLightIndex, GL_DIFFUSE, fb16); + glLight(glLightIndex, GL_SPECULAR, fb16); + + pos = sLight.getPosition(); + fb16.clear(); + fb16.put(pos.x).put(pos.y).put(pos.z).put(1.0f).flip(); + glLight(glLightIndex, GL_POSITION, fb16); + + Vector3f dir = sLight.getDirection(); + fb16.clear(); + fb16.put(dir.x).put(dir.y).put(dir.z).put(1.0f).flip(); + glLight(glLightIndex, GL_SPOT_DIRECTION, fb16); + + float outerAngleRad = sLight.getSpotOuterAngle(); + float innerAngleRad = sLight.getSpotInnerAngle(); + float spotCut = outerAngleRad * FastMath.RAD_TO_DEG; + float spotExpo = 0.0f; + if (outerAngleRad > 0) { + spotExpo = (1.0f - (innerAngleRad / outerAngleRad)) * 128.0f; + } + + glLightf(glLightIndex, GL_SPOT_CUTOFF, spotCut); + glLightf(glLightIndex, GL_SPOT_EXPONENT, spotExpo); + + if (sLight.getSpotRange() > 0) { + glLightf(glLightIndex, GL_LINEAR_ATTENUATION, sLight.getInvSpotRange()); + }else{ + glLightf(glLightIndex, GL_LINEAR_ATTENUATION, 0); + } + + break; + default: + throw new UnsupportedOperationException( + "Unrecognized light type: " + lightType); + } + } + + // Disable lights after the index + for (int i = lightList.size(); i < numLightsSetPrev; i++){ + glDisable(GL_LIGHT0 + i); + } + + // This will set view matrix as well. + setModelView(worldMatrix, viewMatrix); + } + + private int convertTextureType(Texture.Type type) { + switch (type) { + case TwoDimensional: + return GL_TEXTURE_2D; +// case ThreeDimensional: +// return GL_TEXTURE_3D; +// case CubeMap: +// return GL_TEXTURE_CUBE_MAP; + default: + throw new UnsupportedOperationException("Unknown texture type: " + type); + } + } + + private int convertMagFilter(Texture.MagFilter filter) { + switch (filter) { + case Bilinear: + return GL_LINEAR; + case Nearest: + return GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown mag filter: " + filter); + } + } + + private int convertMinFilter(Texture.MinFilter filter) { + switch (filter) { + case Trilinear: + return GL_LINEAR_MIPMAP_LINEAR; + case BilinearNearestMipMap: + return GL_LINEAR_MIPMAP_NEAREST; + case NearestLinearMipMap: + return GL_NEAREST_MIPMAP_LINEAR; + case NearestNearestMipMap: + return GL_NEAREST_MIPMAP_NEAREST; + case BilinearNoMipMaps: + return GL_LINEAR; + case NearestNoMipMaps: + return GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown min filter: " + filter); + } + } + + private int convertWrapMode(Texture.WrapMode mode) { + switch (mode) { + case EdgeClamp: + case Clamp: + case BorderClamp: + return GL_CLAMP; + case Repeat: + return GL_REPEAT; + default: + throw new UnsupportedOperationException("Unknown wrap mode: " + mode); + } + } + + private void setupTextureParams(Texture tex) { + int target = convertTextureType(tex.getType()); + + // filter things + int minFilter = convertMinFilter(tex.getMinFilter()); + int magFilter = convertMagFilter(tex.getMagFilter()); + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, minFilter); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, magFilter); + + // repeat modes + switch (tex.getType()) { +// case ThreeDimensional: +// case CubeMap: +// glTexParameteri(target, GL_TEXTURE_WRAP_R, convertWrapMode(tex.getWrap(WrapAxis.R))); + case TwoDimensional: + glTexParameteri(target, GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); + // fall down here is intentional.. +// case OneDimensional: + glTexParameteri(target, GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S))); + break; + default: + throw new UnsupportedOperationException("Unknown texture type: " + tex.getType()); + } + } + + public void updateTexImageData(Image img, Texture.Type type, int unit) { + int texId = img.getId(); + if (texId == -1) { + // create texture + glGenTextures(ib1); + texId = ib1.get(0); + img.setId(texId); + objManager.registerObject(img); + + statistics.onNewTexture(); + } + + // bind texture + int target = convertTextureType(type); +// if (context.boundTextureUnit != unit) { +// glActiveTexture(GL_TEXTURE0 + unit); +// context.boundTextureUnit = unit; +// } + if (context.boundTextures[unit] != img) { + glEnable(target); + glBindTexture(target, texId); + context.boundTextures[unit] = img; + + statistics.onTextureUse(img, true); + } + + // Check sizes if graphics card doesn't support NPOT + if (!GLContext.getCapabilities().GL_ARB_texture_non_power_of_two) { + if (img.getWidth() != 0 && img.getHeight() != 0) { + if (!FastMath.isPowerOfTwo(img.getWidth()) + || !FastMath.isPowerOfTwo(img.getHeight())) { + + // Resize texture to Power-of-2 size + MipMapGenerator.resizeToPowerOf2(img); + } + } + } + + if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) { + // No pregenerated mips available, + // generate from base level if required + + // Check if hardware mips are supported + if (GLContext.getCapabilities().OpenGL14) { + glTexParameteri(target, GL14.GL_GENERATE_MIPMAP, GL_TRUE); + } else { + MipMapGenerator.generateMipMaps(img); + } + img.setMipmapsGenerated(true); + } else { + } + + if (img.getWidth() > maxTexSize || img.getHeight() > maxTexSize) { + throw new RendererException("Cannot upload texture " + img + ". The maximum supported texture resolution is " + maxTexSize); + } + + /* + 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); + //} + + 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() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated()) ) { + updateTexImageData(image, tex.getType(), 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); + } + + public void modifyTexture(Texture tex, Image pixels, int x, int y) { + setTexture(0, tex); + TextureUtil.uploadSubTexture(pixels, convertTextureType(tex.getType()), 0, x, y); + } + + private void clearTextureUnits() { + Image[] textures = context.boundTextures; + if (textures[0] != null) { + glDisable(GL_TEXTURE_2D); + textures[0] = null; + } + } + + public void deleteImage(Image image) { + int texId = image.getId(); + if (texId != -1) { + ib1.put(0, texId); + ib1.position(0).limit(1); + glDeleteTextures(ib1); + image.resetObject(); + } + } + + private int convertArrayType(VertexBuffer.Type type) { + switch (type) { + case Position: + return GL_VERTEX_ARRAY; + case Normal: + return GL_NORMAL_ARRAY; + case TexCoord: + return GL_TEXTURE_COORD_ARRAY; + case Color: + return GL_COLOR_ARRAY; + default: + return -1; // unsupported + } + } + + private int convertVertexFormat(VertexBuffer.Format fmt) { + switch (fmt) { + case Byte: + return GL_BYTE; + case Float: + return GL_FLOAT; + case Int: + return GL_INT; + case Short: + return GL_SHORT; + case UnsignedByte: + return GL_UNSIGNED_BYTE; + case UnsignedInt: + return GL_UNSIGNED_INT; + case UnsignedShort: + return GL_UNSIGNED_SHORT; + default: + throw new UnsupportedOperationException("Unrecognized vertex format: " + fmt); + } + } + + private int convertElementMode(Mesh.Mode mode) { + switch (mode) { + case Points: + return GL_POINTS; + case Lines: + return GL_LINES; + case LineLoop: + return GL_LINE_LOOP; + case LineStrip: + return GL_LINE_STRIP; + case Triangles: + return GL_TRIANGLES; + case TriangleFan: + return GL_TRIANGLE_FAN; + case TriangleStrip: + return GL_TRIANGLE_STRIP; + default: + throw new UnsupportedOperationException("Unrecognized mesh mode: " + mode); + } + } + + public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) { + if (count > 1) { + throw new UnsupportedOperationException(); + } + + glDrawArrays(convertElementMode(mode), 0, vertCount); + } + + public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { + if (vb.getBufferType() == VertexBuffer.Type.Color && !context.useVertexColor) { + // Ignore vertex color buffer if vertex color is disabled. + return; + } + + int arrayType = convertArrayType(vb.getBufferType()); + if (arrayType == -1) { + return; // unsupported + } + glEnableClientState(arrayType); + context.boundAttribs[vb.getBufferType().ordinal()] = vb; + + if (vb.getBufferType() == Type.Normal) { + // normalize if requested + if (vb.isNormalized() && !context.normalizeEnabled) { + glEnable(GL_NORMALIZE); + context.normalizeEnabled = true; + } else if (!vb.isNormalized() && context.normalizeEnabled) { + glDisable(GL_NORMALIZE); + context.normalizeEnabled = false; + } + } + + // NOTE: Use data from interleaved buffer if specified + Buffer data = idb != null ? idb.getData() : vb.getData(); + int comps = vb.getNumComponents(); + int type = convertVertexFormat(vb.getFormat()); + + data.rewind(); + + switch (vb.getBufferType()) { + case Position: + if (!(data instanceof FloatBuffer)) { + throw new UnsupportedOperationException(); + } + + glVertexPointer(comps, vb.getStride(), (FloatBuffer) data); + break; + case Normal: + if (!(data instanceof FloatBuffer)) { + throw new UnsupportedOperationException(); + } + + glNormalPointer(vb.getStride(), (FloatBuffer) data); + break; + case Color: + if (data instanceof FloatBuffer) { + glColorPointer(comps, vb.getStride(), (FloatBuffer) data); + } else if (data instanceof ByteBuffer) { + glColorPointer(comps, true, vb.getStride(), (ByteBuffer) data); + } else { + throw new UnsupportedOperationException(); + } + break; + case TexCoord: + if (!(data instanceof FloatBuffer)) { + throw new UnsupportedOperationException(); + } + + glTexCoordPointer(comps, vb.getStride(), (FloatBuffer) data); + break; + default: + // Ignore, this is an unsupported attribute for OpenGL1. + break; + } + } + + public void setVertexAttrib(VertexBuffer vb) { + setVertexAttrib(vb, null); + } + + private void drawElements(int mode, int format, Buffer data) { + switch (format) { + case GL_UNSIGNED_BYTE: + glDrawElements(mode, (ByteBuffer) data); + break; + case GL_UNSIGNED_SHORT: + glDrawElements(mode, (ShortBuffer) data); + break; + case GL_UNSIGNED_INT: + glDrawElements(mode, (IntBuffer) data); + break; + default: + throw new UnsupportedOperationException(); + } + } + + public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) { + Mesh.Mode mode = mesh.getMode(); + + Buffer indexData = indexBuf.getData(); + indexData.rewind(); + + if (mesh.getMode() == Mode.Hybrid) { + throw new UnsupportedOperationException(); + /* + int[] modeStart = mesh.getModeStart(); + int[] elementLengths = mesh.getElementLengths(); + + int elMode = convertElementMode(Mode.Triangles); + int fmt = convertVertexFormat(indexBuf.getFormat()); + // int elSize = indexBuf.getFormat().getComponentSize(); + // int listStart = modeStart[0]; + int stripStart = modeStart[1]; + int fanStart = modeStart[2]; + int curOffset = 0; + for (int i = 0; i < elementLengths.length; i++) { + if (i == stripStart) { + elMode = convertElementMode(Mode.TriangleStrip); + } else if (i == fanStart) { + elMode = convertElementMode(Mode.TriangleStrip); + } + int elementLength = elementLengths[i]; + indexData.position(curOffset); + + drawElements(elMode, + fmt, + indexData); + + curOffset += elementLength; + }*/ + } else { + drawElements(convertElementMode(mode), + convertVertexFormat(indexBuf.getFormat()), + indexData); + } + } + + public void clearVertexAttribs() { + for (int i = 0; i < 16; i++) { + VertexBuffer vb = context.boundAttribs[i]; + if (vb != null) { + int arrayType = convertArrayType(vb.getBufferType()); + glDisableClientState(arrayType); + context.boundAttribs[vb.getBufferType().ordinal()] = null; + } + } + } + + private void renderMeshDefault(Mesh mesh, int lod, int count) { + VertexBuffer indices = null; + + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()) { + updateBufferData(interleavedData); + } + + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = mesh.getBuffer(Type.Index); + } + for (VertexBuffer vb : mesh.getBufferList().getArray()) { + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } + + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + + if (indices != null) { + drawTriangleList(indices, mesh, count); + } else { + glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount()); + } + + // TODO: Fix these to use IDList?? + clearVertexAttribs(); + clearTextureUnits(); + resetFixedFuncBindings(); + } + + public void renderMesh(Mesh mesh, int lod, int count) { + if (mesh.getVertexCount() == 0) { + return; + } + + if (context.pointSize != mesh.getPointSize()) { + glPointSize(mesh.getPointSize()); + context.pointSize = mesh.getPointSize(); + } + if (context.lineWidth != mesh.getLineWidth()) { + glLineWidth(mesh.getLineWidth()); + context.lineWidth = mesh.getLineWidth(); + } + + boolean dynamic = false; + if (mesh.getBuffer(Type.InterleavedData) != null) { + throw new UnsupportedOperationException("Interleaved meshes are not supported"); + } + + if (mesh.getNumLodLevels() == 0) { + for (VertexBuffer vb : mesh.getBufferList().getArray()) { + if (vb.getUsage() != VertexBuffer.Usage.Static) { + dynamic = true; + break; + } + } + } else { + dynamic = true; + } + + statistics.onMeshDrawn(mesh, lod); + +// if (!dynamic) { + // dealing with a static object, generate display list +// renderMeshDisplayList(mesh); +// } else { + renderMeshDefault(mesh, lod, count); +// } + + + } + + public void setAlphaToCoverage(boolean value) { + } + + public void setShader(Shader shader) { + } + + public void deleteShader(Shader shader) { + } + + public void deleteShaderSource(ShaderSource source) { + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { + } + + public void setMainFrameBufferOverride(FrameBuffer fb){ + } + + public void setFrameBuffer(FrameBuffer fb) { + } + + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { + } + + public void deleteFrameBuffer(FrameBuffer fb) { + } + + public void updateBufferData(VertexBuffer vb) { + } + + public void deleteBuffer(VertexBuffer vb) { + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java new file mode 100644 index 000000000..fe775f9be --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java @@ -0,0 +1,2486 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.lwjgl; + +import com.jme3.light.LightList; +import com.jme3.material.RenderState; +import com.jme3.material.RenderState.StencilOperation; +import com.jme3.material.RenderState.TestFunction; +import com.jme3.math.*; +import com.jme3.renderer.*; +import com.jme3.scene.Mesh; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.shader.Attribute; +import com.jme3.shader.Shader; +import com.jme3.shader.Shader.ShaderSource; +import com.jme3.shader.Shader.ShaderType; +import com.jme3.shader.Uniform; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.FrameBuffer.RenderBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapAxis; +import com.jme3.util.BufferUtils; +import com.jme3.util.ListMap; +import com.jme3.util.NativeObject; +import com.jme3.util.NativeObjectManager; +import com.jme3.util.SafeArrayList; +import java.nio.*; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3tools.converters.MipMapGenerator; +import jme3tools.shader.ShaderDebug; +import static org.lwjgl.opengl.ARBTextureMultisample.*; +import static org.lwjgl.opengl.EXTFramebufferBlit.*; +import static org.lwjgl.opengl.EXTFramebufferMultisample.*; +import static org.lwjgl.opengl.EXTFramebufferObject.*; +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL12.*; +import static org.lwjgl.opengl.GL13.*; +import static org.lwjgl.opengl.GL14.*; +import static org.lwjgl.opengl.GL15.*; +import static org.lwjgl.opengl.GL20.*; +import org.lwjgl.opengl.*; +//import static org.lwjgl.opengl.ARBDrawInstanced.*; + +public class LwjglRenderer implements Renderer { + + private static final Logger logger = Logger.getLogger(LwjglRenderer.class.getName()); + private static final boolean VALIDATE_SHADER = false; + private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); + private final StringBuilder stringBuf = new StringBuilder(250); + private final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1); + private final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16); + private final FloatBuffer floatBuf16 = BufferUtils.createFloatBuffer(16); + private final RenderContext context = new RenderContext(); + private final NativeObjectManager objManager = new NativeObjectManager(); + 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 FrameBuffer lastFb = null; + private FrameBuffer mainFbOverride = null; + private final Statistics statistics = new Statistics(); + private int vpX, vpY, vpW, vpH; + private int clipX, clipY, clipW, clipH; + + public LwjglRenderer() { + } + + protected void updateNameBuffer() { + int len = stringBuf.length(); + + nameBuf.position(0); + nameBuf.limit(len); + for (int i = 0; i < len; i++) { + nameBuf.put((byte) stringBuf.charAt(i)); + } + + nameBuf.rewind(); + } + + @Override + public Statistics getStatistics() { + return statistics; + } + + @Override + public EnumSet getCaps() { + return caps; + } + + @SuppressWarnings("fallthrough") + public void initialize() { + ContextCapabilities ctxCaps = GLContext.getCapabilities(); + if (ctxCaps.OpenGL20) { + caps.add(Caps.OpenGL20); + if (ctxCaps.OpenGL21) { + caps.add(Caps.OpenGL21); + if (ctxCaps.OpenGL30) { + caps.add(Caps.OpenGL30); + if (ctxCaps.OpenGL31) { + caps.add(Caps.OpenGL31); + if (ctxCaps.OpenGL32) { + caps.add(Caps.OpenGL32); + } + } + } + } + } + + String versionStr = null; + if (ctxCaps.OpenGL20) { + versionStr = glGetString(GL_SHADING_LANGUAGE_VERSION); + } + if (versionStr == null || versionStr.equals("")) { + glslVer = -1; + throw new UnsupportedOperationException("GLSL and OpenGL2 is " + + "required for the LWJGL " + + "renderer!"); + } + + // Fix issue in TestRenderToMemory when GL_FRONT is the main + // buffer being used. + initialDrawBuf = glGetInteger(GL_DRAW_BUFFER); + initialReadBuf = glGetInteger(GL_READ_BUFFER); + + // XXX: This has to be GL_BACK for canvas on Mac + // Since initialDrawBuf is GL_FRONT for pbuffer, gotta + // change this value later on ... +// initialDrawBuf = GL_BACK; +// initialReadBuf = GL_BACK; + + int spaceIdx = versionStr.indexOf(" "); + if (spaceIdx >= 1) { + versionStr = versionStr.substring(0, spaceIdx); + } + + float version = Float.parseFloat(versionStr); + glslVer = (int) (version * 100); + + switch (glslVer) { + default: + if (glslVer < 400) { + break; + } + + // so that future OpenGL revisions wont break jme3 + + // fall through intentional + case 400: + case 330: + case 150: + caps.add(Caps.GLSL150); + case 140: + caps.add(Caps.GLSL140); + case 130: + caps.add(Caps.GLSL130); + case 120: + caps.add(Caps.GLSL120); + case 110: + caps.add(Caps.GLSL110); + case 100: + caps.add(Caps.GLSL100); + break; + } + + if (!caps.contains(Caps.GLSL100)) { + logger.log(Level.WARNING, "Force-adding GLSL100 support, since OpenGL2 is supported."); + caps.add(Caps.GLSL100); + } + + glGetInteger(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, intBuf16); + vertexTextureUnits = intBuf16.get(0); + logger.log(Level.FINER, "VTF Units: {0}", vertexTextureUnits); + if (vertexTextureUnits > 0) { + caps.add(Caps.VertexTextureFetch); + } + + glGetInteger(GL_MAX_TEXTURE_IMAGE_UNITS, intBuf16); + fragTextureUnits = intBuf16.get(0); + logger.log(Level.FINER, "Texture Units: {0}", fragTextureUnits); + + glGetInteger(GL_MAX_VERTEX_UNIFORM_COMPONENTS, intBuf16); + vertexUniforms = intBuf16.get(0); + logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms); + + glGetInteger(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, intBuf16); + fragUniforms = intBuf16.get(0); + logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms); + + glGetInteger(GL_MAX_VERTEX_ATTRIBS, intBuf16); + vertexAttribs = intBuf16.get(0); + logger.log(Level.FINER, "Vertex Attributes: {0}", vertexAttribs); + + glGetInteger(GL_SUBPIXEL_BITS, intBuf16); + int subpixelBits = intBuf16.get(0); + logger.log(Level.FINER, "Subpixel Bits: {0}", subpixelBits); + + glGetInteger(GL_MAX_ELEMENTS_VERTICES, intBuf16); + maxVertCount = intBuf16.get(0); + logger.log(Level.FINER, "Preferred Batch Vertex Count: {0}", maxVertCount); + + glGetInteger(GL_MAX_ELEMENTS_INDICES, intBuf16); + maxTriCount = intBuf16.get(0); + logger.log(Level.FINER, "Preferred Batch Index Count: {0}", maxTriCount); + + glGetInteger(GL_MAX_TEXTURE_SIZE, intBuf16); + maxTexSize = intBuf16.get(0); + logger.log(Level.FINER, "Maximum Texture Resolution: {0}", maxTexSize); + + glGetInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE, intBuf16); + maxCubeTexSize = intBuf16.get(0); + logger.log(Level.FINER, "Maximum CubeMap Resolution: {0}", maxCubeTexSize); + + if (ctxCaps.GL_ARB_color_buffer_float) { + // XXX: Require both 16 and 32 bit float support for FloatColorBuffer. + if (ctxCaps.GL_ARB_half_float_pixel) { + caps.add(Caps.FloatColorBuffer); + } + } + + if (ctxCaps.GL_ARB_depth_buffer_float) { + caps.add(Caps.FloatDepthBuffer); + } + + if (ctxCaps.OpenGL30) { + caps.add(Caps.PackedDepthStencilBuffer); + } + + if (ctxCaps.GL_ARB_draw_instanced) { + caps.add(Caps.MeshInstancing); + } + + if (ctxCaps.GL_ARB_fragment_program) { + caps.add(Caps.ARBprogram); + } + + if (ctxCaps.GL_ARB_texture_buffer_object) { + caps.add(Caps.TextureBuffer); + } + + if (ctxCaps.GL_ARB_texture_float) { + if (ctxCaps.GL_ARB_half_float_pixel) { + caps.add(Caps.FloatTexture); + } + } + + if (ctxCaps.GL_ARB_vertex_array_object) { + caps.add(Caps.VertexBufferArray); + } + + if (ctxCaps.GL_ARB_texture_non_power_of_two) { + caps.add(Caps.NonPowerOfTwoTextures); + } else { + logger.log(Level.WARNING, "Your graphics card does not " + + "support non-power-of-2 textures. " + + "Some features might not work."); + } + + boolean latc = ctxCaps.GL_EXT_texture_compression_latc; + if (latc) { + caps.add(Caps.TextureCompressionLATC); + } + + 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 || ctxCaps.OpenGL30) { + 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); + } + + glGetInteger(GL_MAX_DRAW_BUFFERS, intBuf16); + maxMRTFBOAttachs = intBuf16.get(0); + if (maxMRTFBOAttachs > 1) { + caps.add(Caps.FrameBufferMRT); + logger.log(Level.FINER, "FBO Max MRT renderbuffers: {0}", maxMRTFBOAttachs); + } + +// 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); + } + caps.add(Caps.Multisample); + } + + logger.log(Level.FINE, "Caps: {0}", caps); + } + + public void invalidateState() { + context.reset(); + boundShader = null; + lastFb = null; + + initialDrawBuf = glGetInteger(GL_DRAW_BUFFER); + initialReadBuf = glGetInteger(GL_READ_BUFFER); + } + + public void resetGLObjects() { + logger.log(Level.FINE, "Reseting objects and invalidating state"); + objManager.resetObjects(); + statistics.clearMemory(); + invalidateState(); + } + + public void cleanup() { + logger.log(Level.FINE, "Deleting objects and invalidating state"); + objManager.deleteAllObjects(this); + statistics.clearMemory(); + invalidateState(); + } + + private void checkCap(Caps cap) { + if (!caps.contains(cap)) { + throw new UnsupportedOperationException("Required capability missing: " + cap.name()); + } + } + + /*********************************************************************\ + |* Render State *| + \*********************************************************************/ + public void setDepthRange(float start, float end) { + glDepthRange(start, end); + } + + public void clearBuffers(boolean color, boolean depth, boolean stencil) { + int bits = 0; + if (color) { + //See explanations of the depth below, we must enable color write to be able to clear the color buffer + if (context.colorWriteEnabled == false) { + glColorMask(true, true, true, true); + context.colorWriteEnabled = true; + } + bits = GL_COLOR_BUFFER_BIT; + } + if (depth) { + + //glClear(GL_DEPTH_BUFFER_BIT) seems to not work when glDepthMask is false + //here s some link on openl board + //http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=257223 + //if depth clear is requested, we enable the depthMask + if (context.depthWriteEnabled == false) { + glDepthMask(true); + context.depthWriteEnabled = true; + } + bits |= GL_DEPTH_BUFFER_BIT; + } + if (stencil) { + bits |= GL_STENCIL_BUFFER_BIT; + } + if (bits != 0) { + glClear(bits); + } + } + + public void setBackgroundColor(ColorRGBA color) { + glClearColor(color.r, color.g, color.b, color.a); + } + + public void setAlphaToCoverage(boolean value) { + if (caps.contains(Caps.Multisample)) { + if (value) { + glEnable(ARBMultisample.GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); + } else { + glDisable(ARBMultisample.GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); + } + } + } + + public void applyRenderState(RenderState state) { + if (state.isWireframe() && !context.wireframe) { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + context.wireframe = true; + } else if (!state.isWireframe() && context.wireframe) { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + context.wireframe = false; + } + + if (state.isDepthTest() && !context.depthTestEnabled) { + glEnable(GL_DEPTH_TEST); + glDepthFunc(convertTestFunction(context.depthFunc)); + context.depthTestEnabled = true; + } else if (!state.isDepthTest() && context.depthTestEnabled) { + glDisable(GL_DEPTH_TEST); + context.depthTestEnabled = false; + } + if (state.getDepthFunc() != context.depthFunc) { + glDepthFunc(convertTestFunction(state.getDepthFunc())); + context.depthFunc = state.getDepthFunc(); + } + + if (state.isAlphaTest() && !context.alphaTestEnabled) { + glEnable(GL_ALPHA_TEST); + glAlphaFunc(convertTestFunction(context.alphaFunc), context.alphaTestFallOff); + context.alphaTestEnabled = true; + } else if (!state.isAlphaTest() && context.alphaTestEnabled) { + glDisable(GL_ALPHA_TEST); + context.alphaTestEnabled = false; + } + if (state.getAlphaFallOff() != context.alphaTestFallOff) { + glAlphaFunc(convertTestFunction(context.alphaFunc), context.alphaTestFallOff); + context.alphaTestFallOff = state.getAlphaFallOff(); + } + if (state.getAlphaFunc() != context.alphaFunc) { + glAlphaFunc(convertTestFunction(state.getAlphaFunc()), context.alphaTestFallOff); + context.alphaFunc = state.getAlphaFunc(); + } + + if (state.isDepthWrite() && !context.depthWriteEnabled) { + glDepthMask(true); + context.depthWriteEnabled = true; + } else if (!state.isDepthWrite() && context.depthWriteEnabled) { + glDepthMask(false); + context.depthWriteEnabled = false; + } + + if (state.isColorWrite() && !context.colorWriteEnabled) { + glColorMask(true, true, true, true); + context.colorWriteEnabled = true; + } else if (!state.isColorWrite() && context.colorWriteEnabled) { + glColorMask(false, false, false, false); + context.colorWriteEnabled = false; + } + + if (state.isPointSprite() && !context.pointSprite) { + // Only enable/disable sprite + if (context.boundTextures[0] != null) { + if (context.boundTextureUnit != 0) { + glActiveTexture(GL_TEXTURE0); + context.boundTextureUnit = 0; + } + glEnable(GL_POINT_SPRITE); + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); + } + context.pointSprite = true; + } else if (!state.isPointSprite() && context.pointSprite) { + if (context.boundTextures[0] != null) { + if (context.boundTextureUnit != 0) { + glActiveTexture(GL_TEXTURE0); + context.boundTextureUnit = 0; + } + glDisable(GL_POINT_SPRITE); + glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); + context.pointSprite = false; + } + } + + if (state.isPolyOffset()) { + if (!context.polyOffsetEnabled) { + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + context.polyOffsetEnabled = true; + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } else { + if (state.getPolyOffsetFactor() != context.polyOffsetFactor + || state.getPolyOffsetUnits() != context.polyOffsetUnits) { + glPolygonOffset(state.getPolyOffsetFactor(), + state.getPolyOffsetUnits()); + context.polyOffsetFactor = state.getPolyOffsetFactor(); + context.polyOffsetUnits = state.getPolyOffsetUnits(); + } + } + } else { + if (context.polyOffsetEnabled) { + glDisable(GL_POLYGON_OFFSET_FILL); + context.polyOffsetEnabled = false; + context.polyOffsetFactor = 0; + context.polyOffsetUnits = 0; + } + } + + if (state.getFaceCullMode() != context.cullMode) { + if (state.getFaceCullMode() == RenderState.FaceCullMode.Off) { + glDisable(GL_CULL_FACE); + } else { + glEnable(GL_CULL_FACE); + } + + switch (state.getFaceCullMode()) { + case Off: + break; + case Back: + glCullFace(GL_BACK); + break; + case Front: + glCullFace(GL_FRONT); + break; + case FrontAndBack: + glCullFace(GL_FRONT_AND_BACK); + break; + default: + throw new UnsupportedOperationException("Unrecognized face cull mode: " + + state.getFaceCullMode()); + } + + context.cullMode = state.getFaceCullMode(); + } + + if (state.getBlendMode() != context.blendMode) { + if (state.getBlendMode() == RenderState.BlendMode.Off) { + glDisable(GL_BLEND); + } else { + glEnable(GL_BLEND); + switch (state.getBlendMode()) { + case Off: + break; + case Additive: + glBlendFunc(GL_ONE, GL_ONE); + break; + case AlphaAdditive: + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + case Color: + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); + break; + case Alpha: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case PremultAlpha: + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + break; + case Modulate: + glBlendFunc(GL_DST_COLOR, GL_ZERO); + break; + case ModulateX2: + glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); + break; + default: + throw new UnsupportedOperationException("Unrecognized blend mode: " + + state.getBlendMode()); + } + } + + context.blendMode = state.getBlendMode(); + } + + if (context.stencilTest != state.isStencilTest() + || context.frontStencilStencilFailOperation != state.getFrontStencilStencilFailOperation() + || context.frontStencilDepthFailOperation != state.getFrontStencilDepthFailOperation() + || context.frontStencilDepthPassOperation != state.getFrontStencilDepthPassOperation() + || context.backStencilStencilFailOperation != state.getBackStencilStencilFailOperation() + || context.backStencilDepthFailOperation != state.getBackStencilDepthFailOperation() + || context.backStencilDepthPassOperation != state.getBackStencilDepthPassOperation() + || context.frontStencilFunction != state.getFrontStencilFunction() + || context.backStencilFunction != state.getBackStencilFunction()) { + + context.frontStencilStencilFailOperation = state.getFrontStencilStencilFailOperation(); //terrible looking, I know + context.frontStencilDepthFailOperation = state.getFrontStencilDepthFailOperation(); + context.frontStencilDepthPassOperation = state.getFrontStencilDepthPassOperation(); + context.backStencilStencilFailOperation = state.getBackStencilStencilFailOperation(); + context.backStencilDepthFailOperation = state.getBackStencilDepthFailOperation(); + context.backStencilDepthPassOperation = state.getBackStencilDepthPassOperation(); + context.frontStencilFunction = state.getFrontStencilFunction(); + context.backStencilFunction = state.getBackStencilFunction(); + + if (state.isStencilTest()) { + glEnable(GL_STENCIL_TEST); + glStencilOpSeparate(GL_FRONT, + convertStencilOperation(state.getFrontStencilStencilFailOperation()), + convertStencilOperation(state.getFrontStencilDepthFailOperation()), + convertStencilOperation(state.getFrontStencilDepthPassOperation())); + glStencilOpSeparate(GL_BACK, + convertStencilOperation(state.getBackStencilStencilFailOperation()), + convertStencilOperation(state.getBackStencilDepthFailOperation()), + convertStencilOperation(state.getBackStencilDepthPassOperation())); + glStencilFuncSeparate(GL_FRONT, + convertTestFunction(state.getFrontStencilFunction()), + 0, Integer.MAX_VALUE); + glStencilFuncSeparate(GL_BACK, + convertTestFunction(state.getBackStencilFunction()), + 0, Integer.MAX_VALUE); + } else { + glDisable(GL_STENCIL_TEST); + } + } + } + + private int convertStencilOperation(StencilOperation stencilOp) { + switch (stencilOp) { + case Keep: + return GL_KEEP; + case Zero: + return GL_ZERO; + case Replace: + return GL_REPLACE; + case Increment: + return GL_INCR; + case IncrementWrap: + return GL_INCR_WRAP; + case Decrement: + return GL_DECR; + case DecrementWrap: + return GL_DECR_WRAP; + case Invert: + return GL_INVERT; + default: + throw new UnsupportedOperationException("Unrecognized stencil operation: " + stencilOp); + } + } + + private int convertTestFunction(TestFunction testFunc) { + switch (testFunc) { + case Never: + return GL_NEVER; + case Less: + return GL_LESS; + case LessOrEqual: + return GL_LEQUAL; + case Greater: + return GL_GREATER; + case GreaterOrEqual: + return GL_GEQUAL; + case Equal: + return GL_EQUAL; + case NotEqual: + return GL_NOTEQUAL; + case Always: + return GL_ALWAYS; + default: + throw new UnsupportedOperationException("Unrecognized test function: " + testFunc); + } + } + + /*********************************************************************\ + |* Camera and World transforms *| + \*********************************************************************/ + public void setViewPort(int x, int y, int w, int h) { + if (x != vpX || vpY != y || vpW != w || vpH != h) { + glViewport(x, y, w, h); + vpX = x; + vpY = y; + vpW = w; + vpH = h; + } + } + + public void setClipRect(int x, int y, int width, int height) { + if (!context.clipRectEnabled) { + glEnable(GL_SCISSOR_TEST); + context.clipRectEnabled = true; + } + if (clipX != x || clipY != y || clipW != width || clipH != height) { + glScissor(x, y, width, height); + clipX = x; + clipY = y; + clipW = width; + clipH = height; + } + } + + public void clearClipRect() { + if (context.clipRectEnabled) { + glDisable(GL_SCISSOR_TEST); + context.clipRectEnabled = false; + + clipX = 0; + clipY = 0; + clipW = 0; + clipH = 0; + } + } + + public void onFrame() { + objManager.deleteUnused(this); +// statistics.clearFrame(); + } + + public void setWorldMatrix(Matrix4f worldMatrix) { + } + + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { + } + + /*********************************************************************\ + |* Shaders *| + \*********************************************************************/ + protected void updateUniformLocation(Shader shader, Uniform uniform) { + stringBuf.setLength(0); + stringBuf.append(uniform.getName()).append('\0'); + updateNameBuffer(); + int loc = glGetUniformLocation(shader.getId(), nameBuf); + if (loc < 0) { + uniform.setLocation(-1); + // uniform is not declared in shader + logger.log(Level.FINE, "Uniform {0} is not declared in shader {1}.", new Object[]{uniform.getName(), shader.getSources()}); + } else { + uniform.setLocation(loc); + } + } + + protected void bindProgram(Shader shader) { + int shaderId = shader.getId(); + if (context.boundShaderProgram != shaderId) { + glUseProgram(shaderId); + statistics.onShaderUse(shader, true); + boundShader = shader; + context.boundShaderProgram = shaderId; + } else { + statistics.onShaderUse(shader, false); + } + } + + protected void updateUniform(Shader shader, Uniform uniform) { + int shaderId = shader.getId(); + + assert uniform.getName() != null; + assert shader.getId() > 0; + + bindProgram(shader); + + int loc = uniform.getLocation(); + if (loc == -1) { + return; + } + + if (loc == -2) { + // get uniform location + updateUniformLocation(shader, uniform); + if (uniform.getLocation() == -1) { + // not declared, ignore + uniform.clearUpdateNeeded(); + return; + } + loc = uniform.getLocation(); + } + + if (uniform.getVarType() == null) { + return; // value not set yet.. + } + statistics.onUniformSet(); + + uniform.clearUpdateNeeded(); + FloatBuffer fb; + IntBuffer ib; + 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 IntArray: + ib = (IntBuffer) uniform.getValue(); + glUniform1(loc, ib); + 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 (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 (int i = 0; i < uniforms.size(); i++) { + Uniform uniform = uniforms.getValue(i); + uniform.reset(); // e.g check location again + } + } + + /* + * (Non-javadoc) + * Only used for fixed-function. Ignored. + */ + public void setLighting(LightList list) { + } + + public int convertShaderType(ShaderType type) { + switch (type) { + case Fragment: + return GL_FRAGMENT_SHADER; + case Vertex: + return GL_VERTEX_SHADER; +// case Geometry: +// return ARBGeometryShader4.GL_GEOMETRY_SHADER_ARB; + default: + throw new UnsupportedOperationException("Unrecognized shader type."); + } + } + + public void updateShaderSourceData(ShaderSource source) { + int id = source.getId(); + if (id == -1) { + // Create id + id = glCreateShader(convertShaderType(source.getType())); + if (id <= 0) { + throw new RendererException("Invalid ID received when trying to create shader."); + } + + source.setId(id); + } else { + throw new RendererException("Cannot recompile shader source"); + } + + // Upload shader source. + // Merge the defines and source code. + String language = source.getLanguage(); + 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.FINE, "{0} compile success\n{1}", + new Object[]{source.getName(), infoLog}); + } else { + logger.log(Level.FINE, "{0} compile success", source.getName()); + } + source.clearUpdateNeeded(); + } else { + logger.log(Level.WARNING, "Bad compile of:\n{0}", + new Object[]{ShaderDebug.formatShaderSource(source.getDefines(), source.getSource(), stringBuf.toString())}); + if (infoLog != null) { + throw new RendererException("compile error in:" + source + " error:" + infoLog); + } else { + throw new RendererException("compile error in:" + source + " error: "); + } + } + } + + 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); + } + glAttachShader(id, source.getId()); + } + + if (caps.contains(Caps.OpenGL30)) { + // Check if GLSL version is 1.5 for shader + GL30.glBindFragDataLocation(id, 0, "outFragColor"); + // For MRT + for (int i = 0; i < maxMRTFBOAttachs; i++) { + GL30.glBindFragDataLocation(id, i, "outFragData[" + i + "]"); + } + } + + // Link shaders to program + glLinkProgram(id); + + // Check link status + 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.FINE, "shader link success. \n{0}", infoLog); + } else { + logger.fine("shader link success"); + } + shader.clearUpdateNeeded(); + if (needRegister) { + // Register shader for clean up if it was created in this method. + objManager.registerObject(shader); + statistics.onNewShader(); + } else { + // OpenGL spec: uniform locations may change after re-link + resetUniformLocations(shader); + } + } else { + if (infoLog != null) { + throw new RendererException("Shader link failure, shader:" + shader + " info:" + infoLog); + } else { + throw new RendererException("Shader link failure, shader:" + shader + " info: "); + } + } + } + + public void setShader(Shader shader) { + if (shader == null) { + throw new IllegalArgumentException("Shader cannot be null"); + } else { + if (shader.isUpdateNeeded()) { + updateShaderData(shader); + } + + // NOTE: might want to check if any of the + // sources need an update? + + assert shader.getId() > 0; + + updateShaderUniforms(shader); + bindProgram(shader); + } + } + + public void deleteShaderSource(ShaderSource source) { + if (source.getId() < 0) { + logger.warning("Shader source is not uploaded to GPU, cannot delete."); + return; + } + source.clearUpdateNeeded(); + glDeleteShader(source.getId()); + source.resetObject(); + } + + public void deleteShader(Shader shader) { + if (shader.getId() == -1) { + logger.warning("Shader is not uploaded to GPU, cannot delete."); + return; + } + + for (ShaderSource source : shader.getSources()) { + if (source.getId() != -1) { + glDetachShader(shader.getId(), source.getId()); + deleteShaderSource(source); + } + } + + glDeleteProgram(shader.getId()); + statistics.onDeleteShader(); + shader.resetObject(); + } + + /*********************************************************************\ + |* Framebuffers *| + \*********************************************************************/ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { + copyFrameBuffer(src, dst, true); + } + + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { + if (GLContext.getCapabilities().GL_EXT_framebuffer_blit) { + int srcX0 = 0; + int srcY0 = 0; + int srcX1 = 0; + int srcY1 = 0; + + int dstX0 = 0; + int dstY0 = 0; + int dstX1 = 0; + int dstY1 = 0; + + int prevFBO = context.boundFBO; + + if (mainFbOverride != null) { + if (src == null) { + src = mainFbOverride; + } + if (dst == null) { + dst = mainFbOverride; + } + } + + if (src != null && src.isUpdateNeeded()) { + updateFrameBuffer(src); + } + + if (dst != null && dst.isUpdateNeeded()) { + updateFrameBuffer(dst); + } + + if (src == null) { + glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); + srcX0 = vpX; + srcY0 = vpY; + srcX1 = vpX + vpW; + srcY1 = vpY + vpH; + } else { + glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, src.getId()); + srcX1 = src.getWidth(); + srcY1 = src.getHeight(); + } + if (dst == null) { + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0); + dstX0 = vpX; + dstY0 = vpY; + dstX1 = vpX + vpW; + dstY1 = vpY + vpH; + } else { + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, dst.getId()); + dstX1 = dst.getWidth(); + dstY1 = dst.getHeight(); + } + int mask = GL_COLOR_BUFFER_BIT; + if (copyDepth) { + mask |= GL_DEPTH_BUFFER_BIT; + } + glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, + dstX0, dstY0, dstX1, dstY1, mask, + GL_NEAREST); + + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, prevFBO); + try { + checkFrameBufferError(); + } catch (IllegalStateException ex) { + logger.log(Level.SEVERE, "Source FBO:\n{0}", src); + logger.log(Level.SEVERE, "Dest FBO:\n{0}", dst); + throw ex; + } + } else { + throw new RendererException("EXT_framebuffer_blit required."); + // TODO: support non-blit copies? + } + } + + private String getTargetBufferName(int buffer) { + switch (buffer) { + case GL_NONE: + return "NONE"; + case GL_FRONT: + return "GL_FRONT"; + case GL_BACK: + return "GL_BACK"; + default: + if (buffer >= GL_COLOR_ATTACHMENT0_EXT + && buffer <= GL_COLOR_ATTACHMENT15_EXT) { + return "GL_COLOR_ATTACHMENT" + + (buffer - GL_COLOR_ATTACHMENT0_EXT); + } else { + return "UNKNOWN? " + buffer; + } + } + } + + private void printRealRenderBufferInfo(FrameBuffer fb, RenderBuffer rb, String name) { + System.out.println("== Renderbuffer " + name + " =="); + System.out.println("RB ID: " + rb.getId()); + System.out.println("Is proper? " + glIsRenderbufferEXT(rb.getId())); + + int attachment = convertAttachmentSlot(rb.getSlot()); + + int type = glGetFramebufferAttachmentParameterEXT(GL_DRAW_FRAMEBUFFER_EXT, + attachment, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT); + + int rbName = glGetFramebufferAttachmentParameterEXT(GL_DRAW_FRAMEBUFFER_EXT, + attachment, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT); + + switch (type) { + case GL_NONE: + System.out.println("Type: None"); + break; + case GL_TEXTURE: + System.out.println("Type: Texture"); + break; + case GL_RENDERBUFFER_EXT: + System.out.println("Type: Buffer"); + System.out.println("RB ID: " + rbName); + break; + } + + + + } + + private void printRealFrameBufferInfo(FrameBuffer fb) { + boolean doubleBuffer = glGetBoolean(GL_DOUBLEBUFFER); + String drawBuf = getTargetBufferName(glGetInteger(GL_DRAW_BUFFER)); + String readBuf = getTargetBufferName(glGetInteger(GL_READ_BUFFER)); + + int fbId = fb.getId(); + int curDrawBinding = glGetInteger(ARBFramebufferObject.GL_DRAW_FRAMEBUFFER_BINDING); + int curReadBinding = glGetInteger(ARBFramebufferObject.GL_READ_FRAMEBUFFER_BINDING); + + System.out.println("=== OpenGL FBO State ==="); + System.out.println("Context doublebuffered? " + doubleBuffer); + System.out.println("FBO ID: " + fbId); + System.out.println("Is proper? " + glIsFramebufferEXT(fbId)); + System.out.println("Is bound to draw? " + (fbId == curDrawBinding)); + System.out.println("Is bound to read? " + (fbId == curReadBinding)); + System.out.println("Draw buffer: " + drawBuf); + System.out.println("Read buffer: " + readBuf); + + if (context.boundFBO != fbId) { + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, fbId); + context.boundFBO = fbId; + } + + if (fb.getDepthBuffer() != null) { + printRealRenderBufferInfo(fb, fb.getDepthBuffer(), "Depth"); + } + for (int i = 0; i < fb.getNumColorBuffers(); i++) { + printRealRenderBufferInfo(fb, fb.getColorBuffer(i), "Color" + i); + } + } + + private void checkFrameBufferError() { + int status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + switch (status) { + case GL_FRAMEBUFFER_COMPLETE_EXT: + break; + case GL_FRAMEBUFFER_UNSUPPORTED_EXT: + //Choose different formats + throw new IllegalStateException("Framebuffer object format is " + + "unsupported by the video hardware."); + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: + throw new IllegalStateException("Framebuffer has erronous attachment."); + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: + throw new IllegalStateException("Framebuffer doesn't have any renderbuffers attached."); + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: + throw new IllegalStateException("Framebuffer attachments must have same dimensions."); + case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: + throw new IllegalStateException("Framebuffer attachments must have same formats."); + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: + throw new IllegalStateException("Incomplete draw buffer."); + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: + throw new IllegalStateException("Incomplete read buffer."); + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: + throw new IllegalStateException("Incomplete multisample buffer."); + default: + //Programming error; will fail on all hardware + throw new IllegalStateException("Some video driver error " + + "or programming error occured. " + + "Framebuffer object status is invalid. "); + } + } + + private void updateRenderBuffer(FrameBuffer fb, RenderBuffer rb) { + int id = rb.getId(); + if (id == -1) { + glGenRenderbuffersEXT(intBuf1); + id = intBuf1.get(0); + rb.setId(id); + } + + if (context.boundRB != id) { + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, id); + context.boundRB = id; + } + + if (fb.getWidth() > maxRBSize || fb.getHeight() > maxRBSize) { + throw new RendererException("Resolution " + fb.getWidth() + + ":" + fb.getHeight() + " is not supported."); + } + + TextureUtil.GLImageFormat glFmt = TextureUtil.getImageFormatWithError(rb.getFormat()); + + if (fb.getSamples() > 1 && GLContext.getCapabilities().GL_EXT_framebuffer_multisample) { + int samples = fb.getSamples(); + if (maxFBOSamples < samples) { + samples = maxFBOSamples; + } + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, + samples, + glFmt.internalFormat, + fb.getWidth(), + fb.getHeight()); + } else { + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, + glFmt.internalFormat, + 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(), 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(), rb.getFace()), + 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.registerObject(fb); + + statistics.onNewFrameBuffer(); + } + + if (context.boundFBO != id) { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id); + // binding an FBO automatically sets draw buf to GL_COLOR_ATTACHMENT0 + context.boundDrawBuf = 0; + context.boundFBO = id; + } + + FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer(); + if (depthBuf != null) { + updateFrameBufferAttachment(fb, depthBuf); + } + + for (int i = 0; i < fb.getNumColorBuffers(); i++) { + FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i); + updateFrameBufferAttachment(fb, colorBuf); + } + + fb.clearUpdateNeeded(); + } + + public Vector2f[] getFrameBufferSamplePositions(FrameBuffer fb) { + if (fb.getSamples() <= 1) { + throw new IllegalArgumentException("Framebuffer must be multisampled"); + } + + setFrameBuffer(fb); + + Vector2f[] samplePositions = new Vector2f[fb.getSamples()]; + FloatBuffer samplePos = BufferUtils.createFloatBuffer(2); + for (int i = 0; i < samplePositions.length; i++) { + glGetMultisample(GL_SAMPLE_POSITION, i, samplePos); + samplePos.clear(); + samplePositions[i] = new Vector2f(samplePos.get(0) - 0.5f, + samplePos.get(1) - 0.5f); + } + return samplePositions; + } + + public void setMainFrameBufferOverride(FrameBuffer fb) { + mainFbOverride = fb; + } + + public void setFrameBuffer(FrameBuffer fb) { + if (fb == null && mainFbOverride != null) { + fb = mainFbOverride; + } + + if (lastFb == fb) { + if (fb == null || !fb.isUpdateNeeded()) { + return; + } + } + + // generate mipmaps for last FB if needed + if (lastFb != null) { + for (int i = 0; i < lastFb.getNumColorBuffers(); i++) { + RenderBuffer rb = lastFb.getColorBuffer(i); + Texture tex = rb.getTexture(); + if (tex != null + && tex.getMinFilter().usesMipMapLevels()) { + setTexture(0, rb.getTexture()); + + int textureType = convertTextureType(tex.getType(), tex.getImage().getMultiSamples(), rb.getFace()); + glEnable(textureType); + glGenerateMipmapEXT(textureType); + glDisable(textureType); + } + } + } + + if (fb == null) { + // unbind any fbos + if (context.boundFBO != 0) { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + statistics.onFrameBufferUse(null, true); + + context.boundFBO = 0; + } + // select back buffer + if (context.boundDrawBuf != -1) { + glDrawBuffer(initialDrawBuf); + context.boundDrawBuf = -1; + } + if (context.boundReadBuf != -1) { + glReadBuffer(initialReadBuf); + context.boundReadBuf = -1; + } + + lastFb = null; + } else { + if (fb.getNumColorBuffers() == 0 && fb.getDepthBuffer() == null) { + throw new IllegalArgumentException("The framebuffer: " + fb + + "\nDoesn't have any color/depth buffers"); + } + + if (fb.isUpdateNeeded()) { + updateFrameBuffer(fb); + } + + if (context.boundFBO != fb.getId()) { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb.getId()); + statistics.onFrameBufferUse(fb, true); + + // update viewport to reflect framebuffer's resolution + setViewPort(0, 0, fb.getWidth(), fb.getHeight()); + + context.boundFBO = fb.getId(); + } else { + statistics.onFrameBufferUse(fb, false); + } + if (fb.getNumColorBuffers() == 0) { + // make sure to select NONE as draw buf + // no color buffer attached. select NONE + if (context.boundDrawBuf != -2) { + glDrawBuffer(GL_NONE); + context.boundDrawBuf = -2; + } + if (context.boundReadBuf != -2) { + glReadBuffer(GL_NONE); + context.boundReadBuf = -2; + } + } else { + if (fb.getNumColorBuffers() > maxFBOAttachs) { + throw new RendererException("Framebuffer has more color " + + "attachments than are supported" + + " by the video hardware!"); + } + if (fb.isMultiTarget()) { + if (fb.getNumColorBuffers() > maxMRTFBOAttachs) { + throw new RendererException("Framebuffer has more" + + " multi targets than are supported" + + " by the video hardware!"); + } + + if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()) { + intBuf16.clear(); + for (int i = 0; i < fb.getNumColorBuffers(); i++) { + intBuf16.put(GL_COLOR_ATTACHMENT0_EXT + i); + } + + intBuf16.flip(); + glDrawBuffers(intBuf16); + context.boundDrawBuf = 100 + fb.getNumColorBuffers(); + } + } else { + RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex()); + // select this draw buffer + if (context.boundDrawBuf != rb.getSlot()) { + glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); + context.boundDrawBuf = rb.getSlot(); + } + } + } + + assert fb.getId() >= 0; + assert context.boundFBO == fb.getId(); + + lastFb = fb; + + try { + checkFrameBufferError(); + } catch (IllegalStateException ex) { + logger.log(Level.SEVERE, "=== jMonkeyEngine FBO State ===\n{0}", fb); + printRealFrameBufferInfo(fb); + throw ex; + } + } + } + + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { + if (fb != null) { + RenderBuffer rb = fb.getColorBuffer(); + if (rb == null) { + throw new IllegalArgumentException("Specified framebuffer" + + " does not have a colorbuffer"); + } + + setFrameBuffer(fb); + if (context.boundReadBuf != rb.getSlot()) { + glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); + context.boundReadBuf = rb.getSlot(); + } + } else { + setFrameBuffer(null); + } + + glReadPixels(vpX, vpY, vpW, vpH, /*GL_RGBA*/ GL_BGRA, GL_UNSIGNED_BYTE, byteBuf); + } + + private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { + intBuf1.put(0, rb.getId()); + glDeleteRenderbuffersEXT(intBuf1); + } + + public void deleteFrameBuffer(FrameBuffer fb) { + if (fb.getId() != -1) { + if (context.boundFBO == fb.getId()) { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + context.boundFBO = 0; + } + + if (fb.getDepthBuffer() != null) { + deleteRenderBuffer(fb, fb.getDepthBuffer()); + } + if (fb.getColorBuffer() != null) { + deleteRenderBuffer(fb, fb.getColorBuffer()); + } + + intBuf1.put(0, fb.getId()); + glDeleteFramebuffersEXT(intBuf1); + fb.resetObject(); + + statistics.onDeleteFrameBuffer(); + } + } + + /*********************************************************************\ + |* Textures *| + \*********************************************************************/ + private int convertTextureType(Texture.Type type, int samples, int face) { + 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: + if (face < 0) { + return GL_TEXTURE_CUBE_MAP; + } else if (face < 6) { + return GL_TEXTURE_CUBE_MAP_POSITIVE_X + face; + } else { + throw new UnsupportedOperationException("Invalid cube map face index: " + face); + } + default: + throw new UnsupportedOperationException("Unknown texture type: " + type); + } + } + + private int convertMagFilter(Texture.MagFilter filter) { + switch (filter) { + case Bilinear: + return GL_LINEAR; + case Nearest: + return GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown mag filter: " + filter); + } + } + + private int convertMinFilter(Texture.MinFilter filter) { + switch (filter) { + case Trilinear: + return GL_LINEAR_MIPMAP_LINEAR; + case BilinearNearestMipMap: + return GL_LINEAR_MIPMAP_NEAREST; + case NearestLinearMipMap: + return GL_NEAREST_MIPMAP_LINEAR; + case NearestNearestMipMap: + return GL_NEAREST_MIPMAP_NEAREST; + case BilinearNoMipMaps: + return GL_LINEAR; + case NearestNoMipMaps: + return GL_NEAREST; + default: + throw new UnsupportedOperationException("Unknown min filter: " + filter); + } + } + + private int convertWrapMode(Texture.WrapMode mode) { + switch (mode) { + case BorderClamp: + return GL_CLAMP_TO_BORDER; + case Clamp: + return GL_CLAMP; + case EdgeClamp: + return GL_CLAMP_TO_EDGE; + case Repeat: + return GL_REPEAT; + case MirroredRepeat: + return GL_MIRRORED_REPEAT; + default: + throw new UnsupportedOperationException("Unknown wrap mode: " + mode); + } + } + + @SuppressWarnings("fallthrough") + private void setupTextureParams(Texture tex) { + Image image = tex.getImage(); + int target = convertTextureType(tex.getType(), image != null ? image.getMultiSamples() : 1, -1); + + // filter things + int minFilter = convertMinFilter(tex.getMinFilter()); + int magFilter = convertMagFilter(tex.getMagFilter()); + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, minFilter); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, magFilter); + + if (tex.getAnisotropicFilter() > 1) { + if (GLContext.getCapabilities().GL_EXT_texture_filter_anisotropic) { + glTexParameterf(target, + EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, + tex.getAnisotropicFilter()); + } + } + + if (context.pointSprite) { + return; // Attempt to fix glTexParameter crash for some ATI GPUs + } + + // repeat modes + switch (tex.getType()) { + case ThreeDimensional: + case CubeMap: // cubemaps use 3D coords + glTexParameteri(target, GL_TEXTURE_WRAP_R, convertWrapMode(tex.getWrap(WrapAxis.R))); + case TwoDimensional: + case TwoDimensionalArray: + glTexParameteri(target, GL_TEXTURE_WRAP_T, convertWrapMode(tex.getWrap(WrapAxis.T))); + // fall down here is intentional.. +// case OneDimensional: + glTexParameteri(target, GL_TEXTURE_WRAP_S, convertWrapMode(tex.getWrap(WrapAxis.S))); + break; + default: + throw new UnsupportedOperationException("Unknown texture type: " + tex.getType()); + } + + if(tex.isNeedCompareModeUpdate()){ + // 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); + } + }else{ + //restoring default value + glTexParameteri(target, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + tex.compareModeUpdated(); + } + } + + /** + * Uploads the given image to the GL driver. + * + * @param img The image to upload + * @param type How the data in the image argument should be interpreted. + * @param unit The texture slot to be used to upload the image, not important + */ + public void updateTexImageData(Image img, Texture.Type type, int unit) { + int texId = img.getId(); + if (texId == -1) { + // create texture + glGenTextures(intBuf1); + texId = intBuf1.get(0); + img.setId(texId); + objManager.registerObject(img); + + statistics.onNewTexture(); + } + + // bind texture + int target = convertTextureType(type, img.getMultiSamples(), -1); + 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() && img.isGeneratedMipmapsRequired()) { + // No pregenerated mips available, + // generate from base level if required + if (!GLContext.getCapabilities().OpenGL30) { + glTexParameteri(target, GL_GENERATE_MIPMAP, GL_TRUE); + img.setMipmapsGenerated(true); + } + } else { + // Image already has mipmaps or no mipmap generation desired. +// glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0 ); + if (img.getMipMapSizes() != null) { + glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1); + } + } + + int imageSamples = img.getMultiSamples(); + if (imageSamples > 1) { + if (img.getFormat().isDepthFormat()) { + img.setMultiSamples(Math.min(maxDepthTexSamples, imageSamples)); + } else { + img.setMultiSamples(Math.min(maxColorTexSamples, imageSamples)); + } + } + + // Yes, some OpenGL2 cards (GeForce 5) still dont support NPOT. + if (!GLContext.getCapabilities().GL_ARB_texture_non_power_of_two) { + if (img.getWidth() != 0 && img.getHeight() != 0) { + if (!FastMath.isPowerOfTwo(img.getWidth()) + || !FastMath.isPowerOfTwo(img.getHeight())) { + if (img.getData(0) == null) { + throw new RendererException("non-power-of-2 framebuffer textures are not supported by the video hardware"); + } else { + MipMapGenerator.resizeToPowerOf2(img); + } + } + } + } + + // Check if graphics card doesn't support multisample textures + if (!GLContext.getCapabilities().GL_ARB_texture_multisample) { + if (img.getMultiSamples() > 1) { + throw new RendererException("Multisample textures not supported by graphics hardware"); + } + } + + if (target == GL_TEXTURE_CUBE_MAP) { + // Check max texture size before upload + if (img.getWidth() > maxCubeTexSize || img.getHeight() > maxCubeTexSize) { + throw new RendererException("Cannot upload cubemap " + img + ". The maximum supported cubemap resolution is " + maxCubeTexSize); + } + } else { + if (img.getWidth() > maxTexSize || img.getHeight() > maxTexSize) { + throw new RendererException("Cannot upload texture " + img + ". The maximum supported texture resolution is " + maxTexSize); + } + } + + 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); + } + } else if (target == EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT) { + if (!caps.contains(Caps.TextureArray)) { + throw new RendererException("Texture arrays not supported by graphics hardware"); + } + + List data = img.getData(); + + // -1 index specifies prepare data for 2D Array + TextureUtil.uploadTexture(img, target, -1, 0); + + 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); + } + } else { + TextureUtil.uploadTexture(img, target, 0, 0); + } + + if (img.getMultiSamples() != imageSamples) { + img.setMultiSamples(imageSamples); + } + + if (GLContext.getCapabilities().OpenGL30) { + if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired() && img.getData() != null) { + // XXX: Required for ATI + glEnable(target); + glGenerateMipmapEXT(target); + glDisable(target); + img.setMipmapsGenerated(true); + } + } + + img.clearUpdateNeeded(); + } + + public void setTexture(int unit, Texture tex) { + Image image = tex.getImage(); + if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) { + updateTexImageData(image, tex.getType(), unit); + } + + int texId = image.getId(); + assert texId != -1; + + Image[] textures = context.boundTextures; + + int type = convertTextureType(tex.getType(), image.getMultiSamples(), -1); +// 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 modifyTexture(Texture tex, Image pixels, int x, int y) { + setTexture(0, tex); + TextureUtil.uploadSubTexture(pixels, convertTextureType(tex.getType(), pixels.getMultiSamples(), -1), 0, x, y); + } + + public void clearTextureUnits() { +// IDList textureList = context.textureIndexList; +// Image[] textures = context.boundTextures; +// for (int i = 0; i < textureList.oldLen; i++) { +// int idx = textureList.oldList[i]; +// if (context.boundTextureUnit != idx){ +// glActiveTexture(GL_TEXTURE0 + idx); +// context.boundTextureUnit = idx; +// } +// glDisable(convertTextureType(textures[idx].getType())); +// textures[idx] = null; +// } +// context.textureIndexList.copyNewToOld(); + } + + public void deleteImage(Image image) { + int texId = image.getId(); + if (texId != -1) { + intBuf1.put(0, texId); + intBuf1.position(0).limit(1); + glDeleteTextures(intBuf1); + image.resetObject(); + + statistics.onDeleteTexture(); + } + } + + /*********************************************************************\ + |* Vertex Buffers and Attributes *| + \*********************************************************************/ + private int convertUsage(Usage usage) { + switch (usage) { + case Static: + return GL_STATIC_DRAW; + case Dynamic: + return GL_DYNAMIC_DRAW; + case Stream: + return GL_STREAM_DRAW; + default: + throw new UnsupportedOperationException("Unknown usage type."); + } + } + + private int convertFormat(Format format) { + switch (format) { + case Byte: + return GL_BYTE; + case UnsignedByte: + return GL_UNSIGNED_BYTE; + case Short: + return GL_SHORT; + case UnsignedShort: + return GL_UNSIGNED_SHORT; + case Int: + return GL_INT; + case UnsignedInt: + return GL_UNSIGNED_INT; +// case Half: +// return NVHalfFloat.GL_HALF_FLOAT_NV; +// return ARBHalfFloatVertex.GL_HALF_FLOAT; + case Float: + return GL_FLOAT; + case Double: + return GL_DOUBLE; + default: + throw new UnsupportedOperationException("Unknown buffer format."); + + } + } + + public void updateBufferData(VertexBuffer vb) { + int bufId = vb.getId(); + boolean created = false; + if (bufId == -1) { + // create buffer + glGenBuffers(intBuf1); + bufId = intBuf1.get(0); + vb.setId(bufId); + objManager.registerObject(vb); + + //statistics.onNewVertexBuffer(); + + created = true; + } + + // bind buffer + int target; + if (vb.getBufferType() == VertexBuffer.Type.Index) { + target = GL_ELEMENT_ARRAY_BUFFER; + if (context.boundElementArrayVBO != bufId) { + glBindBuffer(target, bufId); + context.boundElementArrayVBO = bufId; + //statistics.onVertexBufferUse(vb, true); + } else { + //statistics.onVertexBufferUse(vb, false); + } + } else { + target = GL_ARRAY_BUFFER; + if (context.boundArrayVBO != bufId) { + glBindBuffer(target, bufId); + context.boundArrayVBO = bufId; + //statistics.onVertexBufferUse(vb, true); + } else { + //statistics.onVertexBufferUse(vb, false); + } + } + + int usage = convertUsage(vb.getUsage()); + vb.getData().rewind(); + + if (created || vb.hasDataSizeChanged()) { + // upload data based on format + switch (vb.getFormat()) { + case Byte: + case UnsignedByte: + glBufferData(target, (ByteBuffer) vb.getData(), usage); + break; + // case Half: + case Short: + case UnsignedShort: + glBufferData(target, (ShortBuffer) vb.getData(), usage); + break; + case Int: + case UnsignedInt: + glBufferData(target, (IntBuffer) vb.getData(), usage); + break; + case Float: + glBufferData(target, (FloatBuffer) vb.getData(), usage); + break; + case Double: + glBufferData(target, (DoubleBuffer) vb.getData(), usage); + break; + default: + throw new UnsupportedOperationException("Unknown buffer format."); + } + } else { + switch (vb.getFormat()) { + case Byte: + case UnsignedByte: + glBufferSubData(target, 0, (ByteBuffer) vb.getData()); + break; + case Short: + case UnsignedShort: + glBufferSubData(target, 0, (ShortBuffer) vb.getData()); + break; + case Int: + case UnsignedInt: + glBufferSubData(target, 0, (IntBuffer) vb.getData()); + break; + case Float: + glBufferSubData(target, 0, (FloatBuffer) vb.getData()); + break; + case Double: + glBufferSubData(target, 0, (DoubleBuffer) vb.getData()); + break; + default: + throw new UnsupportedOperationException("Unknown buffer format."); + } + } + + vb.clearUpdateNeeded(); + } + + public void deleteBuffer(VertexBuffer vb) { + int bufId = vb.getId(); + if (bufId != -1) { + // delete buffer + intBuf1.put(0, bufId); + intBuf1.position(0).limit(1); + glDeleteBuffers(intBuf1); + vb.resetObject(); + + //statistics.onDeleteVertexBuffer(); + } + } + + public void clearVertexAttribs() { + IDList attribList = context.attribIndexList; + for (int i = 0; i < attribList.oldLen; i++) { + int idx = attribList.oldList[i]; + glDisableVertexAttribArray(idx); + context.boundAttribs[idx] = null; + } + context.attribIndexList.copyNewToOld(); + } + + public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { + if (vb.getBufferType() == VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib"); + } + + int programId = context.boundShaderProgram; + if (programId > 0) { + Attribute attrib = boundShader.getAttribute(vb.getBufferType()); + int loc = attrib.getLocation(); + if (loc == -1) { + return; // not defined + } + if (loc == -2) { + stringBuf.setLength(0); + stringBuf.append("in").append(vb.getBufferType().name()).append('\0'); + updateNameBuffer(); + loc = glGetAttribLocation(programId, nameBuf); + + // not really the name of it in the shader (inPosition\0) but + // the internal name of the enum (Position). + if (loc < 0) { + attrib.setLocation(-1); + return; // not available in shader. + } else { + attrib.setLocation(loc); + } + } + + if (vb.isUpdateNeeded() && idb == null) { + updateBufferData(vb); + } + + VertexBuffer[] attribs = context.boundAttribs; + if (!context.attribIndexList.moveToNew(loc)) { + glEnableVertexAttribArray(loc); + //System.out.println("Enabled ATTRIB IDX: "+loc); + } + if (attribs[loc] != vb) { + // NOTE: Use id from interleaved buffer if specified + int bufId = idb != null ? idb.getId() : vb.getId(); + assert bufId != -1; + if (context.boundArrayVBO != bufId) { + glBindBuffer(GL_ARRAY_BUFFER, bufId); + context.boundArrayVBO = bufId; + //statistics.onVertexBufferUse(vb, true); + } else { + //statistics.onVertexBufferUse(vb, false); + } + + glVertexAttribPointer(loc, + vb.getNumComponents(), + convertFormat(vb.getFormat()), + vb.isNormalized(), + vb.getStride(), + vb.getOffset()); + + attribs[loc] = vb; + } + } else { + throw new IllegalStateException("Cannot render mesh without shader bound"); + } + } + + public void setVertexAttrib(VertexBuffer vb) { + setVertexAttrib(vb, null); + } + + public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) { + if (count > 1) { + ARBDrawInstanced.glDrawArraysInstancedARB(convertElementMode(mode), 0, + vertCount, count); + } else { + glDrawArrays(convertElementMode(mode), 0, vertCount); + } + } + + public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) { + if (indexBuf.getBufferType() != VertexBuffer.Type.Index) { + throw new IllegalArgumentException("Only index buffers are allowed as triangle lists."); + } + + if (indexBuf.isUpdateNeeded()) { + updateBufferData(indexBuf); + } + + int bufId = indexBuf.getId(); + assert bufId != -1; + + if (context.boundElementArrayVBO != bufId) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufId); + context.boundElementArrayVBO = bufId; + //statistics.onVertexBufferUse(indexBuf, true); + } else { + //statistics.onVertexBufferUse(indexBuf, true); + } + + int vertCount = mesh.getVertexCount(); + boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing); + + if (mesh.getMode() == Mode.Hybrid) { + int[] modeStart = mesh.getModeStart(); + int[] elementLengths = mesh.getElementLengths(); + + int elMode = convertElementMode(Mode.Triangles); + int fmt = convertFormat(indexBuf.getFormat()); + int elSize = indexBuf.getFormat().getComponentSize(); + int listStart = modeStart[0]; + int stripStart = modeStart[1]; + int fanStart = modeStart[2]; + int curOffset = 0; + for (int i = 0; i < elementLengths.length; i++) { + if (i == stripStart) { + elMode = convertElementMode(Mode.TriangleStrip); + } else if (i == fanStart) { + elMode = convertElementMode(Mode.TriangleFan); + } + 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); + } + + for (VertexBuffer vb : mesh.getBufferList().getArray()) { + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } + + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + } + + private void renderMeshVertexArray(Mesh mesh, int lod, int count) { + if (mesh.getId() == -1) { + updateVertexArray(mesh); + } else { + // TODO: Check if it was updated + } + + if (context.boundVertexArray != mesh.getId()) { + ARBVertexArrayObject.glBindVertexArray(mesh.getId()); + context.boundVertexArray = mesh.getId(); + } + +// IntMap buffers = mesh.getBuffers(); + VertexBuffer indices; + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = mesh.getBuffer(Type.Index); + } + if (indices != null) { + drawTriangleList(indices, mesh, count); + } else { + drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + private void renderMeshDefault(Mesh mesh, int lod, int count) { + VertexBuffer indices = null; + + VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); + if (interleavedData != null && interleavedData.isUpdateNeeded()) { + updateBufferData(interleavedData); + } + +// IntMap buffers = mesh.getBuffers(); + SafeArrayList buffersList = mesh.getBufferList(); + + if (mesh.getNumLodLevels() > 0) { + indices = mesh.getLodLevel(lod); + } else { + indices = mesh.getBuffer(Type.Index); + } + +// for (Entry entry : buffers) { +// VertexBuffer vb = entry.getValue(); + for (VertexBuffer vb : mesh.getBufferList().getArray()) { + if (vb.getBufferType() == Type.InterleavedData + || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers + || vb.getBufferType() == Type.Index) { + continue; + } + + if (vb.getStride() == 0) { + // not interleaved + setVertexAttrib(vb); + } else { + // interleaved + setVertexAttrib(vb, interleavedData); + } + } + + if (indices != null) { + drawTriangleList(indices, mesh, count); + } else { + drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount()); + } + clearVertexAttribs(); + clearTextureUnits(); + } + + public void renderMesh(Mesh mesh, int lod, int count) { + if (mesh.getVertexCount() == 0) { + return; + } + + if (context.pointSprite && mesh.getMode() != Mode.Points) { + // XXX: Hack, disable point sprite mode if mesh not in point mode + if (context.boundTextures[0] != null) { + if (context.boundTextureUnit != 0) { + glActiveTexture(GL_TEXTURE0); + context.boundTextureUnit = 0; + } + glDisable(GL_POINT_SPRITE); + glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); + context.pointSprite = false; + } + } + + if (context.pointSize != mesh.getPointSize()) { + glPointSize(mesh.getPointSize()); + context.pointSize = mesh.getPointSize(); + } + if (context.lineWidth != mesh.getLineWidth()) { + glLineWidth(mesh.getLineWidth()); + context.lineWidth = mesh.getLineWidth(); + } + + statistics.onMeshDrawn(mesh, lod); +// if (GLContext.getCapabilities().GL_ARB_vertex_array_object){ +// renderMeshVertexArray(mesh, lod, count); +// }else{ + renderMeshDefault(mesh, lod, count); +// } + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/TextureUtil.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/TextureUtil.java new file mode 100644 index 000000000..3fc0bceb8 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/TextureUtil.java @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.renderer.lwjgl; + +import java.nio.ByteBuffer; + +import org.lwjgl.opengl.ARBHalfFloatPixel; +import org.lwjgl.opengl.ARBTextureFloat; +import org.lwjgl.opengl.ARBTextureMultisample; +import org.lwjgl.opengl.ContextCapabilities; +import org.lwjgl.opengl.EXTAbgr; +import org.lwjgl.opengl.EXTBgra; +import org.lwjgl.opengl.EXTPackedFloat; +import org.lwjgl.opengl.EXTTextureArray; +import org.lwjgl.opengl.EXTTextureCompressionLATC; +import org.lwjgl.opengl.EXTTextureCompressionS3TC; +import org.lwjgl.opengl.EXTTextureSharedExponent; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; +import org.lwjgl.opengl.GL13; +import org.lwjgl.opengl.GL14; +import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GLContext; + +import com.jme3.renderer.RendererException; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; + +class TextureUtil { + + static class GLImageFormat { + + int internalFormat; + int format; + int dataType; + boolean compressed; + + public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed) { + this.internalFormat = internalFormat; + this.format = format; + this.dataType = dataType; + this.compressed = compressed; + } + } + + private static final GLImageFormat[] formatToGL = new GLImageFormat[Format.values().length]; + + private static void setFormat(Format format, int glInternalFormat, int glFormat, int glDataType, boolean glCompressed){ + formatToGL[format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType, glCompressed); + } + + static { + // Alpha formats + setFormat(Format.Alpha8, GL11.GL_ALPHA8, GL11.GL_ALPHA, GL11.GL_UNSIGNED_BYTE, false); + setFormat(Format.Alpha16, GL11.GL_ALPHA16, GL11.GL_ALPHA, GL11.GL_UNSIGNED_SHORT, false); + + // Luminance formats + setFormat(Format.Luminance8, GL11.GL_LUMINANCE8, GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_BYTE, false); + setFormat(Format.Luminance16, GL11.GL_LUMINANCE16, GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_SHORT, false); + setFormat(Format.Luminance16F, ARBTextureFloat.GL_LUMINANCE16F_ARB, GL11.GL_LUMINANCE, ARBHalfFloatPixel.GL_HALF_FLOAT_ARB, false); + setFormat(Format.Luminance32F, ARBTextureFloat.GL_LUMINANCE32F_ARB, GL11.GL_LUMINANCE, GL11.GL_FLOAT, false); + + // Luminance alpha formats + setFormat(Format.Luminance8Alpha8, GL11.GL_LUMINANCE8_ALPHA8, GL11.GL_LUMINANCE_ALPHA, GL11.GL_UNSIGNED_BYTE, false); + setFormat(Format.Luminance16Alpha16, GL11.GL_LUMINANCE16_ALPHA16, GL11.GL_LUMINANCE_ALPHA, GL11.GL_UNSIGNED_SHORT, false); + setFormat(Format.Luminance16FAlpha16F, ARBTextureFloat.GL_LUMINANCE_ALPHA16F_ARB, GL11.GL_LUMINANCE_ALPHA, ARBHalfFloatPixel.GL_HALF_FLOAT_ARB, false); + + // Depth formats + setFormat(Format.Depth, GL11.GL_DEPTH_COMPONENT, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_BYTE, false); + setFormat(Format.Depth16, GL14.GL_DEPTH_COMPONENT16, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_SHORT, false); + setFormat(Format.Depth24, GL14.GL_DEPTH_COMPONENT24, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_INT, false); + setFormat(Format.Depth32, GL14.GL_DEPTH_COMPONENT32, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_INT, false); + setFormat(Format.Depth32F, GL30.GL_DEPTH_COMPONENT32F, GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, false); + + // Depth stencil formats + setFormat(Format.Depth24Stencil8, GL30.GL_DEPTH24_STENCIL8, GL30.GL_DEPTH_STENCIL, GL30.GL_UNSIGNED_INT_24_8, false); + + // RGB formats + setFormat(Format.BGR8, GL11.GL_RGB8, EXTBgra.GL_BGR_EXT, GL11.GL_UNSIGNED_BYTE, false); + setFormat(Format.RGB8, GL11.GL_RGB8, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, false); +// setFormat(Format.RGB10, GL11.GL_RGB10, GL11.GL_RGB, GL12.GL_UNSIGNED_INT_10_10_10_2, false); + setFormat(Format.RGB16, GL11.GL_RGB16, GL11.GL_RGB, GL11.GL_UNSIGNED_SHORT, false); + setFormat(Format.RGB16F, ARBTextureFloat.GL_RGB16F_ARB, GL11.GL_RGB, ARBHalfFloatPixel.GL_HALF_FLOAT_ARB, false); + setFormat(Format.RGB32F, ARBTextureFloat.GL_RGB32F_ARB, GL11.GL_RGB, GL11.GL_FLOAT, false); + + // Special RGB formats + setFormat(Format.RGB111110F, EXTPackedFloat.GL_R11F_G11F_B10F_EXT, GL11.GL_RGB, EXTPackedFloat.GL_UNSIGNED_INT_10F_11F_11F_REV_EXT, false); + setFormat(Format.RGB9E5, EXTTextureSharedExponent.GL_RGB9_E5_EXT, GL11.GL_RGB, EXTTextureSharedExponent.GL_UNSIGNED_INT_5_9_9_9_REV_EXT, false); + setFormat(Format.RGB16F_to_RGB111110F, EXTPackedFloat.GL_R11F_G11F_B10F_EXT, GL11.GL_RGB, ARBHalfFloatPixel.GL_HALF_FLOAT_ARB, false); + setFormat(Format.RGB16F_to_RGB9E5, EXTTextureSharedExponent.GL_RGB9_E5_EXT, GL11.GL_RGB, ARBHalfFloatPixel.GL_HALF_FLOAT_ARB, false); + + // RGBA formats + setFormat(Format.ABGR8, GL11.GL_RGBA8, EXTAbgr.GL_ABGR_EXT, GL11.GL_UNSIGNED_BYTE, false); + setFormat(Format.RGB5A1, GL11.GL_RGB5_A1, GL11.GL_RGBA, GL12.GL_UNSIGNED_SHORT_5_5_5_1, false); + setFormat(Format.ARGB4444, GL11.GL_RGBA4, EXTAbgr.GL_ABGR_EXT, GL12.GL_UNSIGNED_SHORT_4_4_4_4, false); + setFormat(Format.RGBA8, GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, false); + setFormat(Format.RGBA16, GL11.GL_RGBA16, GL11.GL_RGBA, GL11.GL_UNSIGNED_SHORT, false); // might be incorrect + setFormat(Format.RGBA16F, ARBTextureFloat.GL_RGBA16F_ARB, GL11.GL_RGBA, ARBHalfFloatPixel.GL_HALF_FLOAT_ARB, false); + setFormat(Format.RGBA32F, ARBTextureFloat.GL_RGBA32F_ARB, GL11.GL_RGBA, GL11.GL_FLOAT, false); + + // DXT formats + setFormat(Format.DXT1, EXTTextureCompressionS3TC.GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, true); + setFormat(Format.DXT1A, EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, true); + setFormat(Format.DXT3, EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, true); + setFormat(Format.DXT5, EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, true); + + // LTC/LATC/3Dc formats + setFormat(Format.LTC, EXTTextureCompressionLATC.GL_COMPRESSED_LUMINANCE_LATC1_EXT, GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_BYTE, true); + setFormat(Format.LATC, EXTTextureCompressionLATC.GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT, GL11.GL_LUMINANCE_ALPHA, GL11.GL_UNSIGNED_BYTE, true); + } + + public static GLImageFormat getImageFormat(ContextCapabilities caps, Format fmt){ + switch (fmt){ + case ABGR8: + if (!caps.GL_EXT_abgr){ + return null; + } + break; + case BGR8: + if (!caps.OpenGL12 && !caps.GL_EXT_bgra){ + return null; + } + break; + case DXT1: + case DXT1A: + case DXT3: + case DXT5: + if (!caps.GL_EXT_texture_compression_s3tc) { + return null; + } + break; + case Depth: + case Depth16: + case Depth24: + case Depth32: + if (!caps.OpenGL14 && !caps.GL_ARB_depth_texture){ + return null; + } + break; + case Depth24Stencil8: + if (!caps.OpenGL30){ + return null; + } + break; + case Luminance16F: + case Luminance16FAlpha16F: + case Luminance32F: + if (!caps.GL_ARB_texture_float){ + return null; + } + break; + case RGB16F: + case RGB32F: + case RGBA16F: + case RGBA32F: + if (!caps.OpenGL30 && !caps.GL_ARB_texture_float){ + return null; + } + break; + case Depth32F: + if (!caps.OpenGL30 && !caps.GL_NV_depth_buffer_float){ + return null; + } + break; + case LATC: + case LTC: + if (!caps.GL_EXT_texture_compression_latc){ + return null; + } + break; + case RGB9E5: + case RGB16F_to_RGB9E5: + if (!caps.OpenGL30 && !caps.GL_EXT_texture_shared_exponent){ + return null; + } + break; + case RGB111110F: + case RGB16F_to_RGB111110F: + if (!caps.OpenGL30 && !caps.GL_EXT_packed_float){ + return null; + } + break; + } + return formatToGL[fmt.ordinal()]; + } + + public static GLImageFormat getImageFormatWithError(Format fmt) { + GLImageFormat glFmt = getImageFormat(GLContext.getCapabilities(), fmt); + if (glFmt == null) { + throw new RendererException("Image format '" + fmt + "' is unsupported by the video hardware."); + } + return glFmt; + } + + public static void uploadTexture(Image image, + int target, + int index, + int border){ + + Image.Format fmt = image.getFormat(); + GLImageFormat glFmt = getImageFormatWithError(fmt); + + ByteBuffer data; + if (index >= 0 && image.getData() != null && image.getData().size() > 0){ + data = image.getData(index); + }else{ + data = null; + } + + int width = image.getWidth(); + int height = image.getHeight(); + int depth = image.getDepth(); + + if (data != null) { + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); + } + + int[] mipSizes = image.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 = image.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 (glFmt.compressed && data != null){ + if (target == GL12.GL_TEXTURE_3D){ + GL13.glCompressedTexImage3D(target, + i, + glFmt.internalFormat, + mipWidth, + mipHeight, + mipDepth, + border, + data); + }else{ + //all other targets use 2D: array, cubemap, 2d + GL13.glCompressedTexImage2D(target, + i, + glFmt.internalFormat, + mipWidth, + mipHeight, + border, + data); + } + }else{ + if (target == GL12.GL_TEXTURE_3D){ + GL12.glTexImage3D(target, + i, + glFmt.internalFormat, + mipWidth, + mipHeight, + mipDepth, + border, + glFmt.format, + glFmt.dataType, + data); + }else if (target == EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT){ + // prepare data for 2D array + // or upload slice + if (index == -1){ + GL12.glTexImage3D(target, + 0, + glFmt.internalFormat, + mipWidth, + mipHeight, + image.getData().size(), //# of slices + border, + glFmt.format, + glFmt.dataType, + data); + }else{ + GL12.glTexSubImage3D(target, + i, // level + 0, // xoffset + 0, // yoffset + index, // zoffset + width, // width + height, // height + 1, // depth + glFmt.format, + glFmt.dataType, + data); + } + }else{ + if (subtex){ + if (samples > 1){ + throw new IllegalStateException("Cannot update multisample textures"); + } + + GL11.glTexSubImage2D(target, + i, + 0, 0, + mipWidth, mipHeight, + glFmt.format, + glFmt.dataType, + data); + }else{ + if (samples > 1){ + ARBTextureMultisample.glTexImage2DMultisample(target, + samples, + glFmt.internalFormat, + mipWidth, + mipHeight, + true); + }else{ + GL11.glTexImage2D(target, + i, + glFmt.internalFormat, + mipWidth, + mipHeight, + border, + glFmt.format, + glFmt.dataType, + data); + } + } + } + } + + pos += mipSizes[i]; + } + } + + /** + * Update the texture currently bound to target at with data from the given Image at position x and y. The parameter + * index is used as the zoffset in case a 3d texture or texture 2d array is being updated. + * + * @param image Image with the source data (this data will be put into the texture) + * @param target the target texture + * @param index the mipmap level to update + * @param x the x position where to put the image in the texture + * @param y the y position where to put the image in the texture + */ + public static void uploadSubTexture( + Image image, + int target, + int index, + int x, + int y) { + Image.Format fmt = image.getFormat(); + GLImageFormat glFmt = getImageFormatWithError(fmt); + + ByteBuffer data = null; + if (index >= 0 && image.getData() != null && image.getData().size() > 0) { + data = image.getData(index); + } + + int width = image.getWidth(); + int height = image.getHeight(); + int depth = image.getDepth(); + + if (data != null) { + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); + } + + int[] mipSizes = image.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 }; + } + } + + int samples = image.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]); + } + + // to remove the cumbersome if/then/else stuff below we'll update the pos right here and use continue after each + // gl*Image call in an attempt to unclutter things a bit + pos += mipSizes[i]; + + int glFmtInternal = glFmt.internalFormat; + int glFmtFormat = glFmt.format; + int glFmtDataType = glFmt.dataType; + + if (glFmt.compressed && data != null){ + if (target == GL12.GL_TEXTURE_3D){ + GL13.glCompressedTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtInternal, data); + continue; + } + + // all other targets use 2D: array, cubemap, 2d + GL13.glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtInternal, data); + continue; + } + + if (target == GL12.GL_TEXTURE_3D){ + GL12.glTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtFormat, glFmtDataType, data); + continue; + } + + if (target == EXTTextureArray.GL_TEXTURE_2D_ARRAY_EXT){ + // prepare data for 2D array or upload slice + if (index == -1){ + GL12.glTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtFormat, glFmtDataType, data); + continue; + } + + GL12.glTexSubImage3D(target, i, x, y, index, width, height, 1, glFmtFormat, glFmtDataType, data); + continue; + } + + if (samples > 1){ + throw new IllegalStateException("Cannot update multisample textures"); + } + + GL11.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtFormat, glFmtDataType, data); + continue; + } + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java new file mode 100644 index 000000000..8197ae19c --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.lwjgl; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; +import com.jme3.input.lwjgl.JInputJoyInput; +import com.jme3.input.lwjgl.LwjglKeyInput; +import com.jme3.input.lwjgl.LwjglMouseInput; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext.Type; +import com.jme3.system.JmeSystem; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.lwjgl.LWJGLException; +import org.lwjgl.Sys; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.OpenGLException; +import org.lwjgl.opengl.Util; + +public abstract class LwjglAbstractDisplay extends LwjglContext implements Runnable { + + private static final Logger logger = Logger.getLogger(LwjglAbstractDisplay.class.getName()); + + protected AtomicBoolean needClose = new AtomicBoolean(false); + protected boolean wasActive = false; + protected int frameRate = 0; + protected boolean autoFlush = true; + + /** + * @return Type.Display or Type.Canvas + */ + public abstract Type getType(); + + /** + * Set the title if its a windowed display + * @param title + */ + public abstract void setTitle(String title); + + /** + * Restart if its a windowed or full-screen display. + */ + public abstract void restart(); + + /** + * Apply the settings, changing resolution, etc. + * @param settings + */ + protected abstract void createContext(AppSettings settings) throws LWJGLException; + + /** + * Destroy the context. + */ + protected abstract void destroyContext(); + + /** + * Does LWJGL display initialization in the OpenGL thread + */ + protected boolean initInThread(){ + try { + if (!JmeSystem.isLowPermissions()){ + // Enable uncaught exception handler only for current thread + Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown); + if (needClose.get()){ + // listener.handleError() has requested the + // context to close. Satisfy request. + deinitInThread(); + } + } + }); + } + + // For canvas, this will create a pbuffer, + // allowing us to query information. + // When the canvas context becomes available, it will + // be replaced seamlessly. + createContext(settings); + printContextInitInfo(); + + created.set(true); + super.internalCreate(); + } catch (Exception ex){ + try { + if (Display.isCreated()) + Display.destroy(); + } catch (Exception ex2){ + logger.log(Level.WARNING, null, ex2); + } + + listener.handleError("Failed to create display", ex); + return false; // if we failed to create display, do not continue + } + + listener.initialize(); + return true; + } + + protected boolean checkGLError(){ + try { + Util.checkGLError(); + } catch (OpenGLException ex){ + listener.handleError("An OpenGL error has occured!", ex); + } + // NOTE: Always return true since this is used in an "assert" statement + return true; + } + + /** + * execute one iteration of the render loop in the OpenGL thread + */ + protected void runLoop(){ + if (!created.get()) + throw new IllegalStateException(); + + listener.update(); + + // All this does is call swap buffers + // If the canvas is not active, there's no need to waste time + // doing that .. + if (renderable.get()){ + assert checkGLError(); + + // calls swap buffers, etc. + try { + if (autoFlush){ + Display.update(false); + }else{ + Display.processMessages(); + Thread.sleep(50); + // add a small wait + // to reduce CPU usage + } + } catch (Throwable ex){ + listener.handleError("Error while swapping buffers", ex); + } + } + + if (frameRate > 0) + Display.sync(frameRate); + + if (renderable.get()){ + if (autoFlush){ + // check input after we synchronize with framerate. + // this reduces input lag. + Display.processMessages(); + } + } + + // Subclasses just call GLObjectManager clean up objects here + // it is safe .. for now. + renderer.onFrame(); + } + + /** + * De-initialize in the OpenGL thread. + */ + protected void deinitInThread(){ + destroyContext(); + + listener.destroy(); + logger.fine("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.FINE, "Using LWJGL {0}", Sys.getVersion()); + if (!initInThread()) { + logger.log(Level.SEVERE, "Display initialization failed. Cannot continue."); + return; + } + while (true){ + if (renderable.get()){ + if (Display.isCloseRequested()) + listener.requestClose(false); + + if (wasActive != Display.isActive()) { + if (!wasActive) { + listener.gainFocus(); + timer.reset(); + wasActive = true; + } else { + listener.loseFocus(); + wasActive = false; + } + } + } + + runLoop(); + + if (needClose.get()) + break; + } + deinitInThread(); + } + + public JoyInput getJoyInput() { + if (joyInput == null){ + joyInput = new JInputJoyInput(); + } + return joyInput; + } + + public MouseInput getMouseInput() { + if (mouseInput == null){ + mouseInput = new LwjglMouseInput(this); + } + return mouseInput; + } + + public KeyInput getKeyInput() { + if (keyInput == null){ + keyInput = new LwjglKeyInput(this); + } + return keyInput; + } + + public TouchInput getTouchInput() { + return null; + } + + public void setAutoFlushFrames(boolean enabled){ + this.autoFlush = enabled; + } + + public void destroy(boolean waitFor){ + needClose.set(true); + if (waitFor) + waitFor(false); + } + +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java new file mode 100644 index 000000000..30db8590b --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.lwjgl; + +import com.jme3.system.AppSettings; +import com.jme3.system.JmeCanvasContext; +import com.jme3.system.JmeContext.Type; +import com.jme3.system.JmeSystem; +import com.jme3.system.Platform; +import java.awt.Canvas; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.SwingUtilities; +import org.lwjgl.LWJGLException; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.Pbuffer; +import org.lwjgl.opengl.PixelFormat; + +public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContext { + + protected static final int TASK_NOTHING = 0, + TASK_DESTROY_DISPLAY = 1, + TASK_CREATE_DISPLAY = 2, + TASK_COMPLETE = 3; + +// protected static final boolean USE_SHARED_CONTEXT = +// Boolean.parseBoolean(System.getProperty("jme3.canvas.sharedctx", "true")); + + protected static final boolean USE_SHARED_CONTEXT = false; + + private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName()); + private Canvas canvas; + private int width; + private int height; + + private final Object taskLock = new Object(); + private int desiredTask = TASK_NOTHING; + + private Thread renderThread; + private boolean runningFirstTime = true; + private boolean mouseWasGrabbed = false; + + private boolean mouseWasCreated = false; + private boolean keyboardWasCreated = false; + + private Pbuffer pbuffer; + private PixelFormat pbufferFormat; + private PixelFormat canvasFormat; + + private class GLCanvas extends Canvas { + @Override + public void addNotify(){ + super.addNotify(); + + if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED) { + return; // already destroyed. + } + + if (renderThread == null){ + logger.log(Level.FINE, "EDT: Creating OGL thread."); + + // Also set some settings on the canvas here. + // So we don't do it outside the AWT thread. + canvas.setFocusable(true); + canvas.setIgnoreRepaint(true); + + renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread"); + renderThread.start(); + }else if (needClose.get()){ + return; + } + + logger.log(Level.FINE, "EDT: Telling OGL to create display .."); + synchronized (taskLock){ + desiredTask = TASK_CREATE_DISPLAY; +// while (desiredTask != TASK_COMPLETE){ +// try { +// taskLock.wait(); +// } catch (InterruptedException ex) { +// return; +// } +// } +// desiredTask = TASK_NOTHING; + } +// logger.log(Level.FINE, "EDT: OGL has created the display"); + } + + @Override + public void removeNotify(){ + if (needClose.get()){ + logger.log(Level.FINE, "EDT: Application is stopped. Not restoring canvas."); + super.removeNotify(); + return; + } + + // We must tell GL context to shutdown and wait for it to + // shutdown, otherwise, issues will occur. + logger.log(Level.FINE, "EDT: Telling OGL to destroy display .."); + synchronized (taskLock){ + desiredTask = TASK_DESTROY_DISPLAY; + while (desiredTask != TASK_COMPLETE){ + try { + taskLock.wait(); + } catch (InterruptedException ex){ + super.removeNotify(); + return; + } + } + desiredTask = TASK_NOTHING; + } + + logger.log(Level.FINE, "EDT: Acknowledged receipt of canvas death"); + // GL context is dead at this point + + super.removeNotify(); + } + } + + public LwjglCanvas(){ + super(); + canvas = new GLCanvas(); + } + + @Override + public Type getType() { + return Type.Canvas; + } + + public void create(boolean waitFor){ + if (renderThread == null){ + logger.log(Level.FINE, "MAIN: Creating OGL thread."); + + renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread"); + renderThread.start(); + } + // do not do anything. + // superclass's create() will be called at initInThread() + if (waitFor) { + waitFor(true); + } + } + + @Override + public void setTitle(String title) { + } + + @Override + public void restart() { + frameRate = settings.getFrameRate(); + // TODO: Handle other cases, like change of pixel format, etc. + } + + public Canvas getCanvas(){ + return canvas; + } + + @Override + protected void runLoop(){ + if (desiredTask != TASK_NOTHING){ + synchronized (taskLock){ + switch (desiredTask){ + case TASK_CREATE_DISPLAY: + logger.log(Level.FINE, "OGL: Creating display .."); + restoreCanvas(); + listener.gainFocus(); + desiredTask = TASK_NOTHING; + break; + case TASK_DESTROY_DISPLAY: + logger.log(Level.FINE, "OGL: Destroying display .."); + listener.loseFocus(); + pauseCanvas(); + break; + } + desiredTask = TASK_COMPLETE; + taskLock.notifyAll(); + } + } + + if (renderable.get()){ + int newWidth = Math.max(canvas.getWidth(), 1); + int newHeight = Math.max(canvas.getHeight(), 1); + if (width != newWidth || height != newHeight){ + width = newWidth; + height = newHeight; + if (listener != null){ + listener.reshape(width, height); + } + } + }else{ + if (frameRate <= 0){ + // NOTE: MUST be done otherwise + // Windows OS will freeze + Display.sync(30); + } + } + + super.runLoop(); + } + + private void pauseCanvas(){ + if (Mouse.isCreated()){ + if (Mouse.isGrabbed()){ + Mouse.setGrabbed(false); + mouseWasGrabbed = true; + } + mouseWasCreated = true; + Mouse.destroy(); + } + if (Keyboard.isCreated()){ + keyboardWasCreated = true; + Keyboard.destroy(); + } + + renderable.set(false); + destroyContext(); + } + + /** + * Called to restore the canvas. + */ + private void restoreCanvas(){ + logger.log(Level.FINE, "OGL: Waiting for canvas to become displayable.."); + while (!canvas.isDisplayable()){ + try { + Thread.sleep(10); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "OGL: Interrupted! ", ex); + } + } + + logger.log(Level.FINE, "OGL: Creating display context .."); + + // Set renderable to true, since canvas is now displayable. + renderable.set(true); + createContext(settings); + + logger.log(Level.FINE, "OGL: Display is active!"); + + try { + if (mouseWasCreated){ + Mouse.create(); + if (mouseWasGrabbed){ + Mouse.setGrabbed(true); + mouseWasGrabbed = false; + } + } + if (keyboardWasCreated){ + Keyboard.create(); + keyboardWasCreated = false; + } + } catch (LWJGLException ex){ + logger.log(Level.SEVERE, "Encountered exception when restoring input", ex); + } + + SwingUtilities.invokeLater(new Runnable(){ + public void run(){ + canvas.requestFocus(); + } + }); + } + + /** + * It seems it is best to use one pixel format for all shared contexts. + * @see http://developer.apple.com/library/mac/#qa/qa1248/_index.html + */ + protected PixelFormat acquirePixelFormat(boolean forPbuffer){ + if (forPbuffer){ + // Use 0 samples for pbuffer format, prevents + // crashes on bad drivers + if (pbufferFormat == null){ + pbufferFormat = new PixelFormat(settings.getBitsPerPixel(), + 0, + settings.getDepthBits(), + settings.getStencilBits(), + 0, // samples + 0, + 0, + 0, + settings.useStereo3D()); + } + return pbufferFormat; + }else{ + if (canvasFormat == null){ + int samples = getNumSamplesToUse(); + canvasFormat = new PixelFormat(settings.getBitsPerPixel(), + 0, + settings.getDepthBits(), + settings.getStencilBits(), + samples, + 0, + 0, + 0, + settings.useStereo3D()); + } + return canvasFormat; + } + } + + /** + * Makes sure the pbuffer is available and ready for use + */ + protected void makePbufferAvailable() throws LWJGLException{ + if (pbuffer != null && pbuffer.isBufferLost()){ + logger.log(Level.WARNING, "PBuffer was lost!"); + pbuffer.destroy(); + pbuffer = null; + } + + if (pbuffer == null) { + pbuffer = new Pbuffer(1, 1, acquirePixelFormat(true), null); + pbuffer.makeCurrent(); + logger.log(Level.FINE, "OGL: Pbuffer has been created"); + + // Any created objects are no longer valid + if (!runningFirstTime){ + renderer.resetGLObjects(); + } + } + + pbuffer.makeCurrent(); + if (!pbuffer.isCurrent()){ + throw new LWJGLException("Pbuffer cannot be made current"); + } + } + + protected void destroyPbuffer(){ + if (pbuffer != null){ + if (!pbuffer.isBufferLost()){ + pbuffer.destroy(); + } + pbuffer = null; + } + } + + /** + * This is called: + * 1) When the context thread ends + * 2) Any time the canvas becomes non-displayable + */ + protected void destroyContext(){ + try { + // invalidate the state so renderer can resume operation + if (!USE_SHARED_CONTEXT){ + renderer.cleanup(); + } + + if (Display.isCreated()){ + /* FIXES: + * org.lwjgl.LWJGLException: X Error + * BadWindow (invalid Window parameter) request_code: 2 minor_code: 0 + * + * Destroying keyboard early prevents the error above, triggered + * by destroying keyboard in by Display.destroy() or Display.setParent(null). + * Therefore Keyboard.destroy() should precede any of these calls. + */ + if (Keyboard.isCreated()){ + // Should only happen if called in + // LwjglAbstractDisplay.deinitInThread(). + Keyboard.destroy(); + } + + //try { + // NOTE: On Windows XP, not calling setParent(null) + // freezes the application. + // On Mac it freezes the application. + // On Linux it fixes a crash with X Window System. + if (JmeSystem.getPlatform() == Platform.Windows32 + || JmeSystem.getPlatform() == Platform.Windows64){ + //Display.setParent(null); + } + //} catch (LWJGLException ex) { + // logger.log(Level.SEVERE, "Encountered exception when setting parent to null", ex); + //} + + Display.destroy(); + } + + // The canvas is no longer visible, + // but the context thread is still running. + if (!needClose.get()){ + // MUST make sure there's still a context current here .. + // Display is dead, make pbuffer available to the system + makePbufferAvailable(); + + renderer.invalidateState(); + }else{ + // The context thread is no longer running. + // Destroy pbuffer. + destroyPbuffer(); + } + } catch (LWJGLException ex) { + listener.handleError("Failed make pbuffer available", ex); + } + } + + /** + * This is called: + * 1) When the context thread starts + * 2) Any time the canvas becomes displayable again. + */ + @Override + protected void createContext(AppSettings settings) { + // In case canvas is not visible, we still take framerate + // from settings to prevent "100% CPU usage" + frameRate = settings.getFrameRate(); + + try { + if (renderable.get()){ + if (!runningFirstTime){ + // because the display is a different opengl context + // must reset the context state. + if (!USE_SHARED_CONTEXT){ + renderer.cleanup(); + } + } + + // if the pbuffer is currently active, + // make sure to deactivate it + destroyPbuffer(); + + if (Keyboard.isCreated()){ + Keyboard.destroy(); + } + + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + } + + Display.setVSyncEnabled(settings.isVSync()); + Display.setParent(canvas); + + if (USE_SHARED_CONTEXT){ + Display.create(acquirePixelFormat(false), pbuffer); + }else{ + Display.create(acquirePixelFormat(false)); + } + + renderer.invalidateState(); + }else{ + // First create the pbuffer, if it is needed. + makePbufferAvailable(); + } + + // At this point, the OpenGL context is active. + if (runningFirstTime){ + // THIS is the part that creates the renderer. + // It must always be called, now that we have the pbuffer workaround. + initContextFirstTime(); + runningFirstTime = false; + } + } catch (LWJGLException ex) { + listener.handleError("Failed to initialize OpenGL context", ex); + // TODO: Fix deadlock that happens after the error (throw runtime exception?) + } + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java new file mode 100644 index 000000000..290c3e400 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.lwjgl; + +import com.jme3.input.lwjgl.JInputJoyInput; +import com.jme3.input.lwjgl.LwjglKeyInput; +import com.jme3.input.lwjgl.LwjglMouseInput; +import com.jme3.math.FastMath; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.lwjgl.LwjglGL1Renderer; +import com.jme3.renderer.lwjgl.LwjglRenderer; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext; +import com.jme3.system.JmeSystem; +import com.jme3.system.SystemListener; +import com.jme3.system.Timer; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.lwjgl.LWJGLException; +import org.lwjgl.Sys; +import org.lwjgl.opengl.*; + +/** + * A LWJGL implementation of a graphics context. + */ +public abstract class LwjglContext implements JmeContext { + + private static final Logger logger = Logger.getLogger(LwjglContext.class.getName()); + + protected AtomicBoolean created = new AtomicBoolean(false); + protected AtomicBoolean renderable = new AtomicBoolean(false); + protected final Object createdLock = new Object(); + + protected AppSettings settings = new AppSettings(true); + protected Renderer renderer; + protected LwjglKeyInput keyInput; + protected LwjglMouseInput mouseInput; + protected JInputJoyInput joyInput; + protected Timer timer; + protected SystemListener listener; + + public void setSystemListener(SystemListener listener){ + this.listener = listener; + } + + protected void printContextInitInfo() { + logger.log(Level.INFO, "Lwjgl {0} context running on thread {1}", + new Object[]{Sys.getVersion(), Thread.currentThread().getName()}); + + logger.log(Level.INFO, "Adapter: {0}", Display.getAdapter()); + logger.log(Level.INFO, "Driver Version: {0}", Display.getVersion()); + + String vendor = GL11.glGetString(GL11.GL_VENDOR); + logger.log(Level.INFO, "Vendor: {0}", vendor); + + String version = GL11.glGetString(GL11.GL_VERSION); + logger.log(Level.INFO, "OpenGL Version: {0}", version); + + String renderGl = GL11.glGetString(GL11.GL_RENDERER); + logger.log(Level.INFO, "Renderer: {0}", renderGl); + + if (GLContext.getCapabilities().OpenGL20){ + String shadingLang = GL11.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION); + logger.log(Level.INFO, "GLSL Ver: {0}", shadingLang); + } + } + + protected ContextAttribs createContextAttribs() { + if (settings.getBoolean("GraphicsDebug") || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) { + ContextAttribs attr; + if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) { + attr = new ContextAttribs(3, 2); + attr = attr.withProfileCore(true).withForwardCompatible(true).withProfileCompatibility(false); + } else { + attr = new ContextAttribs(); + } + if (settings.getBoolean("GraphicsDebug")) { + attr = attr.withDebug(true); + } + return attr; + } else { + return null; + } + } + + protected int determineMaxSamples(int requestedSamples) { + boolean displayWasCurrent = false; + try { + // If we already have a valid context, determine samples using current + // context. + if (Display.isCreated() && Display.isCurrent()) { + if (GLContext.getCapabilities().GL_ARB_framebuffer_object) { + return GL11.glGetInteger(ARBFramebufferObject.GL_MAX_SAMPLES); + } else if (GLContext.getCapabilities().GL_EXT_framebuffer_multisample) { + return GL11.glGetInteger(EXTFramebufferMultisample.GL_MAX_SAMPLES_EXT); + } + // Doesn't support any of the needed extensions .. continue down. + displayWasCurrent = true; + } + } catch (LWJGLException ex) { + listener.handleError("Failed to check if display is current", ex); + } + + if ((Pbuffer.getCapabilities() & Pbuffer.PBUFFER_SUPPORTED) == 0) { + // No pbuffer, assume everything is supported. + return Integer.MAX_VALUE; + } else { + Pbuffer pb = null; + + if (!displayWasCurrent) { + // OpenGL2 method: Create pbuffer and query samples + // from GL_ARB_framebuffer_object or GL_EXT_framebuffer_multisample. + try { + pb = new Pbuffer(1, 1, new PixelFormat(0, 0, 0), null); + pb.makeCurrent(); + + if (GLContext.getCapabilities().GL_ARB_framebuffer_object) { + return GL11.glGetInteger(ARBFramebufferObject.GL_MAX_SAMPLES); + } else if (GLContext.getCapabilities().GL_EXT_framebuffer_multisample) { + return GL11.glGetInteger(EXTFramebufferMultisample.GL_MAX_SAMPLES_EXT); + } + + // OpenGL2 method failed. + } catch (LWJGLException ex) { + // Something else failed. + return Integer.MAX_VALUE; + } finally { + if (pb != null) { + pb.destroy(); + pb = null; + } + } + } + + // OpenGL1 method (DOESNT WORK RIGHT NOW ..) + requestedSamples = FastMath.nearestPowerOfTwo(requestedSamples); + try { + requestedSamples = Integer.MAX_VALUE; + /* + while (requestedSamples > 1) { + try { + pb = new Pbuffer(1, 1, new PixelFormat(16, 0, 8, 0, requestedSamples), null); + } catch (LWJGLException ex) { + if (ex.getMessage().startsWith("Failed to find ARB pixel format")) { + // Unsupported format, so continue. + requestedSamples = FastMath.nearestPowerOfTwo(requestedSamples / 2); + } else { + // Something else went wrong .. + return Integer.MAX_VALUE; + } + } finally { + if (pb != null){ + pb.destroy(); + pb = null; + } + } + }*/ + } finally { + if (displayWasCurrent) { + try { + Display.makeCurrent(); + } catch (LWJGLException ex) { + listener.handleError("Failed to make display current after checking samples", ex); + } + } + } + + return requestedSamples; + } + } + + protected int getNumSamplesToUse() { + int samples = 0; + if (settings.getSamples() > 1){ + samples = settings.getSamples(); + int supportedSamples = determineMaxSamples(samples); + if (supportedSamples < samples) { + samples = supportedSamples; + } + } + return samples; + } + + protected void initContextFirstTime(){ + if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2) + || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)){ + renderer = new LwjglRenderer(); + }else if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL1)){ + renderer = new LwjglGL1Renderer(); + }else if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL_ANY)){ + // Choose an appropriate renderer based on capabilities + if (GLContext.getCapabilities().OpenGL20){ + renderer = new LwjglRenderer(); + }else{ + renderer = new LwjglGL1Renderer(); + } + }else{ + throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer()); + } + + // Init renderer + if (renderer instanceof LwjglRenderer){ + ((LwjglRenderer)renderer).initialize(); + }else if (renderer instanceof LwjglGL1Renderer){ + ((LwjglGL1Renderer)renderer).initialize(); + }else{ + assert false; + } + + // Init input + if (keyInput != null) { + keyInput.initialize(); + } + + if (mouseInput != null) { + mouseInput.initialize(); + } + + if (joyInput != null) { + joyInput.initialize(); + } + } + + public void internalDestroy(){ + renderer = null; + timer = null; + renderable.set(false); + synchronized (createdLock){ + created.set(false); + createdLock.notifyAll(); + } + } + + public void internalCreate(){ + timer = new LwjglTimer(); + + synchronized (createdLock){ + created.set(true); + createdLock.notifyAll(); + } + + if (renderable.get()){ + initContextFirstTime(); + }else{ + assert getType() == Type.Canvas; + } + } + + public void create(){ + create(false); + } + + public void destroy(){ + destroy(false); + } + + protected void waitFor(boolean createdVal){ + synchronized (createdLock){ + while (created.get() != createdVal){ + try { + createdLock.wait(); + } catch (InterruptedException ex) { + } + } + } + } + + public boolean isCreated(){ + return created.get(); + } + + public boolean isRenderable(){ + return renderable.get(); + } + + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + } + + public AppSettings getSettings(){ + return settings; + } + + public Renderer getRenderer() { + return renderer; + } + + public Timer getTimer() { + return timer; + } + +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java new file mode 100644 index 000000000..40590864a --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.lwjgl; + +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext.Type; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.lwjgl.LWJGLException; +import org.lwjgl.opengl.*; + +public class LwjglDisplay extends LwjglAbstractDisplay { + + private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName()); + + private final AtomicBoolean needRestart = new AtomicBoolean(false); + private PixelFormat pixelFormat; + + protected DisplayMode getFullscreenDisplayMode(int width, int height, int bpp, int freq){ + try { + DisplayMode[] modes = Display.getAvailableDisplayModes(); + for (DisplayMode mode : modes){ + if (mode.getWidth() == width + && mode.getHeight() == height + && (mode.getBitsPerPixel() == bpp || (bpp==24&&mode.getBitsPerPixel()==32)) + && mode.getFrequency() == freq){ + return mode; + } + } + } catch (LWJGLException ex) { + listener.handleError("Failed to acquire fullscreen display mode!", ex); + } + return null; + } + + protected void createContext(AppSettings settings) throws LWJGLException{ + DisplayMode displayMode; + if (settings.getWidth() <= 0 || settings.getHeight() <= 0){ + displayMode = Display.getDesktopDisplayMode(); + settings.setResolution(displayMode.getWidth(), displayMode.getHeight()); + }else if (settings.isFullscreen()){ + displayMode = getFullscreenDisplayMode(settings.getWidth(), settings.getHeight(), + settings.getBitsPerPixel(), settings.getFrequency()); + if (displayMode == null) { + throw new RuntimeException("Unable to find fullscreen display mode matching settings"); + } + }else{ + displayMode = new DisplayMode(settings.getWidth(), settings.getHeight()); + } + + int samples = getNumSamplesToUse(); + PixelFormat pf = new PixelFormat(settings.getBitsPerPixel(), + 0, + settings.getDepthBits(), + settings.getStencilBits(), + samples, + 0, + 0, + 0, + settings.useStereo3D()); + + frameRate = settings.getFrameRate(); + logger.log(Level.FINE, "Selected display mode: {0}", displayMode); + + boolean pixelFormatChanged = false; + if (created.get() && (pixelFormat.getBitsPerPixel() != pf.getBitsPerPixel() + ||pixelFormat.getDepthBits() != pf.getDepthBits() + ||pixelFormat.getStencilBits() != pf.getStencilBits() + ||pixelFormat.getSamples() != pf.getSamples())){ + renderer.resetGLObjects(); + Display.destroy(); + pixelFormatChanged = true; + } + pixelFormat = pf; + + Display.setTitle(settings.getTitle()); + + if (displayMode != null) { + if (settings.isFullscreen()) { + Display.setDisplayModeAndFullscreen(displayMode); + } else { + Display.setFullscreen(false); + Display.setDisplayMode(displayMode); + } + } else { + Display.setFullscreen(settings.isFullscreen()); + } + + if (settings.getIcons() != null) { + Display.setIcon(imagesToByteBuffers(settings.getIcons())); + } + + Display.setVSyncEnabled(settings.isVSync()); + + if (created.get() && !pixelFormatChanged) { + Display.releaseContext(); + Display.makeCurrent(); + Display.update(); + } + + if (!created.get() || pixelFormatChanged){ + ContextAttribs attr = createContextAttribs(); + if (attr != null) { + Display.create(pixelFormat, attr); + } else { + Display.create(pixelFormat); + } + renderable.set(true); + + if (pixelFormatChanged && pixelFormat.getSamples() > 1 + && GLContext.getCapabilities().GL_ARB_multisample){ + GL11.glEnable(ARBMultisample.GL_MULTISAMPLE_ARB); + } + } + } + + protected void destroyContext(){ + try { + renderer.cleanup(); + Display.releaseContext(); + Display.destroy(); + } catch (LWJGLException ex) { + listener.handleError("Failed to destroy context", ex); + } + } + + public void create(boolean waitFor){ + if (created.get()){ + logger.warning("create() called when display is already created!"); + return; + } + + new Thread(this, "LWJGL Renderer Thread").start(); + if (waitFor) + waitFor(true); + } + + @Override + public void runLoop(){ + // This method is overriden to do restart + if (needRestart.getAndSet(false)){ + try{ + createContext(settings); + }catch (LWJGLException ex){ + logger.log(Level.SEVERE, "Failed to set display settings!", ex); + } + listener.reshape(settings.getWidth(), settings.getHeight()); + logger.fine("Display restarted."); + } + + super.runLoop(); + } + + @Override + public void restart() { + if (created.get()){ + needRestart.set(true); + }else{ + logger.warning("Display is not created, cannot restart window."); + } + } + + public Type getType() { + return Type.Display; + } + + public void setTitle(String title){ + if (created.get()) + Display.setTitle(title); + } + + private ByteBuffer[] imagesToByteBuffers(Object[] images) { + ByteBuffer[] out = new ByteBuffer[images.length]; + for (int i = 0; i < images.length; i++) { + BufferedImage image = (BufferedImage) images[i]; + out[i] = imageToByteBuffer(image); + } + return out; + } + + private ByteBuffer imageToByteBuffer(BufferedImage image) { + if (image.getType() != BufferedImage.TYPE_INT_ARGB_PRE) { + BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); + Graphics2D g = convertedImage.createGraphics(); + double width = image.getWidth() * (double) 1; + double height = image.getHeight() * (double) 1; + g.drawImage(image, (int) ((convertedImage.getWidth() - width) / 2), + (int) ((convertedImage.getHeight() - height) / 2), + (int) (width), (int) (height), null); + g.dispose(); + image = convertedImage; + } + + byte[] imageBuffer = new byte[image.getWidth() * image.getHeight() * 4]; + int counter = 0; + for (int i = 0; i < image.getHeight(); i++) { + for (int j = 0; j < image.getWidth(); j++) { + int colorSpace = image.getRGB(j, i); + imageBuffer[counter + 0] = (byte) ((colorSpace << 8) >> 24); + imageBuffer[counter + 1] = (byte) ((colorSpace << 16) >> 24); + imageBuffer[counter + 2] = (byte) ((colorSpace << 24) >> 24); + imageBuffer[counter + 3] = (byte) (colorSpace >> 24); + counter += 4; + } + } + return ByteBuffer.wrap(imageBuffer); + } + +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java new file mode 100644 index 000000000..7297db794 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.lwjgl; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; +import com.jme3.input.dummy.DummyKeyInput; +import com.jme3.input.dummy.DummyMouseInput; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.lwjgl.LWJGLException; +import org.lwjgl.Sys; +import org.lwjgl.opengl.*; + +public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { + + private static final Logger logger = Logger.getLogger(LwjglOffscreenBuffer.class.getName()); + private Pbuffer pbuffer; + protected AtomicBoolean needClose = new AtomicBoolean(false); + private int width; + private int height; + private PixelFormat pixelFormat; + + protected void initInThread(){ + if ((Pbuffer.getCapabilities() & Pbuffer.PBUFFER_SUPPORTED) == 0){ + logger.severe("Offscreen surfaces are not supported."); + return; + } + + int samples = getNumSamplesToUse(); + pixelFormat = new PixelFormat(settings.getBitsPerPixel(), + 0, + settings.getDepthBits(), + settings.getStencilBits(), + samples); + + width = settings.getWidth(); + height = settings.getHeight(); + try{ + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown); + } + }); + + pbuffer = new Pbuffer(width, height, pixelFormat, null, null, createContextAttribs()); + pbuffer.makeCurrent(); + + renderable.set(true); + + logger.fine("Offscreen buffer created."); + printContextInitInfo(); + } catch (LWJGLException ex){ + listener.handleError("Failed to create display", ex); + } finally { + // TODO: It is possible to avoid "Failed to find pixel format" + // error here by creating a default display. + } + super.internalCreate(); + listener.initialize(); + } + + protected boolean checkGLError(){ + try { + Util.checkGLError(); + } catch (OpenGLException ex){ + listener.handleError("An OpenGL error has occured!", ex); + } + // NOTE: Always return true since this is used in an "assert" statement + return true; + } + + protected void runLoop(){ + if (!created.get()) { + throw new IllegalStateException(); + } + + if (pbuffer.isBufferLost()) { + pbuffer.destroy(); + + try { + pbuffer = new Pbuffer(width, height, pixelFormat, null); + pbuffer.makeCurrent(); + + // Context MUST be reset here to avoid invalid objects! + renderer.invalidateState(); + } catch (LWJGLException ex) { + listener.handleError("Failed to restore pbuffer content", ex); + } + } + + listener.update(); + checkGLError(); + + renderer.onFrame(); + + int frameRate = settings.getFrameRate(); + if (frameRate >= 1) { + Display.sync(frameRate); + } + } + + protected void deinitInThread(){ + renderable.set(false); + + listener.destroy(); + renderer.cleanup(); + pbuffer.destroy(); + logger.fine("Offscreen buffer destroyed."); + + super.internalDestroy(); + } + + public void run(){ + logger.log(Level.FINE, "Using LWJGL {0}", Sys.getVersion()); + initInThread(); + while (!needClose.get()){ + runLoop(); + } + deinitInThread(); + } + + public void destroy(boolean waitFor){ + needClose.set(true); + if (waitFor) + waitFor(false); + } + + public void create(boolean waitFor){ + if (created.get()){ + logger.warning("create() called when pbuffer is already created!"); + return; + } + + new Thread(this, "LWJGL Renderer Thread").start(); + if (waitFor) + waitFor(true); + } + + public void restart() { + } + + public void setAutoFlushFrames(boolean enabled){ + } + + public Type getType() { + return Type.OffscreenSurface; + } + + public MouseInput getMouseInput() { + return new DummyMouseInput(); + } + + public KeyInput getKeyInput() { + return new DummyKeyInput(); + } + + public JoyInput getJoyInput() { + return null; + } + + public TouchInput getTouchInput() { + return null; + } + + public void setTitle(String title) { + } + +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglSmoothingTimer.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglSmoothingTimer.java new file mode 100644 index 000000000..bd2e275ed --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglSmoothingTimer.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.lwjgl; + +import com.jme3.math.FastMath; +import com.jme3.system.Timer; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.lwjgl.Sys; + +/** + * 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 LwjglSmoothingTimer extends Timer { + private static final Logger logger = Logger.getLogger(LwjglSmoothingTimer.class + .getName()); + + private long lastFrameDiff; + + //frame rate parameters. + private long oldTime; + + private float lastTPF, lastFPS; + + public static int TIMER_SMOOTHNESS = 32; + + private long[] tpf; + + private int smoothIndex; + + private final static long LWJGL_TIMER_RES = Sys.getTimerResolution(); + private final static float INV_LWJGL_TIMER_RES = ( 1f / LWJGL_TIMER_RES ); + private static float invTimerRezSmooth; + + public final static long LWJGL_TIME_TO_NANOS = (1000000000 / LWJGL_TIMER_RES); + + private long startTime; + + private boolean allSmooth = false; + + /** + * Constructor builds a Timer object. All values will be + * initialized to it's default values. + */ + public LwjglSmoothingTimer() { + reset(); + + //print timer resolution info + logger.log(Level.FINE, "Timer resolution: {0} ticks per second", LWJGL_TIMER_RES); + } + + public void reset() { + lastFrameDiff = 0; + lastFPS = 0; + lastTPF = 0; + + // init to -1 to indicate this is a new timer. + oldTime = -1; + //reset time + startTime = Sys.getTime(); + + tpf = new long[TIMER_SMOOTHNESS]; + smoothIndex = TIMER_SMOOTHNESS - 1; + invTimerRezSmooth = ( 1f / (LWJGL_TIMER_RES * TIMER_SMOOTHNESS)); + + // set tpf... -1 values will not be used for calculating the average in update() + for ( int i = tpf.length; --i >= 0; ) { + tpf[i] = -1; + } + } + + /** + * @see Timer#getTime() + */ + public long getTime() { + return Sys.getTime() - startTime; + } + + /** + * @see 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/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglTimer.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglTimer.java new file mode 100644 index 000000000..6adf03323 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglTimer.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.lwjgl; + +import com.jme3.system.Timer; +import java.util.logging.Level; +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()); + + //frame rate parameters. + private long oldTime; + private long startTime; + + private float lastTPF, lastFPS; + + private final static long LWJGL_TIMER_RES = Sys.getTimerResolution(); + private final static float INV_LWJGL_TIMER_RES = ( 1f / LWJGL_TIMER_RES ); + + public final static long LWJGL_TIME_TO_NANOS = (1000000000 / LWJGL_TIMER_RES); + + /** + * Constructor builds a Timer object. All values will be + * initialized to it's default values. + */ + public LwjglTimer() { + reset(); + logger.log(Level.FINE, "Timer resolution: {0} ticks per second", LWJGL_TIMER_RES); + } + + public void reset() { + startTime = Sys.getTime(); + oldTime = getTime(); + } + + @Override + public float getTimeInSeconds() { + return getTime() * INV_LWJGL_TIMER_RES; + } + + /** + * @see Timer#getTime() + */ + public long getTime() { + return Sys.getTime() - startTime; + } + + /** + * @see 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 curTime = getTime(); + lastTPF = (curTime - oldTime) * (1.0f / LWJGL_TIMER_RES); + lastFPS = 1.0f / lastTPF; + oldTime = curTime; + } + + /** + * 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/jme3-networking/src/main/java/com/jme3/network/AbstractMessage.java b/jme3-networking/src/main/java/com/jme3/network/AbstractMessage.java new file mode 100644 index 000000000..5a13a6823 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/AbstractMessage.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + +import com.jme3.network.serializing.Serializable; + +/** + * Interface implemented by all network messages. + * + * @version $Revision$ + * @author Paul Speed + */ +@Serializable() +public abstract class AbstractMessage implements Message +{ + private transient boolean reliable = true; + + protected AbstractMessage() + { + } + + protected AbstractMessage( boolean reliable ) + { + this.reliable = reliable; + } + + /** + * Sets this message to 'reliable' or not and returns this + * message. + */ + public Message setReliable(boolean f) + { + this.reliable = f; + return this; + } + + /** + * Indicates which way an outgoing message should be sent + * or which way an incoming message was sent. + */ + public boolean isReliable() + { + return reliable; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/Client.java b/jme3-networking/src/main/java/com/jme3/network/Client.java new file mode 100644 index 000000000..6837a4d86 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/Client.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + + +/** + * Represents a remote connection to a server that can be used + * for sending and receiving messages. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface Client extends MessageConnection +{ + /** + * Starts the client allowing it to begin processing incoming + * messages and delivering them to listeners. + */ + public void start(); + + /** + * Returns true if this client is fully connected to the + * host. + */ + public boolean isConnected(); + + /** + * Returns a unique ID for this client within the remote + * server or -1 if this client isn't fully connected to the + * server. + */ + public int getId(); + + /** + * Returns the 'game name' for servers to which this client should be able + * to connect. This should match the 'game name' set on the server or this + * client will be turned away. + */ + public String getGameName(); + + /** + * Returns the game-specific version of the server this client should + * be able to connect to. + */ + public int getVersion(); + + /** + * Sends a message to the server. + */ + public void send( Message message ); + + /** + * Sends a message to the other end of the connection using + * the specified alternate channel. + */ + public void send( int channel, Message message ); + + /** + * Closes this connection to the server. + */ + public void close(); + + /** + * Adds a listener that will be notified about connection + * state changes. + */ + public void addClientStateListener( ClientStateListener listener ); + + /** + * Removes a previously registered connection listener. + */ + public void removeClientStateListener( ClientStateListener listener ); + + /** + * Adds a listener that will be notified when any message or object + * is received from the server. + */ + public void addMessageListener( MessageListener listener ); + + /** + * Adds a listener that will be notified when messages of the specified + * types are received. + */ + public void addMessageListener( MessageListener listener, Class... classes ); + + /** + * Removes a previously registered wildcard listener. This does + * not remove this listener from any type-specific registrations. + */ + public void removeMessageListener( MessageListener listener ); + + /** + * Removes a previously registered type-specific listener from + * the specified types. + */ + public void removeMessageListener( MessageListener listener, Class... classes ); + + /** + * Adds a listener that will be notified when any connection errors + * occur. If a client has no error listeners then the default behavior + * is to close the connection and provide an appropriate DisconnectInfo + * to any ClientStateListeners. If the application adds its own error + * listeners then it must take care of closing the connection itself. + */ + public void addErrorListener( ErrorListener listener ); + + /** + * Removes a previously registered error listener. + */ + public void removeErrorListener( ErrorListener listener ); +} + + diff --git a/jme3-networking/src/main/java/com/jme3/network/ClientStateListener.java b/jme3-networking/src/main/java/com/jme3/network/ClientStateListener.java new file mode 100644 index 000000000..fc9ac6bfe --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/ClientStateListener.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + + +/** + * Listener that is notified about the connection state of + * a Client. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface ClientStateListener +{ + /** + * Called when the specified client is fully connected to + * the remote server. + */ + public void clientConnected( Client c ); + + /** + * Called when the client has disconnected from the remote + * server. If info is null then the client shut down the + * connection normally, otherwise the info object contains + * additional information about the disconnect. + */ + public void clientDisconnected( Client c, DisconnectInfo info ); + + /** + * Provided with the clientDisconnected() notification to + * include additional information about the disconnect. + */ + public class DisconnectInfo + { + public String reason; + public Throwable error; + + @Override + public String toString() { + return "DisconnectInfo[" + reason + ", " + error + "]"; + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/ConnectionListener.java b/jme3-networking/src/main/java/com/jme3/network/ConnectionListener.java new file mode 100644 index 000000000..12d3a7262 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/ConnectionListener.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + + +/** + * Listener that is notified about connection arrivals and + * removals within a server. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface ConnectionListener +{ + /** + * Called when a connection has been added to the specified server and + * is fully setup. + */ + public void connectionAdded( Server server, HostedConnection conn ); + + /** + * Called when a connection has been removed from the specified + * server. + */ + public void connectionRemoved( Server server, HostedConnection conn ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/ErrorListener.java b/jme3-networking/src/main/java/com/jme3/network/ErrorListener.java new file mode 100644 index 000000000..3825396d2 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/ErrorListener.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + + +/** + * Notified when errors happen on a connection. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface ErrorListener +{ + public void handleError( S source, Throwable t ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/Filter.java b/jme3-networking/src/main/java/com/jme3/network/Filter.java new file mode 100644 index 000000000..de8f324e3 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/Filter.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + + +/** + * Determines a true or false value for a given input. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface Filter +{ + /** + * Returns true if the specified input is accepted by this + * filter. + */ + public boolean apply( T input ); +} + + diff --git a/jme3-networking/src/main/java/com/jme3/network/Filters.java b/jme3-networking/src/main/java/com/jme3/network/Filters.java new file mode 100644 index 000000000..1cc45e017 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/Filters.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; + +/** + * Static utility methods pertaining to Filter instances. + * + * @version $Revision$ + * @author Paul Speed + */ +public class Filters +{ + /** + * Creates a filter that returns true for any value in the specified + * list of values and false for all other cases. + */ + public static Filter in( T... values ) + { + return in( new HashSet(Arrays.asList(values)) ); + } + + /** + * Creates a filter that returns true for any value in the specified + * collection and false for all other cases. + */ + public static Filter in( Collection collection ) + { + return new InFilter(collection); + } + + /** + * Creates a filter that returns true for any value NOT in the specified + * list of values and false for all other cases. This is the equivalent + * of calling not(in(values)). + */ + public static Filter notIn( T... values ) + { + return not( in( values ) ); + } + + /** + * Creates a filter that returns true for any value NOT in the specified + * collection and false for all other cases. This is the equivalent + * of calling not(in(collection)). + */ + public static Filter notIn( Collection collection ) + { + return not( in( collection ) ); + } + + /** + * Creates a filter that returns true for inputs that are .equals() + * equivalent to the specified value. + */ + public static Filter equalTo( T value ) + { + return new EqualToFilter(value); + } + + /** + * Creates a filter that returns true for inputs that are NOT .equals() + * equivalent to the specified value. This is the equivalent of calling + * not(equalTo(value)). + */ + public static Filter notEqualTo( T value ) + { + return not(equalTo(value)); + } + + /** + * Creates a filter that returns true when the specified delegate filter + * returns false, and vice versa. + */ + public static Filter not( Filter f ) + { + return new NotFilter(f); + } + + private static class EqualToFilter implements Filter + { + private T value; + + public EqualToFilter( T value ) + { + this.value = value; + } + + public boolean apply( T input ) + { + return value == input || (value != null && value.equals(input)); + } + } + + private static class InFilter implements Filter + { + private Collection collection; + + public InFilter( Collection collection ) + { + this.collection = collection; + } + + public boolean apply( T input ) + { + return collection.contains(input); + } + } + + private static class NotFilter implements Filter + { + private Filter delegate; + + public NotFilter( Filter delegate ) + { + this.delegate = delegate; + } + + public boolean apply( T input ) + { + return !delegate.apply(input); + } + } +} + + diff --git a/jme3-networking/src/main/java/com/jme3/network/HostedConnection.java b/jme3-networking/src/main/java/com/jme3/network/HostedConnection.java new file mode 100644 index 000000000..263011820 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/HostedConnection.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + +import java.util.Set; + +/** + * This is the connection back to a client that is being + * hosted in a server instance. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface HostedConnection extends MessageConnection +{ + /** + * Returns the Server instance that is hosting this connection. + */ + public Server getServer(); + + /** + * Returns the server-unique ID for this client. + */ + public int getId(); + + /** + * Returns the transport specific remote address of this connection + * as a string. This may or may not be unique per connection depending + * on the type of transport. It is provided for information and filtering + * purposes. + */ + public String getAddress(); + + /** + * Closes and removes this connection from the server + * sending the optional reason to the remote client. + */ + public void close( String reason ); + + /** + * Sets a session attribute specific to this connection. If the value + * is set to null then the attribute is removed. + * + * @return The previous session value for this key or null + * if there was no previous value. + */ + public Object setAttribute( String name, Object value ); + + /** + * Retrieves a previosly stored session attribute or + * null if no such attribute exists. + */ + public T getAttribute( String name ); + + /** + * Returns a read-only set of attribute names currently stored + * for this client session. + */ + public Set attributeNames(); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/Message.java b/jme3-networking/src/main/java/com/jme3/network/Message.java new file mode 100644 index 000000000..9586b039e --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/Message.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + +import com.jme3.network.serializing.Serializable; + +/** + * Interface implemented by all network messages. + * + * @version $Revision$ + * @author Paul Speed + */ +@Serializable() +public interface Message +{ + /** + * Sets this message to 'reliable' or not and returns this + * message. + */ + public Message setReliable(boolean f); + + /** + * Indicates which way an outgoing message should be sent + * or which way an incoming message was sent. + */ + public boolean isReliable(); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/MessageConnection.java b/jme3-networking/src/main/java/com/jme3/network/MessageConnection.java new file mode 100644 index 000000000..6c88636dc --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/MessageConnection.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + + +/** + * The source of a received message and the common abstract interface + * of client->server and server->client objects. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface MessageConnection +{ + /** + * Indicates the default reliable channel that is used + * when calling the channel-less send() with a reliable + * message. This channel number can be used in the send(channel, msg) + * version of send. + * + *

      Normally, callers should just call the regular non-channel + * send message but these channel numbers are useful for extensions + * that allow the user to specify a channel and want to still + * support the default channels.

      + */ + public static final int CHANNEL_DEFAULT_RELIABLE = -2; + + /** + * Indicates the default unreliable channel that is used + * when calling the channel-less send() with a reliable=false + * message. This channel number can be used in the send(channel, msg) + * version of send. + * + *

      Normally, callers should just call the regular non-channel + * send message but these channel numbers are useful for extensions + * that allow the user to specify a channel and want to still + * support the default channels.

      + */ + public static final int CHANNEL_DEFAULT_UNRELIABLE = -1; + + /** + * Sends a message to the other end of the connection. + */ + public void send( Message message ); + + /** + * Sends a message to the other end of the connection using + * the specified alternate channel. + */ + public void send( int channel, Message message ); +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/MessageListener.java b/jme3-networking/src/main/java/com/jme3/network/MessageListener.java new file mode 100644 index 000000000..68c708b16 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/MessageListener.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + + +/** + * Listener notified about new messages. + * + *

      Note about multithreading: on the server, these messages may + * be delivered by more than one thread depending on the server + * implementation used. Listener implementations should treat their + * shared data structures accordingly and set them up for multithreaded + * access. The only threading guarantee is that for a single + * HostedConnection, there will only ever be one thread at a time + * and the messages will always be delivered to that connection in the + * order that they were delivered.

      + * + * @version $Revision$ + * @author Paul Speed + */ +public interface MessageListener +{ + public void messageReceived( S source, Message m ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/Network.java b/jme3-networking/src/main/java/com/jme3/network/Network.java new file mode 100644 index 000000000..9dac8eae8 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/Network.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + +import com.jme3.network.base.DefaultClient; +import com.jme3.network.base.DefaultServer; +import com.jme3.network.base.TcpConnectorFactory; +import com.jme3.network.kernel.tcp.SelectorKernel; +import com.jme3.network.kernel.tcp.SocketConnector; +import com.jme3.network.kernel.udp.UdpConnector; +import com.jme3.network.kernel.udp.UdpKernel; +import java.io.IOException; +import java.net.InetAddress; + +/** + * The main service provider for conveniently creating + * server and client instances. + * + * @version $Revision$ + * @author Paul Speed + */ +public class Network +{ + public static final String DEFAULT_GAME_NAME = "Unnamed jME3 Game"; + public static final int DEFAULT_VERSION = 42; + + /** + * Creates a Server that will utilize both reliable and fast + * transports to communicate with clients. The specified port + * will be used for both TCP and UDP communication. + */ + public static Server createServer( int port ) throws IOException + { + return createServer( DEFAULT_GAME_NAME, DEFAULT_VERSION, port, port ); + } + + /** + * Creates a Server that will utilize both reliable and fast + * transports to communicate with clients. The specified port + * will be used for both TCP and UDP communication. + */ + public static Server createServer( int tcpPort, int udpPort ) throws IOException + { + return createServer( DEFAULT_GAME_NAME, DEFAULT_VERSION, tcpPort, udpPort ); + } + + /** + * Creates a named and versioned Server that will utilize both reliable and fast + * transports to communicate with clients. The specified port + * will be used for both TCP and UDP communication. + * + * @param gameName This is the name that identifies the game. Connecting clients + * must use this name or be turned away. + * @param version This is a game-specific verison that helps detect when out-of-date + * clients have connected to an incompatible server. + * @param tcpPort The port upon which the TCP hosting will listen for new connections. + * @param udpPort The port upon which the UDP hosting will listen for new 'fast' UDP + * messages. Set to -1 if 'fast' traffic should go over TCP. This will + * completely disable UDP traffic for this server. + */ + public static Server createServer( String gameName, int version, int tcpPort, int udpPort ) throws IOException + { + UdpKernel fast = udpPort == -1 ? null : new UdpKernel(udpPort); + SelectorKernel reliable = new SelectorKernel(tcpPort); + + return new DefaultServer( gameName, version, reliable, fast ); + } + + /** + * Creates a client that can be connected at a later time. + */ + public static NetworkClient createClient() + { + return createClient( DEFAULT_GAME_NAME, DEFAULT_VERSION ); + } + + /** + * Creates a client that can be connected at a later time. The specified + * game name and version must match the server or the client will be turned + * away. + */ + public static NetworkClient createClient( String gameName, int version ) + { + return new NetworkClientImpl(gameName, version); + } + + /** + * Creates a Client that communicates with the specified host and port + * using both reliable and fast transports. + */ + public static Client connectToServer( String host, int hostPort ) throws IOException + { + return connectToServer( DEFAULT_GAME_NAME, DEFAULT_VERSION, host, hostPort, hostPort ); + } + + /** + * Creates a Client that communicates with the specified host and separate TCP and UDP ports + * using both reliable and fast transports. + */ + public static Client connectToServer( String host, int hostPort, int remoteUdpPort ) throws IOException + { + return connectToServer( DEFAULT_GAME_NAME, DEFAULT_VERSION, host, hostPort, remoteUdpPort ); + } + + /** + * Creates a Client that communicates with the specified host and port + * using both reliable and fast transports. + */ + public static Client connectToServer( String gameName, int version, + String host, int hostPort ) throws IOException + { + return connectToServer( gameName, version, host, hostPort, hostPort ); + } + + /** + * Creates a Client that communicates with the specified host and and separate TCP and UDP ports + * using both reliable and fast transports. + * + * @param gameName This is the name that identifies the game. This must match + * the target server's name or this client will be turned away. + * @param version This is a game-specific verison that helps detect when out-of-date + * clients have connected to an incompatible server. This must match + * the server's version of this client will be turned away. + * @param hostPort The remote TCP port on the server to which this client should + * send reliable messages. + * @param remoteUdpPort The remote UDP port on the server to which this client should + * send 'fast'/unreliable messages. Set to -1 if 'fast' traffic should + * go over TCP. This will completely disable UDP traffic for this + * client. + */ + public static Client connectToServer( String gameName, int version, + String host, int hostPort, int remoteUdpPort ) throws IOException + { + InetAddress remoteAddress = InetAddress.getByName(host); + UdpConnector fast = remoteUdpPort == -1 ? null : new UdpConnector( remoteAddress, remoteUdpPort ); + SocketConnector reliable = new SocketConnector( remoteAddress, hostPort ); + + return new DefaultClient( gameName, version, reliable, fast, new TcpConnectorFactory(remoteAddress) ); + } + + + protected static class NetworkClientImpl extends DefaultClient implements NetworkClient + { + public NetworkClientImpl(String gameName, int version) + { + super( gameName, version ); + } + + public void connectToServer( String host, int port, int remoteUdpPort ) throws IOException + { + connectToServer( InetAddress.getByName(host), port, remoteUdpPort ); + } + + public void connectToServer( InetAddress address, int port, int remoteUdpPort ) throws IOException + { + UdpConnector fast = new UdpConnector( address, remoteUdpPort ); + SocketConnector reliable = new SocketConnector( address, port ); + + setPrimaryConnectors( reliable, fast, new TcpConnectorFactory(address) ); + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/NetworkClient.java b/jme3-networking/src/main/java/com/jme3/network/NetworkClient.java new file mode 100644 index 000000000..2f2a8e70e --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/NetworkClient.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + +import java.io.IOException; +import java.net.InetAddress; + +/** + * A Client whose network connection information can + * be provided post-creation. The actual connection stack + * will be setup the same as if Network.connectToServer + * had been called. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface NetworkClient extends Client +{ + /** + * Connects this client to the specified remote server and ports. + */ + public void connectToServer( String host, int port, int remoteUdpPort ) throws IOException; + + /** + * Connects this client to the specified remote server and ports. + * + * @param address The hosts internet address. + * @param port The remote TCP port on the server to which this client should + * send reliable messages. + * @param remoteUdpPort The remote UDP port on the server to which this client should + * send 'fast'/unreliable messages. Set to -1 if 'fast' traffic should + * go over TCP. This will completely disable UDP traffic for this + * client. + */ + public void connectToServer( InetAddress address, int port, int remoteUdpPort ) throws IOException; + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/Server.java b/jme3-networking/src/main/java/com/jme3/network/Server.java new file mode 100644 index 000000000..72926eab7 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/Server.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network; + +import java.util.Collection; + +/** + * Represents a host that can send and receive messages to + * a set of remote client connections. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface Server +{ + /** + * Returns the 'game name' for this server. This should match the + * 'game name' set on connecting clients or they will be turned away. + */ + public String getGameName(); + + /** + * Returns the game-specific version of this server used for detecting + * mismatched clients. + */ + public int getVersion(); + + /** + * Sends the specified message to all connected clients. + */ + public void broadcast( Message message ); + + /** + * Sends the specified message to all connected clients that match + * the filter. If no filter is specified then this is the same as + * calling broadcast(message) and the message will be delivered to + * all connections. + *

      Examples:

      + *
      +     *    // Broadcast to connections: conn1, conn2, and conn3
      +     *    server.broadcast( Filters.in( conn1, conn2, conn3 ), message );
      +     *
      +     *    // Broadcast to all connections exception source
      +     *    server.broadcast( Filters.notEqualTo( source ), message );
      +     *  
      + */ + public void broadcast( Filter filter, Message message ); + + /** + * Sends the specified message over the specified alternate channel to all connected + * clients that match the filter. If no filter is specified then this is the same as + * calling broadcast(message) and the message will be delivered to + * all connections. + *

      Examples:

      + *
      +     *    // Broadcast to connections: conn1, conn2, and conn3
      +     *    server.broadcast( Filters.in( conn1, conn2, conn3 ), message );
      +     *
      +     *    // Broadcast to all connections exception source
      +     *    server.broadcast( Filters.notEqualTo( source ), message );
      +     *  
      + */ + public void broadcast( int channel, Filter filter, Message message ); + + /** + * Start the server so that it will began accepting new connections + * and processing messages. + */ + public void start(); + + /** + * Adds an alternate channel to the server, using the specified port. This is an + * entirely separate connection where messages are sent and received in parallel + * to the two primary default channels. All channels must be added before the connection + * is started. + * Returns the ID of the created channel for use when specifying the channel in send or + * broadcast calls. The ID is returned entirely out of convenience since the IDs + * are predictably incremented. The first channel is 0, second is 1, and so on. + */ + public int addChannel( int port ); + + /** + * Returns true if the server has been started. + */ + public boolean isRunning(); + + /** + * Closes all client connections, stops and running processing threads, and + * closes the host connection. + */ + public void close(); + + /** + * Retrieves a hosted connection by ID. + */ + public HostedConnection getConnection( int id ); + + /** + * Retrieves a read-only collection of all currently connected connections. + */ + public Collection getConnections(); + + /** + * Returns true if the server has active connections at the time of this + * call. + */ + public boolean hasConnections(); + + /** + * Adds a listener that will be notified when new hosted connections + * arrive. + */ + public void addConnectionListener( ConnectionListener listener ); + + /** + * Removes a previously registered connection listener. + */ + public void removeConnectionListener( ConnectionListener listener ); + + /** + * Adds a listener that will be notified when any message or object + * is received from one of the clients. + * + *

      Note about MessageListener multithreading: on the server, message events may + * be delivered by more than one thread depending on the server + * implementation used. Listener implementations should treat their + * shared data structures accordingly and set them up for multithreaded + * access. The only threading guarantee is that for a single + * HostedConnection, there will only ever be one thread at a time + * and the messages will always be delivered to that connection in the + * order that they were delivered. This is the only restriction placed + * upon server message dispatch pool implementations.

      + */ + public void addMessageListener( MessageListener listener ); + + /** + * Adds a listener that will be notified when messages of the specified + * types are received from one of the clients. + */ + public void addMessageListener( MessageListener listener, Class... classes ); + + /** + * Removes a previously registered wildcard listener. This does + * not remove this listener from any type-specific registrations. + */ + public void removeMessageListener( MessageListener listener ); + + /** + * Removes a previously registered type-specific listener from + * the specified types. + */ + public void removeMessageListener( MessageListener listener, Class... classes ); + + +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/base/ConnectorAdapter.java b/jme3-networking/src/main/java/com/jme3/network/base/ConnectorAdapter.java new file mode 100644 index 000000000..6f4935f8f --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/base/ConnectorAdapter.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.base; + +import com.jme3.network.ErrorListener; +import com.jme3.network.Message; +import com.jme3.network.MessageListener; +import com.jme3.network.kernel.Connector; +import com.jme3.network.kernel.ConnectorException; +import java.nio.ByteBuffer; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Wraps a single Connector and forwards new messages + * to the supplied message dispatcher. This is used + * by DefaultClient to manage its connector objects. + * This is only responsible for message reading and provides + * no support for buffering writes. + * + *

      This adapter assumes a simple protocol where two + * bytes define a (short) object size with the object data + * to follow. Note: this limits the size of serialized + * objects to 32676 bytes... even though, for example, + * datagram packets can hold twice that. :P

      + * + * @version $Revision$ + * @author Paul Speed + */ +public class ConnectorAdapter extends Thread +{ + private static final int OUTBOUND_BACKLOG = 16000; + + private Connector connector; + private MessageListener dispatcher; + private ErrorListener errorHandler; + private AtomicBoolean go = new AtomicBoolean(true); + + private BlockingQueue outbound; + + // Writes messages out on a background thread + private WriterThread writer; + + // Marks the messages as reliable or not if they came + // through this connector. + private boolean reliable; + + public ConnectorAdapter( Connector connector, MessageListener dispatcher, + ErrorListener errorHandler, boolean reliable ) + { + super( String.valueOf(connector) ); + this.connector = connector; + this.dispatcher = dispatcher; + this.errorHandler = errorHandler; + this.reliable = reliable; + setDaemon(true); + + // The backlog makes sure that the outbound channel blocks once + // a certain backlog level is reached. It is set high so that it + // is only reached in the worst cases... which are usually things like + // raw throughput tests. Technically, a saturated TCP channel could + // back up quite a bit if the buffers are full and the socket has + // stalled but 16,000 messages is still a big backlog. + outbound = new ArrayBlockingQueue(OUTBOUND_BACKLOG); + + // Note: this technically adds a potential deadlock case + // with the above code where there wasn't one before. For example, + // if a TCP outbound queue fills to capacity and a client sends + // in such a way that they block TCP message handling then if the HostedConnection + // on the server is similarly blocked then the TCP network buffers may + // all get full and no outbound messages move and we forever block + // on the queue. + // However, in practice this can't really happen... or at least it's + // the sign of other really bad things. + // First, currently the server-side outbound queues are all unbounded and + // so won't ever block the handling of messages if the outbound channel is full. + // Second, there would have to be a huge amount of data backlog for this + // to ever occur anyway. + // Third, it's a sign of a really poor architecture if 16,000 messages + // can go out in a way that blocks reads. + + writer = new WriterThread(); + writer.start(); + } + + public void close() + { + go.set(false); + + // Kill the writer service + writer.shutdown(); + + if( connector.isConnected() ) + { + // Kill the connector + connector.close(); + } + } + + protected void dispatch( Message m ) + { + dispatcher.messageReceived( null, m ); + } + + public void write( ByteBuffer data ) + { + try { + outbound.put( data ); + } catch( InterruptedException e ) { + throw new RuntimeException( "Interrupted while waiting for queue to drain", e ); + } + } + + protected void handleError( Exception e ) + { + if( !go.get() ) + return; + + errorHandler.handleError( this, e ); + } + + public void run() + { + MessageProtocol protocol = new MessageProtocol(); + + try { + while( go.get() ) { + ByteBuffer buffer = connector.read(); + if( buffer == null ) { + if( go.get() ) { + throw new ConnectorException( "Connector closed." ); + } else { + // Just dump out because a null buffer is expected + // from a closed/closing connector + break; + } + } + + protocol.addBuffer( buffer ); + + Message m = null; + while( (m = protocol.getMessage()) != null ) { + m.setReliable( reliable ); + dispatch( m ); + } + } + } catch( Exception e ) { + handleError( e ); + } + } + + protected class WriterThread extends Thread + { + public WriterThread() + { + super( String.valueOf(connector) + "-writer" ); + } + + public void shutdown() + { + interrupt(); + } + + private void write( ByteBuffer data ) + { + try { + connector.write(data); + } catch( Exception e ) { + handleError( e ); + } + } + + public void run() + { + while( go.get() ) { + try { + ByteBuffer data = outbound.take(); + write(data); + } catch( InterruptedException e ) { + if( !go.get() ) + return; + throw new RuntimeException( "Interrupted waiting for data", e ); + } + } + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/base/ConnectorFactory.java b/jme3-networking/src/main/java/com/jme3/network/base/ConnectorFactory.java new file mode 100644 index 000000000..9a15f17b0 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/base/ConnectorFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.base; + +import com.jme3.network.kernel.Connector; +import java.io.IOException; + + +/** + * Creates Connectors for a specific host. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface ConnectorFactory +{ + public Connector createConnector( int channel, int port ) throws IOException; +} diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java new file mode 100644 index 000000000..54e8fd7f9 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.base; + +import com.jme3.network.*; +import com.jme3.network.ClientStateListener.DisconnectInfo; +import com.jme3.network.kernel.Connector; +import com.jme3.network.message.ChannelInfoMessage; +import com.jme3.network.message.ClientRegistrationMessage; +import com.jme3.network.message.DisconnectMessage; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A default implementation of the Client interface that delegates + * its network connectivity to a kernel.Connector. + * + * @version $Revision$ + * @author Paul Speed + */ +public class DefaultClient implements Client +{ + static Logger log = Logger.getLogger(DefaultClient.class.getName()); + + // First two channels are reserved for reliable and + // unreliable. Note: channels are endpoint specific so these + // constants and the handling need not have anything to do with + // the same constants in DefaultServer... which is why they are + // separate. + private static final int CH_RELIABLE = 0; + private static final int CH_UNRELIABLE = 1; + private static final int CH_FIRST = 2; + + private ThreadLocal dataBuffer = new ThreadLocal(); + + private int id = -1; + private boolean isRunning = false; + private CountDownLatch connecting = new CountDownLatch(1); + private String gameName; + private int version; + private MessageListenerRegistry messageListeners = new MessageListenerRegistry(); + private List stateListeners = new CopyOnWriteArrayList(); + private List> errorListeners = new CopyOnWriteArrayList>(); + private Redispatch dispatcher = new Redispatch(); + private List channels = new ArrayList(); + + private ConnectorFactory connectorFactory; + + public DefaultClient( String gameName, int version ) + { + this.gameName = gameName; + this.version = version; + } + + public DefaultClient( String gameName, int version, Connector reliable, Connector fast, + ConnectorFactory connectorFactory ) + { + this( gameName, version ); + setPrimaryConnectors( reliable, fast, connectorFactory ); + } + + protected void setPrimaryConnectors( Connector reliable, Connector fast, ConnectorFactory connectorFactory ) + { + if( reliable == null ) + throw new IllegalArgumentException( "The reliable connector cannot be null." ); + if( isRunning ) + throw new IllegalStateException( "Client is already started." ); + if( !channels.isEmpty() ) + throw new IllegalStateException( "Channels already exist." ); + + this.connectorFactory = connectorFactory; + channels.add(new ConnectorAdapter(reliable, dispatcher, dispatcher, true)); + if( fast != null ) { + channels.add(new ConnectorAdapter(fast, dispatcher, dispatcher, false)); + } else { + // Add the null adapter to keep the indexes right + channels.add(null); + } + } + + protected void checkRunning() + { + if( !isRunning ) + throw new IllegalStateException( "Client is not started." ); + } + + public void start() + { + if( isRunning ) + throw new IllegalStateException( "Client is already started." ); + + // Start up the threads and stuff for the + // connectors that we have + for( ConnectorAdapter ca : channels ) { + if( ca == null ) + continue; + ca.start(); + } + + // Send our connection message with a generated ID until + // we get one back from the server. We'll hash time in + // millis and time in nanos. + // This is used to match the TCP and UDP endpoints up on the + // other end since they may take different routes to get there. + // Behind NAT, many game clients may be coming over the same + // IP address from the server's perspective and they may have + // their UDP ports mapped all over the place. + // + // Since currentTimeMillis() is absolute time and nano time + // is roughtly related to system start time, adding these two + // together should be plenty unique for our purposes. It wouldn't + // hurt to reconcile with IP on the server side, though. + long tempId = System.currentTimeMillis() + System.nanoTime(); + + // Set it true here so we can send some messages. + isRunning = true; + + ClientRegistrationMessage reg; + reg = new ClientRegistrationMessage(); + reg.setId(tempId); + reg.setGameName(getGameName()); + reg.setVersion(getVersion()); + reg.setReliable(true); + send(CH_RELIABLE, reg, false); + + // Send registration messages to any other configured + // connectors + reg = new ClientRegistrationMessage(); + reg.setId(tempId); + reg.setReliable(false); + for( int ch = CH_UNRELIABLE; ch < channels.size(); ch++ ) { + if( channels.get(ch) == null ) + continue; + send(ch, reg, false); + } + } + + protected void waitForConnected() + { + if( isConnected() ) + return; + + try { + connecting.await(); + } catch( InterruptedException e ) { + throw new RuntimeException( "Interrupted waiting for connect", e ); + } + } + + public boolean isConnected() + { + return id != -1 && isRunning; + } + + public int getId() + { + return id; + } + + public String getGameName() + { + return gameName; + } + + public int getVersion() + { + return version; + } + + public void send( Message message ) + { + if( message.isReliable() || channels.get(CH_UNRELIABLE) == null ) { + send(CH_RELIABLE, message, true); + } else { + send(CH_UNRELIABLE, message, true); + } + } + + public void send( int channel, Message message ) + { + if( channel >= 0 ) { + // Make sure we aren't still connecting. Channels + // won't be valid until we are fully connected since + // we receive the channel list from the server. + // The default channels don't require the connection + // to be fully up before sending. + waitForConnected(); + } + + if( channel < CHANNEL_DEFAULT_RELIABLE || channel + CH_FIRST >= channels.size() ) { + throw new IllegalArgumentException( "Channel is undefined:" + channel ); + } + send(channel + CH_FIRST, message, true); + } + + protected void send( int channel, Message message, boolean waitForConnected ) + { + checkRunning(); + + if( waitForConnected ) { + // Make sure we aren't still connecting + waitForConnected(); + } + + ByteBuffer buffer = dataBuffer.get(); + if( buffer == null ) { + buffer = ByteBuffer.allocate( 65536 + 2 ); + dataBuffer.set(buffer); + } + buffer.clear(); + + // Convert the message to bytes + buffer = MessageProtocol.messageToBuffer(message, buffer); + + // Since we share the buffer between invocations, we will need to + // copy this message's part out of it. This is because we actually + // do the send on a background thread. + byte[] temp = new byte[buffer.remaining()]; + System.arraycopy(buffer.array(), buffer.position(), temp, 0, buffer.remaining()); + buffer = ByteBuffer.wrap(temp); + + channels.get(channel).write(buffer); + } + + public void close() + { + checkRunning(); + + closeConnections( null ); + } + + protected void closeConnections( DisconnectInfo info ) + { + if( !isRunning ) + return; + + // Send a close message + + // Tell the thread it's ok to die + for( ConnectorAdapter ca : channels ) { + if( ca == null ) + continue; + ca.close(); + } + + // Wait for the threads? + + // Just in case we never fully connected + connecting.countDown(); + + fireDisconnected(info); + + isRunning = false; + } + + public void addClientStateListener( ClientStateListener listener ) + { + stateListeners.add( listener ); + } + + public void removeClientStateListener( ClientStateListener listener ) + { + stateListeners.remove( listener ); + } + + public void addMessageListener( MessageListener listener ) + { + messageListeners.addMessageListener( listener ); + } + + public void addMessageListener( MessageListener listener, Class... classes ) + { + messageListeners.addMessageListener( listener, classes ); + } + + public void removeMessageListener( MessageListener listener ) + { + messageListeners.removeMessageListener( listener ); + } + + public void removeMessageListener( MessageListener listener, Class... classes ) + { + messageListeners.removeMessageListener( listener, classes ); + } + + public void addErrorListener( ErrorListener listener ) + { + errorListeners.add( listener ); + } + + public void removeErrorListener( ErrorListener listener ) + { + errorListeners.remove( listener ); + } + + protected void fireConnected() + { + for( ClientStateListener l : stateListeners ) { + l.clientConnected( this ); + } + } + + protected void fireDisconnected( DisconnectInfo info ) + { + for( ClientStateListener l : stateListeners ) { + l.clientDisconnected( this, info ); + } + } + + /** + * Either calls the ErrorListener or closes the connection + * if there are no listeners. + */ + protected void handleError( Throwable t ) + { + // If there are no listeners then close the connection with + // a reason + if( errorListeners.isEmpty() ) { + log.log( Level.SEVERE, "Termining connection due to unhandled error", t ); + DisconnectInfo info = new DisconnectInfo(); + info.reason = "Connection Error"; + info.error = t; + closeConnections(info); + return; + } + + for( ErrorListener l : errorListeners ) { + l.handleError( this, t ); + } + } + + protected void configureChannels( long tempId, int[] ports ) { + + try { + for( int i = 0; i < ports.length; i++ ) { + Connector c = connectorFactory.createConnector( i, ports[i] ); + ConnectorAdapter ca = new ConnectorAdapter(c, dispatcher, dispatcher, true); + int ch = channels.size(); + channels.add( ca ); + + // Need to send the connection its hook-up registration + // and start it. + ca.start(); + ClientRegistrationMessage reg; + reg = new ClientRegistrationMessage(); + reg.setId(tempId); + reg.setReliable(true); + send( ch, reg, false ); + } + } catch( IOException e ) { + throw new RuntimeException( "Error configuring channels", e ); + } + } + + protected void dispatch( Message m ) + { + // Pull off the connection management messages we're + // interested in and then pass on the rest. + if( m instanceof ClientRegistrationMessage ) { + // Then we've gotten our real id + this.id = (int)((ClientRegistrationMessage)m).getId(); + log.log( Level.FINE, "Connection established, id:{0}.", this.id ); + connecting.countDown(); + fireConnected(); + return; + } else if( m instanceof ChannelInfoMessage ) { + // This is an interum step in the connection process and + // now we need to add a bunch of connections + configureChannels( ((ChannelInfoMessage)m).getId(), ((ChannelInfoMessage)m).getPorts() ); + return; + } else if( m instanceof DisconnectMessage ) { + // Can't do too much else yet + String reason = ((DisconnectMessage)m).getReason(); + log.log( Level.SEVERE, "Connection terminated, reason:{0}.", reason ); + DisconnectInfo info = new DisconnectInfo(); + info.reason = reason; + closeConnections(info); + } + + // Make sure client MessageListeners are called single-threaded + // since it could receive messages from the TCP and UDP + // thread simultaneously. + synchronized( this ) { + messageListeners.messageReceived( this, m ); + } + } + + protected class Redispatch implements MessageListener, ErrorListener + { + public void messageReceived( Object source, Message m ) + { + dispatch( m ); + } + + public void handleError( Object source, Throwable t ) + { + // Only doing the DefaultClient.this to make the code + // checker happy... it compiles fine without it but I + // don't like red lines in my editor. :P + DefaultClient.this.handleError( t ); + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java new file mode 100644 index 000000000..3816fbace --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java @@ -0,0 +1,592 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.base; + +import com.jme3.network.*; +import com.jme3.network.kernel.Endpoint; +import com.jme3.network.kernel.Kernel; +import com.jme3.network.message.ChannelInfoMessage; +import com.jme3.network.message.ClientRegistrationMessage; +import com.jme3.network.message.DisconnectMessage; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A default implementation of the Server interface that delegates + * its network connectivity to kernel.Kernel. + * + * @version $Revision$ + * @author Paul Speed + */ +public class DefaultServer implements Server +{ + static Logger log = Logger.getLogger(DefaultServer.class.getName()); + + // First two channels are reserved for reliable and + // unreliable + private static final int CH_RELIABLE = 0; + private static final int CH_UNRELIABLE = 1; + private static final int CH_FIRST = 2; + + private boolean isRunning = false; + private AtomicInteger nextId = new AtomicInteger(0); + private String gameName; + private int version; + private KernelFactory kernelFactory = KernelFactory.DEFAULT; + private KernelAdapter reliableAdapter; + private KernelAdapter fastAdapter; + private List channels = new ArrayList(); + private List alternatePorts = new ArrayList(); + private Redispatch dispatcher = new Redispatch(); + private Map connections = new ConcurrentHashMap(); + private Map endpointConnections + = new ConcurrentHashMap(); + + // Keeps track of clients for whom we've only received the UDP + // registration message + private Map connecting = new ConcurrentHashMap(); + + private MessageListenerRegistry messageListeners + = new MessageListenerRegistry(); + private List connectionListeners = new CopyOnWriteArrayList(); + + public DefaultServer( String gameName, int version, Kernel reliable, Kernel fast ) + { + if( reliable == null ) + throw new IllegalArgumentException( "Default server reqiures a reliable kernel instance." ); + + this.gameName = gameName; + this.version = version; + + reliableAdapter = new KernelAdapter( this, reliable, dispatcher, true ); + channels.add( reliableAdapter ); + if( fast != null ) { + fastAdapter = new KernelAdapter( this, fast, dispatcher, false ); + channels.add( fastAdapter ); + } + } + + public String getGameName() + { + return gameName; + } + + public int getVersion() + { + return version; + } + + public int addChannel( int port ) + { + if( isRunning ) + throw new IllegalStateException( "Channels cannot be added once server is started." ); + + // Note: it does bug me that channels aren't 100% universal and + // setup externally but it requires a more invasive set of changes + // for "connection types" and some kind of registry of kernel and + // connector factories. This really would be the best approach and + // would allow all kinds of channel customization maybe... but for + // now, we hard-code the standard connections and treat the +2 extras + // differently. + + // Check for consistency with the channels list + if( channels.size() - CH_FIRST != alternatePorts.size() ) + throw new IllegalStateException( "Channel and port lists do not match." ); + + try { + int result = alternatePorts.size(); + alternatePorts.add(port); + + Kernel kernel = kernelFactory.createKernel(result, port); + channels.add( new KernelAdapter(this, kernel, dispatcher, true) ); + + return result; + } catch( IOException e ) { + throw new RuntimeException( "Error adding channel for port:" + port, e ); + } + } + + protected void checkChannel( int channel ) + { + if( channel < MessageConnection.CHANNEL_DEFAULT_RELIABLE + || channel >= alternatePorts.size() ) { + throw new IllegalArgumentException( "Channel is undefined:" + channel ); + } + } + + public void start() + { + if( isRunning ) + throw new IllegalStateException( "Server is already started." ); + + // Initialize the kernels + for( KernelAdapter ka : channels ) { + ka.initialize(); + } + + // Start em up + for( KernelAdapter ka : channels ) { + ka.start(); + } + + isRunning = true; + } + + public boolean isRunning() + { + return isRunning; + } + + public void close() + { + if( !isRunning ) + throw new IllegalStateException( "Server is not started." ); + + try { + // Kill the adpaters, they will kill the kernels + for( KernelAdapter ka : channels ) { + ka.close(); + } + + isRunning = false; + } catch( InterruptedException e ) { + throw new RuntimeException( "Interrupted while closing", e ); + } + } + + public void broadcast( Message message ) + { + broadcast( null, message ); + } + + public void broadcast( Filter filter, Message message ) + { + if( connections.isEmpty() ) + return; + + ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null); + + FilterAdapter adapter = filter == null ? null : new FilterAdapter(filter); + + if( message.isReliable() || fastAdapter == null ) { + // Don't need to copy the data because message protocol is already + // giving us a fresh buffer + reliableAdapter.broadcast( adapter, buffer, true, false ); + } else { + fastAdapter.broadcast( adapter, buffer, false, false ); + } + } + + public void broadcast( int channel, Filter filter, Message message ) + { + if( connections.isEmpty() ) + return; + + checkChannel(channel); + + ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null); + + FilterAdapter adapter = filter == null ? null : new FilterAdapter(filter); + + channels.get(channel+CH_FIRST).broadcast( adapter, buffer, true, false ); + } + + public HostedConnection getConnection( int id ) + { + return connections.get(id); + } + + public boolean hasConnections() + { + return !connections.isEmpty(); + } + + public Collection getConnections() + { + return Collections.unmodifiableCollection((Collection)connections.values()); + } + + public void addConnectionListener( ConnectionListener listener ) + { + connectionListeners.add(listener); + } + + public void removeConnectionListener( ConnectionListener listener ) + { + connectionListeners.remove(listener); + } + + public void addMessageListener( MessageListener listener ) + { + messageListeners.addMessageListener( listener ); + } + + public void addMessageListener( MessageListener listener, Class... classes ) + { + messageListeners.addMessageListener( listener, classes ); + } + + public void removeMessageListener( MessageListener listener ) + { + messageListeners.removeMessageListener( listener ); + } + + public void removeMessageListener( MessageListener listener, Class... classes ) + { + messageListeners.removeMessageListener( listener, classes ); + } + + protected void dispatch( HostedConnection source, Message m ) + { + if( source == null ) { + messageListeners.messageReceived( source, m ); + } else { + + // A semi-heavy handed way to make sure the listener + // doesn't get called at the same time from two different + // threads for the same hosted connection. + synchronized( source ) { + messageListeners.messageReceived( source, m ); + } + } + } + + protected void fireConnectionAdded( HostedConnection conn ) + { + for( ConnectionListener l : connectionListeners ) { + l.connectionAdded( this, conn ); + } + } + + protected void fireConnectionRemoved( HostedConnection conn ) + { + for( ConnectionListener l : connectionListeners ) { + l.connectionRemoved( this, conn ); + } + } + + protected int getChannel( KernelAdapter ka ) + { + return channels.indexOf(ka); + } + + protected void registerClient( KernelAdapter ka, Endpoint p, ClientRegistrationMessage m ) + { + Connection addedConnection = null; + + // generally this will only be called by one thread but it's + // important enough I won't take chances + synchronized( this ) { + // Grab the random ID that the client created when creating + // its two registration messages + long tempId = m.getId(); + + // See if we already have one + Connection c = connecting.remove(tempId); + if( c == null ) { + c = new Connection(channels.size()); + log.log( Level.FINE, "Registering client for endpoint, pass 1:{0}.", p ); + } else { + log.log( Level.FINE, "Refining client registration for endpoint:{0}.", p ); + } + + // Fill in what we now know + int channel = getChannel(ka); + c.setChannel(channel, p); + log.log( Level.FINE, "Setting up channel:{0}", channel ); + + // If it's channel 0 then this is the initial connection + // and we will send the connection information + if( channel == CH_RELIABLE ) { + // Validate the name and version which is only sent + // over the reliable connection at this point. + if( !getGameName().equals(m.getGameName()) + || getVersion() != m.getVersion() ) { + + log.log( Level.FINE, "Kicking client due to name/version mismatch:{0}.", c ); + + // Need to kick them off... I may regret doing this from within + // the sync block but the alternative is more code + c.close( "Server client mismatch, server:" + getGameName() + " v" + getVersion() + + " client:" + m.getGameName() + " v" + m.getVersion() ); + return; + } + + // Else send the extra channel information to the client + if( !alternatePorts.isEmpty() ) { + ChannelInfoMessage cim = new ChannelInfoMessage( m.getId(), alternatePorts ); + c.send(cim); + } + } + + if( c.isComplete() ) { + // Then we are fully connected + if( connections.put( c.getId(), c ) == null ) { + + for( Endpoint cp : c.channels ) { + if( cp == null ) + continue; + endpointConnections.put( cp, c ); + } + + addedConnection = c; + } + } else { + // Need to keep getting channels so we'll keep it in + // the map + connecting.put(tempId, c); + } + } + + // Best to do this outside of the synch block to avoid + // over synchronizing which is the path to deadlocks + if( addedConnection != null ) { + log.log( Level.FINE, "Client registered:{0}.", addedConnection ); + + // Send the ID back to the client letting it know it's + // fully connected. + m = new ClientRegistrationMessage(); + m.setId( addedConnection.getId() ); + m.setReliable(true); + addedConnection.send(m); + + // Now we can notify the listeners about the + // new connection. + fireConnectionAdded( addedConnection ); + } + } + + protected HostedConnection getConnection( Endpoint endpoint ) + { + return endpointConnections.get(endpoint); + } + + protected void connectionClosed( Endpoint p ) + { + if( p.isConnected() ) { + log.log( Level.FINE, "Connection closed:{0}.", p ); + } else { + log.log( Level.FINE, "Connection closed:{0}.", p ); + } + + // Try to find the endpoint in all ways that it might + // exist. Note: by this point the raw network channel is + // closed already. + + // Also note: this method will be called multiple times per + // HostedConnection if it has multiple endpoints. + + Connection removed = null; + synchronized( this ) { + // Just in case the endpoint was still connecting + connecting.values().remove(p); + + // And the regular management + removed = (Connection)endpointConnections.remove(p); + if( removed != null ) { + connections.remove( removed.getId() ); + } + + log.log( Level.FINE, "Connections size:{0}", connections.size() ); + log.log( Level.FINE, "Endpoint mappings size:{0}", endpointConnections.size() ); + } + + // Better not to fire events while we hold a lock + // so always do this outside the synch block. + // Note: checking removed.closed just to avoid spurious log messages + // since in general we are called back for every endpoint closing. + if( removed != null && !removed.closed ) { + + log.log( Level.FINE, "Client closed:{0}.", removed ); + + removed.closeConnection(); + } + } + + protected class Connection implements HostedConnection + { + private int id; + private boolean closed; + private Endpoint[] channels; + private int setChannelCount = 0; + + private Map sessionData = new ConcurrentHashMap(); + + public Connection( int channelCount ) + { + id = nextId.getAndIncrement(); + channels = new Endpoint[channelCount]; + } + + void setChannel( int channel, Endpoint p ) + { + if( channels[channel] != null && channels[channel] != p ) { + throw new RuntimeException( "Channel has already been set:" + channel + + " = " + channels[channel] + ", cannot be set to:" + p ); + } + channels[channel] = p; + if( p != null ) + setChannelCount++; + } + + boolean isComplete() + { + return setChannelCount == channels.length; + } + + public Server getServer() + { + return DefaultServer.this; + } + + public int getId() + { + return id; + } + + public String getAddress() + { + return channels[CH_RELIABLE] == null ? null : channels[CH_RELIABLE].getAddress(); + } + + public void send( Message message ) + { + ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null); + if( message.isReliable() || channels[CH_UNRELIABLE] == null ) { + channels[CH_RELIABLE].send( buffer ); + } else { + channels[CH_UNRELIABLE].send( buffer ); + } + } + + public void send( int channel, Message message ) + { + checkChannel(channel); + ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null); + channels[channel+CH_FIRST].send(buffer); + } + + protected void closeConnection() + { + if( closed ) + return; + closed = true; + + // Make sure all endpoints are closed. Note: reliable + // should always already be closed through all paths that I + // can conceive... but it doesn't hurt to be sure. + for( Endpoint p : channels ) { + if( p == null ) + continue; + p.close(); + } + + fireConnectionRemoved( this ); + } + + public void close( String reason ) + { + // Send a reason + DisconnectMessage m = new DisconnectMessage(); + m.setType( DisconnectMessage.KICK ); + m.setReason( reason ); + m.setReliable( true ); + send( m ); + + // Just close the reliable endpoint + // fast will be cleaned up as a side-effect + // when closeConnection() is called by the + // connectionClosed() endpoint callback. + if( channels[CH_RELIABLE] != null ) { + // Close with flush so we make sure our + // message gets out + channels[CH_RELIABLE].close(true); + } + } + + public Object setAttribute( String name, Object value ) + { + if( value == null ) + return sessionData.remove(name); + return sessionData.put(name, value); + } + + @SuppressWarnings("unchecked") + public T getAttribute( String name ) + { + return (T)sessionData.get(name); + } + + public Set attributeNames() + { + return Collections.unmodifiableSet(sessionData.keySet()); + } + + public String toString() + { + return "Connection[ id=" + id + ", reliable=" + channels[CH_RELIABLE] + + ", fast=" + channels[CH_UNRELIABLE] + " ]"; + } + } + + protected class Redispatch implements MessageListener + { + public void messageReceived( HostedConnection source, Message m ) + { + dispatch( source, m ); + } + } + + protected class FilterAdapter implements Filter + { + private Filter delegate; + + public FilterAdapter( Filter delegate ) + { + this.delegate = delegate; + } + + public boolean apply( Endpoint input ) + { + HostedConnection conn = getConnection( input ); + if( conn == null ) + return false; + return delegate.apply(conn); + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/base/KernelAdapter.java b/jme3-networking/src/main/java/com/jme3/network/base/KernelAdapter.java new file mode 100644 index 000000000..38fc98fa8 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/base/KernelAdapter.java @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.base; + +import com.jme3.network.Filter; +import com.jme3.network.HostedConnection; +import com.jme3.network.Message; +import com.jme3.network.MessageListener; +import com.jme3.network.kernel.Endpoint; +import com.jme3.network.kernel.EndpointEvent; +import com.jme3.network.kernel.Envelope; +import com.jme3.network.kernel.Kernel; +import com.jme3.network.message.ClientRegistrationMessage; +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Wraps a single Kernel and forwards new messages + * to the supplied message dispatcher and new endpoint + * events to the connection dispatcher. This is used + * by DefaultServer to manage its kernel objects. + * + *

      This adapter assumes a simple protocol where two + * bytes define a (short) object size with the object data + * to follow. Note: this limits the size of serialized + * objects to 32676 bytes... even though, for example, + * datagram packets can hold twice that. :P

      + * + * @version $Revision$ + * @author Paul Speed + */ +public class KernelAdapter extends Thread +{ + static Logger log = Logger.getLogger(KernelAdapter.class.getName()); + + private DefaultServer server; // this is unfortunate + private Kernel kernel; + private MessageListener messageDispatcher; + private AtomicBoolean go = new AtomicBoolean(true); + + // Keeps track of the in-progress messages that are received + // on reliable connections + private Map messageBuffers = new ConcurrentHashMap(); + + // Marks the messages as reliable or not if they came + // through this connector. + private boolean reliable; + + public KernelAdapter( DefaultServer server, Kernel kernel, MessageListener messageDispatcher, + boolean reliable ) + { + super( String.valueOf(kernel) ); + this.server = server; + this.kernel = kernel; + this.messageDispatcher = messageDispatcher; + this.reliable = reliable; + setDaemon(true); + } + + public Kernel getKernel() + { + return kernel; + } + + public void initialize() + { + kernel.initialize(); + } + + public void broadcast( Filter filter, ByteBuffer data, boolean reliable, + boolean copy ) + { + kernel.broadcast( filter, data, reliable, copy ); + } + + public void close() throws InterruptedException + { + go.set(false); + + // Kill the kernel + kernel.terminate(); + } + + protected void reportError( Endpoint p, Object context, Exception e ) + { + // Should really be queued up so the outer thread can + // retrieve them. For now we'll just log it. FIXME + log.log( Level.SEVERE, "Unhandled error, endpoint:" + p + ", context:" + context, e ); + + // In lieu of other options, at least close the endpoint + p.close(); + } + + protected HostedConnection getConnection( Endpoint p ) + { + return server.getConnection(p); + } + + protected void connectionClosed( Endpoint p ) + { + // Remove any message buffer we've been accumulating + // on behalf of this endpoing + messageBuffers.remove(p); + + log.log( Level.FINE, "Buffers size:{0}", messageBuffers.size() ); + + server.connectionClosed(p); + } + + /** + * Note on threading for those writing their own server + * or adapter implementations. The rule that a single connection be + * processed by only one thread at a time is more about ensuring that + * the messages are delivered in the order that they are received + * than for any user-code safety. 99% of the time the user code should + * be writing for multithreaded access anyway. + * + *

      The issue with the messages is that if a an implementation is + * using a general thread pool then it would be possible for a + * naive implementation to have one thread grab an Envelope from + * connection 1's and another grab the next Envelope. Since an Envelope + * may contain several messages, delivering the second thread's messages + * before or during the first's would be really confusing and hard + * to code for in user code.

      + * + *

      And that's why this note is here. DefaultServer does a rudimentary + * per-connection locking but it couldn't possibly guard against + * out of order Envelope processing.

      + */ + protected void dispatch( Endpoint p, Message m ) + { + // Because this class is the only one with the information + // to do it... we need to pull of the registration message + // here. + if( m instanceof ClientRegistrationMessage ) { + server.registerClient( this, p, (ClientRegistrationMessage)m ); + return; + } + + try { + HostedConnection source = getConnection(p); + if( source == null ) { + if( reliable ) { + // If it's a reliable connection then it's slightly more + // concerning but this can happen all the time for a UDP endpoint. + log.log( Level.WARNING, "Recieved message from unconnected endpoint:" + p + " message:" + m ); + } + return; + } + messageDispatcher.messageReceived( source, m ); + } catch( Exception e ) { + reportError(p, m, e); + } + } + + protected MessageProtocol getMessageBuffer( Endpoint p ) + { + if( !reliable ) { + // Since UDP comes in packets and they aren't split + // up, there is no reason to buffer. In fact, there would + // be a down side because there is no way for us to reliably + // clean these up later since we'd create another one for + // any random UDP packet that comes to the port. + return new MessageProtocol(); + } else { + // See if we already have one + MessageProtocol result = messageBuffers.get(p); + if( result == null ) { + result = new MessageProtocol(); + messageBuffers.put(p, result); + } + return result; + } + } + + protected void createAndDispatch( Envelope env ) + { + MessageProtocol protocol = getMessageBuffer(env.getSource()); + + byte[] data = env.getData(); + ByteBuffer buffer = ByteBuffer.wrap(data); + + int count = protocol.addBuffer( buffer ); + if( count == 0 ) { + // This can happen if there was only a partial message + // received. However, this should never happen for unreliable + // connections. + if( !reliable ) { + // Log some additional information about the packet. + int len = Math.min( 10, data.length ); + StringBuilder sb = new StringBuilder(); + for( int i = 0; i < len; i++ ) { + sb.append( "[" + Integer.toHexString(data[i]) + "]" ); + } + log.log( Level.FINE, "First 10 bytes of incomplete nessage:" + sb ); + throw new RuntimeException( "Envelope contained incomplete data:" + env ); + } + } + + // Should be complete... and maybe we should check but we don't + Message m = null; + while( (m = protocol.getMessage()) != null ) { + m.setReliable(reliable); + dispatch( env.getSource(), m ); + } + } + + protected void createAndDispatch( EndpointEvent event ) + { + // Only need to tell the server about disconnects + if( event.getType() == EndpointEvent.Type.REMOVE ) { + connectionClosed( event.getEndpoint() ); + } + } + + protected void flushEvents() + { + EndpointEvent event; + while( (event = kernel.nextEvent()) != null ) { + try { + createAndDispatch( event ); + } catch( Exception e ) { + reportError(event.getEndpoint(), event, e); + } + } + } + + public void run() + { + while( go.get() ) { + + try { + // Check for pending events + flushEvents(); + + // Grab the next envelope + Envelope e = kernel.read(); + if( e == Kernel.EVENTS_PENDING ) + continue; // We'll catch it up above + + // Check for pending events that might have + // come in while we were blocking. This is usually + // when the connection add events come through + flushEvents(); + + try { + createAndDispatch( e ); + } catch( Exception ex ) { + reportError(e.getSource(), e, ex); + } + + } catch( InterruptedException ex ) { + if( !go.get() ) + return; + throw new RuntimeException( "Unexpected interruption", ex ); + } + } + } + +} + + diff --git a/jme3-networking/src/main/java/com/jme3/network/base/KernelFactory.java b/jme3-networking/src/main/java/com/jme3/network/base/KernelFactory.java new file mode 100644 index 000000000..645019039 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/base/KernelFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.base; + +import com.jme3.network.kernel.Kernel; +import java.io.IOException; + + +/** + * Supplied to the DefaultServer to create any additional + * channel kernels that might be required. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface KernelFactory +{ + public static final KernelFactory DEFAULT = new NioKernelFactory(); + + public Kernel createKernel( int channel, int port ) throws IOException; +} diff --git a/jme3-networking/src/main/java/com/jme3/network/base/MessageListenerRegistry.java b/jme3-networking/src/main/java/com/jme3/network/base/MessageListenerRegistry.java new file mode 100644 index 000000000..f151e1c84 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/base/MessageListenerRegistry.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.base; + +import com.jme3.network.Message; +import com.jme3.network.MessageListener; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Keeps track of message listeners registered to specific + * types or to any type. + * + * @version $Revision$ + * @author Paul Speed + */ +public class MessageListenerRegistry implements MessageListener +{ + static Logger log = Logger.getLogger(MessageListenerRegistry.class.getName()); + + private List> listeners = new CopyOnWriteArrayList>(); + private Map>> typeListeners + = new ConcurrentHashMap>>(); + + public MessageListenerRegistry() + { + } + + public void messageReceived( S source, Message m ) + { + boolean delivered = false; + + for( MessageListener l : listeners ) { + l.messageReceived( source, m ); + delivered = true; + } + + for( MessageListener l : getListeners(m.getClass(),false) ) { + l.messageReceived( source, m ); + delivered = true; + } + + if( !delivered ) { + log.log( Level.FINE, "Received message had no registered listeners: {0}", m ); + } + } + + protected List> getListeners( Class c, boolean create ) + { + List> result = typeListeners.get(c); + if( result == null && create ) { + result = new CopyOnWriteArrayList>(); + typeListeners.put( c, result ); + } + + if( result == null ) { + result = Collections.emptyList(); + } + return result; + } + + public void addMessageListener( MessageListener listener ) + { + if( listener == null ) + throw new IllegalArgumentException( "Listener cannot be null." ); + listeners.add(listener); + } + + public void removeMessageListener( MessageListener listener ) + { + listeners.remove(listener); + } + + public void addMessageListener( MessageListener listener, Class... classes ) + { + if( listener == null ) + throw new IllegalArgumentException( "Listener cannot be null." ); + for( Class c : classes ) { + getListeners(c, true).add(listener); + } + } + + public void removeMessageListener( MessageListener listener, Class... classes ) + { + for( Class c : classes ) { + getListeners(c, false).remove(listener); + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java b/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java new file mode 100644 index 000000000..a8d0d39fd --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.base; + +import com.jme3.network.Message; +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.LinkedList; + +/** + * Consolidates the conversion of messages to/from byte buffers + * and provides a rolling message buffer. ByteBuffers can be + * pushed in and messages will be extracted, accumulated, and + * available for retrieval. This is not thread safe and is meant + * to be used within a single message processing thread. + * + *

      The protocol is based on a simple length + data format + * where two bytes represent the (short) length of the data + * and the rest is the raw data for the Serializers class.

      + * + * @version $Revision$ + * @author Paul Speed + */ +public class MessageProtocol +{ + private LinkedList messages = new LinkedList(); + private ByteBuffer current; + private int size; + private Byte carry; + + /** + * Converts a message to a ByteBuffer using the Serializer + * and the (short length) + data protocol. If target is null + * then a 32k byte buffer will be created and filled. + */ + public static ByteBuffer messageToBuffer( Message message, ByteBuffer target ) + { + // Could let the caller pass their own in + ByteBuffer buffer = target == null ? ByteBuffer.allocate( 32767 + 2 ) : target; + + try { + buffer.position( 2 ); + Serializer.writeClassAndObject( buffer, message ); + buffer.flip(); + short dataLength = (short)(buffer.remaining() - 2); + buffer.putShort( dataLength ); + buffer.position( 0 ); + + return buffer; + } catch( IOException e ) { + throw new RuntimeException( "Error serializing message", e ); + } + } + + /** + * Retrieves and removes an extracted message from the accumulated buffer + * or returns null if there are no more messages. + */ + public Message getMessage() + { + if( messages.isEmpty() ) { + return null; + } + + return messages.removeFirst(); + } + + /** + * Adds the specified buffer, extracting the contained messages + * and making them available to getMessage(). The left over + * data is buffered to be combined with future data. + & + * @return The total number of queued messages after this call. + */ + public int addBuffer( ByteBuffer buffer ) + { + // push the data from the buffer into as + // many messages as we can + while( buffer.remaining() > 0 ) { + + if( current == null ) { + + // If we have a left over carry then we need to + // do manual processing to get the short value + if( carry != null ) { + byte high = carry; + byte low = buffer.get(); + + size = (high & 0xff) << 8 | (low & 0xff); + carry = null; + } + else if( buffer.remaining() < 2 ) { + // It's possible that the supplied buffer only has one + // byte in it... and in that case we will get an underflow + // when attempting to read the short below. + + // It has to be 1 or we'd never get here... but one + // isn't enough so we stash it away. + carry = buffer.get(); + break; + } else { + // We are not currently reading an object so + // grab the size. + // Note: this is somewhat limiting... int would + // be better. + size = buffer.getShort(); + } + + // Allocate the buffer into which we'll feed the + // data as we get it + current = ByteBuffer.allocate(size); + } + + if( current.remaining() <= buffer.remaining() ) { + // We have at least one complete object so + // copy what we can into current, create a message, + // and then continue pulling from buffer. + + // Artificially set the limit so we don't overflow + int extra = buffer.remaining() - current.remaining(); + buffer.limit( buffer.position() + current.remaining() ); + + // Now copy the data + current.put( buffer ); + current.flip(); + + // Now set the limit back to a good value + buffer.limit( buffer.position() + extra ); + + createMessage( current ); + + current = null; + } else { + + // Not yet a complete object so just copy what we have + current.put( buffer ); + } + } + + return messages.size(); + } + + /** + * Creates a message from the properly sized byte buffer + * and adds it to the messages queue. + */ + protected void createMessage( ByteBuffer buffer ) + { + try { + Object obj = Serializer.readClassAndObject( buffer ); + Message m = (Message)obj; + messages.add(m); + } catch( IOException e ) { + throw new RuntimeException( "Error deserializing object", e ); + } + } +} + + + diff --git a/jme3-networking/src/main/java/com/jme3/network/base/NioKernelFactory.java b/jme3-networking/src/main/java/com/jme3/network/base/NioKernelFactory.java new file mode 100644 index 000000000..1f44fed0a --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/base/NioKernelFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.base; + +import com.jme3.network.kernel.Kernel; +import com.jme3.network.kernel.tcp.SelectorKernel; +import java.io.IOException; + + +/** + * KernelFactory implemention for creating TCP kernels + * using the NIO selector model. + * + * @version $Revision$ + * @author Paul Speed + */ +public class NioKernelFactory implements KernelFactory +{ + public Kernel createKernel( int channel, int port ) throws IOException + { + return new SelectorKernel(port); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/base/TcpConnectorFactory.java b/jme3-networking/src/main/java/com/jme3/network/base/TcpConnectorFactory.java new file mode 100644 index 000000000..1166dd32e --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/base/TcpConnectorFactory.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.base; + +import com.jme3.network.kernel.Connector; +import com.jme3.network.kernel.tcp.SocketConnector; +import java.io.IOException; +import java.net.InetAddress; + + +/** + * Creates TCP connectors to a specific remote address. + * + * @version $Revision$ + * @author Paul Speed + */ +public class TcpConnectorFactory implements ConnectorFactory +{ + private InetAddress remoteAddress; + + public TcpConnectorFactory( InetAddress remoteAddress ) + { + this.remoteAddress = remoteAddress; + } + + public Connector createConnector( int channel, int port ) throws IOException + { + return new SocketConnector( remoteAddress, port ); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/base/package.html b/jme3-networking/src/main/java/com/jme3/network/base/package.html new file mode 100644 index 000000000..a00cb16aa --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/base/package.html @@ -0,0 +1,12 @@ + + + + + + + +The base package contains the default implementations for the +{@link com.jme3.network.Client} and {@link com.jme3.network.Server} +interfaces from the public API. + + diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/AbstractKernel.java b/jme3-networking/src/main/java/com/jme3/network/kernel/AbstractKernel.java new file mode 100644 index 000000000..c8aa5ddc2 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/AbstractKernel.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Base implementation of the Kernel interface providing several + * useful default implementations of some methods. This implementation + * assumes that the kernel will be managing its own internal threads + * and queuing any results for the caller to retrieve on their own + * thread. + * + * @version $Revision$ + * @author Paul Speed + */ +public abstract class AbstractKernel implements Kernel +{ + static Logger log = Logger.getLogger(AbstractKernel.class.getName()); + + private AtomicLong nextId = new AtomicLong(1); + + /** + * Contains the pending endpoint events waiting for the caller + * to retrieve them. + */ + private ConcurrentLinkedQueue endpointEvents = new ConcurrentLinkedQueue(); + + /** + * Contains the pending envelopes waiting for the caller to + * retrieve them. + */ + private LinkedBlockingQueue envelopes = new LinkedBlockingQueue(); + + protected AbstractKernel() + { + } + + protected void reportError( Exception e ) + { + // Should really be queued up so the outer thread can + // retrieve them. For now we'll just log it. FIXME + log.log( Level.SEVERE, "Unhanddled kernel error", e ); + } + + protected long nextEndpointId() + { + return nextId.getAndIncrement(); + } + + /** + * Returns true if there are waiting envelopes. + */ + public boolean hasEnvelopes() + { + return !envelopes.isEmpty(); + } + + /** + * Removes one envelope from the received messages queue or + * blocks until one is available. + */ + public Envelope read() throws InterruptedException + { + return envelopes.take(); + } + + /** + * Removes and returnsn one endpoint event from the event queue or + * null if there are no endpoint events. + */ + public EndpointEvent nextEvent() + { + return endpointEvents.poll(); + } + + protected void addEvent( EndpointEvent e ) + { + endpointEvents.add( e ); + } + + protected void addEnvelope( Envelope env ) + { + if( !envelopes.offer( env ) ) { + throw new KernelException( "Critical error, could not enqueue envelope." ); + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/Connector.java b/jme3-networking/src/main/java/com/jme3/network/kernel/Connector.java new file mode 100644 index 000000000..cc6fc3f31 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/Connector.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel; + +import java.nio.ByteBuffer; + +/** + * A single channel remote connection allowing the sending + * and receiving of data. As opposed to the Kernel, this will + * only ever receive data from one Endpoint and so bypasses + * the envelope wrapping. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface Connector +{ + /** + * Returns true if this connector is currently connected. + */ + public boolean isConnected(); + + /** + * Closes the connection. Any subsequent attempts to read + * or write will fail with an exception. + */ + public void close(); + + /** + * Returns true if there is currently data available for + * reading. Some connector implementations may not be able + * to answer this question accurately and will always return + * false. + */ + public boolean available(); + + /** + * Reads a chunk of data from the connection, blocking if + * there is no data available. The buffer may only be valid + * until the next read() call is made. Callers should copy + * the data if they need it for longer than that. + * + * @return The data read or null if there is no more data + * because the connection is closed. + */ + public ByteBuffer read(); + + /** + * Writes a chunk of data to the connection from data.position() + * to data.limit(). + */ + public void write( ByteBuffer data ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/ConnectorException.java b/jme3-networking/src/main/java/com/jme3/network/kernel/ConnectorException.java new file mode 100644 index 000000000..7c55a4eea --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/ConnectorException.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel; + + +/** + * Represents a client-side connection error, usually encapsulating + * an IOException as its cause. + * + * @version $Revision$ + * @author Paul Speed + */ +public class ConnectorException extends RuntimeException +{ + public ConnectorException( String message, Throwable cause ) + { + super( message, cause ); + } + + public ConnectorException( String message ) + { + super( message ); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/Endpoint.java b/jme3-networking/src/main/java/com/jme3/network/kernel/Endpoint.java new file mode 100644 index 000000000..55449eb86 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/Endpoint.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel; + +import java.nio.ByteBuffer; + +/** + * An abstract endpoint in a Kernel that can be used for + * sending/receiving messages within the kernel space. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface Endpoint +{ + /** + * Returns an ID that is unique for this endpoint within its + * Kernel instance. + */ + public long getId(); + + /** + * Returns the transport specific remote address of this endpoint + * as a string. This may or may not be unique per endpoint depending + * on the type of transport. + */ + public String getAddress(); + + /** + * Returns the kernel to which this endpoint belongs. + */ + public Kernel getKernel(); + + /** + * Returns true if this endpoint is currently connected. + */ + public boolean isConnected(); + + /** + * Sends data to the other end of the connection represented + * by this endpoint. + */ + public void send( ByteBuffer data ); + + /** + * Closes this endpoint without flushing any of its + * currently enqueued outbound data. + */ + public void close(); + + /** + * Closes this endpoint, optionally flushing any queued + * data before closing. As soon as this method is called, + * ne send() calls will fail with an exception... even while + * close() is still flushing the earlier queued messages. + */ + public void close(boolean flushData); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/EndpointEvent.java b/jme3-networking/src/main/java/com/jme3/network/kernel/EndpointEvent.java new file mode 100644 index 000000000..b43f2aaf3 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/EndpointEvent.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel; + + +/** + * Provides information about an added or + * removed connection. + * + * @version $Revision$ + * @author Paul Speed + */ +public class EndpointEvent +{ + public enum Type { ADD, REMOVE }; + + private Kernel source; + private Endpoint endpoint; + private Type type; + + public EndpointEvent( Kernel source, Endpoint p, Type type ) + { + this.source = source; + this.endpoint = p; + this.type = type; + } + + public static EndpointEvent createAdd( Kernel source, Endpoint p ) + { + return new EndpointEvent( source, p, Type.ADD ); + } + + public static EndpointEvent createRemove( Kernel source, Endpoint p ) + { + return new EndpointEvent( source, p, Type.REMOVE ); + } + + public Kernel getSource() + { + return source; + } + + public Endpoint getEndpoint() + { + return endpoint; + } + + public Type getType() + { + return type; + } + + public String toString() + { + return "EndpointEvent[" + type + ", " + endpoint + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/Envelope.java b/jme3-networking/src/main/java/com/jme3/network/kernel/Envelope.java new file mode 100644 index 000000000..13c6849cd --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/Envelope.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel; + +/** + * Encapsulates a received piece of data. This is used by the Kernel + * to track incoming chunks of data. + * + * @version $Revision$ + * @author Paul Speed + */ +public class Envelope +{ + private Endpoint source; + private byte[] data; + private boolean reliable; + + /** + * Creates an incoming envelope holding the data from the specified + * source. The 'reliable' flag further indicates on which mode of + * transport the data arrrived. + */ + public Envelope( Endpoint source, byte[] data, boolean reliable ) + { + this.source = source; + this.data = data; + this.reliable = reliable; + } + + public Endpoint getSource() + { + return source; + } + + public byte[] getData() + { + return data; + } + + public boolean isReliable() + { + return reliable; + } + + public String toString() + { + return "Envelope[" + source + ", " + (reliable?"reliable":"unreliable") + ", " + data.length + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/Kernel.java b/jme3-networking/src/main/java/com/jme3/network/kernel/Kernel.java new file mode 100644 index 000000000..91edba198 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/Kernel.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel; + +import com.jme3.network.Filter; +import java.nio.ByteBuffer; + +/** + * Defines the basic byte[] passing messaging + * kernel. + * + * @version $Revision$ + * @author Paul Speed + */ +public interface Kernel +{ + /** + * A marker envelope returned from read() that indicates that + * there are events pending. This allows a single thread to + * more easily process the envelopes and endpoint events. + */ + public static final Envelope EVENTS_PENDING = new Envelope( null, new byte[0], false ); + + /** + * Initializes the kernel and starts any internal processing. + */ + public void initialize(); + + /** + * Gracefully terminates the kernel and stops any internal + * daemon processing. This method will not return until all + * internal threads have been shut down. + */ + public void terminate() throws InterruptedException; + + /** + * Dispatches the data to all endpoints managed by the + * kernel that match the specified endpoint filter.. + * If 'copy' is true then the implementation will copy the byte buffer + * before delivering it to endpoints. This allows the caller to reuse + * the data buffer. Though it is important that the buffer not be changed + * by another thread while this call is running. + * Only the bytes from data.position() to data.remaining() are sent. + */ + public void broadcast( Filter filter, ByteBuffer data, boolean reliable, + boolean copy ); + + /** + * Returns true if there are waiting envelopes. + */ + public boolean hasEnvelopes(); + + /** + * Removes one envelope from the received messages queue or + * blocks until one is available. + */ + public Envelope read() throws InterruptedException; + + /** + * Removes and returnsn one endpoint event from the event queue or + * null if there are no endpoint events. + */ + public EndpointEvent nextEvent(); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/KernelException.java b/jme3-networking/src/main/java/com/jme3/network/kernel/KernelException.java new file mode 100644 index 000000000..d000d2a72 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/KernelException.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel; + + +/** + * Represents a kernel-level error, usually encapsulating + * an IOException as its cause. + * + * @version $Revision$ + * @author Paul Speed + */ +public class KernelException extends RuntimeException +{ + public KernelException( String message, Throwable cause ) + { + super( message, cause ); + } + + public KernelException( String message ) + { + super( message ); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/NamedThreadFactory.java b/jme3-networking/src/main/java/com/jme3/network/kernel/NamedThreadFactory.java new file mode 100644 index 000000000..59dedd883 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/NamedThreadFactory.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel; + +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +/** + * A simple factory that delegates to java.util.concurrent's + * default thread factory but adds a prefix to the beginning + * of the thread name. + * + * @version $Revision$ + * @author Paul Speed + */ +public class NamedThreadFactory implements ThreadFactory +{ + private String name; + private boolean daemon; + private ThreadFactory delegate; + + public NamedThreadFactory( String name ) + { + this( name, Executors.defaultThreadFactory() ); + } + + public NamedThreadFactory( String name, boolean daemon ) + { + this( name, daemon, Executors.defaultThreadFactory() ); + } + + public NamedThreadFactory( String name, ThreadFactory delegate ) + { + this( name, false, delegate ); + } + + public NamedThreadFactory( String name, boolean daemon, ThreadFactory delegate ) + { + this.name = name; + this.daemon = daemon; + this.delegate = delegate; + } + + public Thread newThread( Runnable r ) + { + Thread result = delegate.newThread(r); + String s = result.getName(); + result.setName( name + "[" + s + "]" ); + result.setDaemon(daemon); + return result; + } +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/package.html b/jme3-networking/src/main/java/com/jme3/network/kernel/package.html new file mode 100644 index 000000000..7029c958f --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/package.html @@ -0,0 +1,34 @@ + + + + + + + + + +The kernel package is the heart of the JME networking module +and controls the routing and dispatch of message data over +different transport implementations. Most users will never +have to deal with these classes unless they are writing their own +client and server implementations that diverge from the standard +classes that are provided. + +

      {@link com.jme3.network.kernel.Kernel} defines the core of a server-side message +broker that abstracts away the specific transport and underlying +threading model used. For example, it might use NIO selectors +in a single threaded model or straight multithreaded socket +model. Or it might implement SSL connections. Once created, +{@link com.jme3.network.kernel.Kernel} users don't need to care about the details.

      + +

      {@link com.jme3.network.kernel.Endpoint} is a managed connection within a +{@link com.jme3.network.kernel.Kernel} providing kernel to client connectivity.

      + +

      {@link com.jme3.network.kernel.Connector} defines the basic client-side message sender +and these objects are typically used to connect to a {@link com.jme3.network.kernel.Kernel} +though they can connect to any network port that supports the implementation's +protocol. Implementations are provided for straight TCP and UDP communication +and could be extended to support SSL or different threading models.

      + + + diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/NioEndpoint.java b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/NioEndpoint.java new file mode 100644 index 000000000..0a022758f --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/NioEndpoint.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel.tcp; + +import com.jme3.network.kernel.Endpoint; +import com.jme3.network.kernel.Kernel; +import com.jme3.network.kernel.KernelException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.util.concurrent.ConcurrentLinkedQueue; + + +/** + * Endpoint implementation that encapsulates the + * channel IO based connection information and keeps + * track of the outbound data queue for the channel. + * + * @version $Revision$ + * @author Paul Speed + */ +public class NioEndpoint implements Endpoint +{ + protected static final ByteBuffer CLOSE_MARKER = ByteBuffer.allocate(0); + + private long id; + private SocketChannel socket; + private SelectorKernel kernel; + private ConcurrentLinkedQueue outbound = new ConcurrentLinkedQueue(); + private boolean closing = false; + + public NioEndpoint( SelectorKernel kernel, long id, SocketChannel socket ) + { + this.id = id; + this.socket = socket; + this.kernel = kernel; + } + + public Kernel getKernel() + { + return kernel; + } + + public void close() + { + close(false); + } + + public void close( boolean flushData ) + { + if( flushData ) { + closing = true; + + // Enqueue a close marker message to let the server + // know we should close + send( CLOSE_MARKER, false, true ); + + return; + } + + try { + // Note: even though we may be disconnected from the socket.isConnected() + // standpoint, it's still safest to tell the kernel so that it can be sure + // to stop managing us gracefully. + kernel.closeEndpoint(this); + } catch( IOException e ) { + throw new KernelException( "Error closing endpoint for socket:" + socket, e ); + } + } + + public long getId() + { + return id; + } + + public String getAddress() + { + return String.valueOf(socket.socket().getRemoteSocketAddress()); + } + + public boolean isConnected() + { + return socket.isConnected(); + } + + /** + * The wakeup option is used internally when the kernel is + * broadcasting out to a bunch of endpoints and doesn't want to + * necessarily wakeup right away. + */ + protected void send( ByteBuffer data, boolean copy, boolean wakeup ) + { + // We create a ByteBuffer per endpoint since we + // use it to track the data sent to each endpoint + // separately. + ByteBuffer buffer; + if( !copy ) { + buffer = data; + } else { + // Copy the buffer + buffer = ByteBuffer.allocate(data.remaining()); + buffer.put(data); + buffer.flip(); + } + + // Queue it up + outbound.add(buffer); + + if( wakeup ) + kernel.wakeupSelector(); + } + + /** + * Called by the SelectorKernel to get the current top + * buffer for writing. + */ + protected ByteBuffer peekPending() + { + return outbound.peek(); + } + + /** + * Called by the SelectorKernel when the top buffer + * has been exhausted. + */ + protected ByteBuffer removePending() + { + return outbound.poll(); + } + + protected boolean hasPending() + { + return !outbound.isEmpty(); + } + + public void send( ByteBuffer data ) + { + if( data == null ) { + throw new IllegalArgumentException( "Data cannot be null." ); + } + if( closing ) { + throw new KernelException( "Endpoint has been closed:" + socket ); + } + send( data, true, true ); + } + + public String toString() + { + return "NioEndpoint[" + id + ", " + socket + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SelectorKernel.java b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SelectorKernel.java new file mode 100644 index 000000000..42aa3ddbc --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SelectorKernel.java @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel.tcp; + +import com.jme3.network.Filter; +import com.jme3.network.kernel.*; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.nio.channels.spi.SelectorProvider; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * A Kernel implementation based on NIO selectors. + * + * @version $Revision$ + * @author Paul Speed + */ +public class SelectorKernel extends AbstractKernel +{ + static Logger log = Logger.getLogger(SelectorKernel.class.getName()); + + private InetSocketAddress address; + private SelectorThread thread; + + private Map endpoints = new ConcurrentHashMap(); + + public SelectorKernel( InetAddress host, int port ) + { + this( new InetSocketAddress(host, port) ); + } + + public SelectorKernel( int port ) throws IOException + { + this( new InetSocketAddress(port) ); + } + + public SelectorKernel( InetSocketAddress address ) + { + this.address = address; + } + + protected SelectorThread createSelectorThread() + { + return new SelectorThread(); + } + + public void initialize() + { + if( thread != null ) + throw new IllegalStateException( "Kernel already initialized." ); + + thread = createSelectorThread(); + + try { + thread.connect(); + thread.start(); + } catch( IOException e ) { + throw new KernelException( "Error hosting:" + address, e ); + } + } + + public void terminate() throws InterruptedException + { + if( thread == null ) + throw new IllegalStateException( "Kernel not initialized." ); + + try { + thread.close(); + thread = null; + } catch( IOException e ) { + throw new KernelException( "Error closing host connection:" + address, e ); + } + } + + public void broadcast( Filter filter, ByteBuffer data, boolean reliable, + boolean copy ) + { + if( !reliable ) + throw new UnsupportedOperationException( "Unreliable send not supported by this kernel." ); + + if( copy ) { + // Copy the data just once + byte[] temp = new byte[data.remaining()]; + System.arraycopy(data.array(), data.position(), temp, 0, data.remaining()); + data = ByteBuffer.wrap(temp); + } + + // Hand it to all of the endpoints that match our routing + for( NioEndpoint p : endpoints.values() ) { + // Does it match the filter? + if( filter != null && !filter.apply(p) ) + continue; + + // Give it the data... but let each endpoint track their + // own completion over the shared array of bytes by + // duplicating it + p.send( data.duplicate(), false, false ); + } + + // Wake up the selector so it can reinitialize its + // state accordingly. + wakeupSelector(); + } + + protected NioEndpoint addEndpoint( SocketChannel c ) + { + // Note: we purposely do NOT put the key in the endpoint. + // SelectionKeys are dangerous outside the selector thread + // and this is safer. + NioEndpoint p = new NioEndpoint( this, nextEndpointId(), c ); + + endpoints.put( p.getId(), p ); + + // Enqueue an endpoint event for the listeners + addEvent( EndpointEvent.createAdd( this, p ) ); + + return p; + } + + protected void removeEndpoint( NioEndpoint p, SocketChannel c ) + { + endpoints.remove( p.getId() ); + log.log( Level.FINE, "Endpoints size:{0}", endpoints.size() ); + + // Enqueue an endpoint event for the listeners + addEvent( EndpointEvent.createRemove( this, p ) ); + + // If there are no pending messages then add one so that the + // kernel-user knows to wake up if it is only listening for + // envelopes. + if( !hasEnvelopes() ) { + // Note: this is not really a race condition. At worst, our + // event has already been handled by now and it does no harm + // to check again. + addEnvelope( EVENTS_PENDING ); + } + } + + /** + * Called by the endpoints when they need to be closed. + */ + protected void closeEndpoint( NioEndpoint p ) throws IOException + { + //log.log( Level.FINE, "Closing endpoint:{0}.", p ); + + thread.cancel(p); + } + + /** + * Used internally by the endpoints to wakeup the selector + * when they have data to send. + */ + protected void wakeupSelector() + { + thread.wakeupSelector(); + } + + protected void newData( NioEndpoint p, SocketChannel c, ByteBuffer shared, int size ) + { + // Note: if ever desirable, it would be possible to accumulate + // data per source channel and only 'finalize' it when + // asked for more envelopes then were ready. I just don't + // think it will be an issue in practice. The busier the + // server, the more the buffers will fill before we get to them. + // And if the server isn't busy, who cares if we chop things up + // smaller... the network is still likely to deliver things in + // bulk anyway. + + // Must copy the shared data before we use it + byte[] dataCopy = new byte[size]; + System.arraycopy(shared.array(), 0, dataCopy, 0, size); + + Envelope env = new Envelope( p, dataCopy, true ); + addEnvelope( env ); + } + + /** + * This class is purposely tucked neatly away because + * messing with the selector from other threads for any + * reason is very bad. This is the safest architecture. + */ + protected class SelectorThread extends Thread + { + private ServerSocketChannel serverChannel; + private Selector selector; + private AtomicBoolean go = new AtomicBoolean(true); + private ByteBuffer working = ByteBuffer.allocate( 8192 ); + + /** + * Because we want to keep the keys to ourselves, we'll do + * the endpoint -> key mapping internally. + */ + private Map endpointKeys = new ConcurrentHashMap(); + + public SelectorThread() + { + setName( "Selector@" + address ); + setDaemon(true); + } + + public void connect() throws IOException + { + // Create a new selector + this.selector = SelectorProvider.provider().openSelector(); + + // Create a new non-blocking server socket channel + this.serverChannel = ServerSocketChannel.open(); + serverChannel.configureBlocking(false); + + // Bind the server socket to the specified address and port + serverChannel.socket().bind(address); + + // Register the server socket channel, indicating an interest in + // accepting new connections + serverChannel.register(selector, SelectionKey.OP_ACCEPT); + + log.log( Level.FINE, "Hosting TCP connection:{0}.", address ); + } + + public void close() throws IOException, InterruptedException + { + // Set the thread to stop + go.set(false); + + // Make sure the channel is closed + serverChannel.close(); + + // Force the selector to stop blocking + wakeupSelector(); + + // And wait for it + join(); + } + + protected void wakeupSelector() + { + selector.wakeup(); + } + + protected void setupSelectorOptions() + { + // For now, selection keys will either be in OP_READ + // or OP_WRITE. So while we are writing a buffer, we + // will not be reading. This is way simpler and less + // error prone... it can always be changed when everything + // else works if we are looking to micro-optimize. + + // Setup options based on the current state of + // the endpoints. This could potentially be more + // efficiently done as change requests... or simply + // keeping a thread-safe set of endpoints with pending + // writes. For most cases, it shouldn't matter. + for( Map.Entry e : endpointKeys.entrySet() ) { + if( e.getKey().hasPending() ) { + e.getValue().interestOps(SelectionKey.OP_WRITE); + } + } + } + + protected void accept( SelectionKey key ) throws IOException + { + // Would only get accepts on a server channel + ServerSocketChannel serverChan = (ServerSocketChannel)key.channel(); + + // Setup the connection to be non-blocking + SocketChannel remoteChan = serverChan.accept(); + remoteChan.configureBlocking(false); + + // And disable Nagle's buffering algorithm... we want + // data to go when we put it there. + Socket sock = remoteChan.socket(); + sock.setTcpNoDelay(true); + + // Let the selector know we're interested in reading + // data from the channel + SelectionKey endKey = remoteChan.register( selector, SelectionKey.OP_READ ); + + // And now create a new endpoint + NioEndpoint p = addEndpoint( remoteChan ); + endKey.attach(p); + endpointKeys.put(p, endKey); + } + + protected void cancel( NioEndpoint p ) throws IOException + { + SelectionKey key = endpointKeys.remove(p); + if( key == null ) { + //log.log( Level.FINE, "Endpoint already closed:{0}.", p ); + return; // already closed it + } + log.log( Level.FINE, "Endpoint keys size:{0}", endpointKeys.size() ); + + log.log( Level.FINE, "Closing endpoint:{0}.", p ); + SocketChannel c = (SocketChannel)key.channel(); + + // Note: key.cancel() is specifically thread safe. One of + // the few things one can do with a key from another + // thread. + key.cancel(); + c.close(); + removeEndpoint( p, c ); + } + + protected void cancel( SelectionKey key, SocketChannel c ) throws IOException + { + NioEndpoint p = (NioEndpoint)key.attachment(); + log.log( Level.FINE, "Closing channel endpoint:{0}.", p ); + Object o = endpointKeys.remove(p); + + log.log( Level.FINE, "Endpoint keys size:{0}", endpointKeys.size() ); + + key.cancel(); + c.close(); + removeEndpoint( p, c ); + } + + protected void read( SelectionKey key ) throws IOException + { + NioEndpoint p = (NioEndpoint)key.attachment(); + SocketChannel c = (SocketChannel)key.channel(); + working.clear(); + + int size; + try { + size = c.read(working); + } catch( IOException e ) { + // The remove end forcibly closed the connection... + // close out our end and cancel the key + cancel( key, c ); + return; + } + + if( size == -1 ) { + // The remote end shut down cleanly... + // close out our end and cancel the key + cancel( key, c ); + return; + } + + newData( p, c, working, size ); + } + + protected void write( SelectionKey key ) throws IOException + { + NioEndpoint p = (NioEndpoint)key.attachment(); + SocketChannel c = (SocketChannel)key.channel(); + + // We will send what we can and move on. + ByteBuffer current = p.peekPending(); + if( current == NioEndpoint.CLOSE_MARKER ) { + // This connection wants to be closed now + closeEndpoint(p); + + // Nothing more to do + return; + } + + c.write( current ); + + // If we wrote all of that packet then we need to remove it + if( current.remaining() == 0 ) { + p.removePending(); + } + + // If we happened to empty the pending queue then let's read + // again. + if( !p.hasPending() ) { + key.interestOps( SelectionKey.OP_READ ); + } + } + + protected void select() throws IOException + { + selector.select(); + + for( Iterator i = selector.selectedKeys().iterator(); i.hasNext(); ) { + SelectionKey key = i.next(); + i.remove(); + + if( !key.isValid() ) + { + // When does this happen? + log.log( Level.FINE, "Key is not valid:{0}.", key ); + continue; + } + + try { + if( key.isAcceptable() ) + accept(key); + else if( key.isWritable() ) + write(key); + else if( key.isReadable() ) + read(key); + } catch( IOException e ) { + if( !go.get() ) + return; // error likely due to shutting down + reportError( e ); + + // And at this level, errors likely mean the key is now + // dead and it doesn't hurt to kick them anyway. If we + // find IOExceptions that are not fatal, this can be + // readdressed + cancel( key, (SocketChannel)key.channel() ); + } + } + } + + public void run() + { + log.log( Level.FINE, "Kernel started for connection:{0}.", address ); + + // An atomic is safest and costs almost nothing + while( go.get() ) { + // Setup any queued option changes + setupSelectorOptions(); + + // Check for available keys and process them + try { + select(); + } catch( ClosedSelectorException e ) { + if( !go.get() ) + return; // it's because we're shutting down + throw new KernelException( "Premature selector closing", e ); + } catch( CancelledKeyException e ) { + if( !go.get() ) + return; // it's because we're shutting down + throw new KernelException( "Invalid key state", e ); + } catch( IOException e ) { + if( !go.get() ) + return; // error likely due to shutting down + reportError( e ); + } + } + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SocketConnector.java b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SocketConnector.java new file mode 100644 index 000000000..1cb237303 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SocketConnector.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel.tcp; + +import com.jme3.network.kernel.Connector; +import com.jme3.network.kernel.ConnectorException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; + + +/** + * A straight forward socket-based connector implementation that + * does not use any separate threading. It relies completely on + * the buffering in the OS network layer. + * + * @version $Revision$ + * @author Paul Speed + */ +public class SocketConnector implements Connector +{ + private Socket sock; + private InputStream in; + private OutputStream out; + private SocketAddress remoteAddress; + private byte[] buffer = new byte[65535]; + private AtomicBoolean connected = new AtomicBoolean(false); + + public SocketConnector( InetAddress address, int port ) throws IOException + { + this.sock = new Socket(address, port); + remoteAddress = sock.getRemoteSocketAddress(); // for info purposes + + // Disable Nagle's buffering so data goes out when we + // put it there. + sock.setTcpNoDelay(true); + + in = sock.getInputStream(); + out = sock.getOutputStream(); + + connected.set(true); + } + + protected void checkClosed() + { + if( sock == null ) + throw new ConnectorException( "Connection is closed:" + remoteAddress ); + } + + public boolean isConnected() + { + if( sock == null ) + return false; + return sock.isConnected(); + } + + public void close() + { + checkClosed(); + try { + Socket temp = sock; + sock = null; + connected.set(false); + temp.close(); + } catch( IOException e ) { + throw new ConnectorException( "Error closing socket for:" + remoteAddress, e ); + } + } + + public boolean available() + { + checkClosed(); + try { + return in.available() > 0; + } catch( IOException e ) { + throw new ConnectorException( "Error retrieving data availability for:" + remoteAddress, e ); + } + } + + public ByteBuffer read() + { + checkClosed(); + + try { + // Read what we can + int count = in.read(buffer); + if( count < 0 ) { + // Socket is closed + close(); + return null; + } + + // Wrap it in a ByteBuffer for the caller + return ByteBuffer.wrap( buffer, 0, count ); + } catch( IOException e ) { + if( !connected.get() ) { + // Nothing to see here... just move along + return null; + } + throw new ConnectorException( "Error reading from connection to:" + remoteAddress, e ); + } + } + + public void write( ByteBuffer data ) + { + checkClosed(); + + try { + out.write(data.array(), data.position(), data.remaining()); + } catch( IOException e ) { + throw new ConnectorException( "Error writing to connection:" + remoteAddress, e ); + } + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpConnector.java b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpConnector.java new file mode 100644 index 000000000..20682ecee --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpConnector.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel.udp; + +import com.jme3.network.kernel.Connector; +import com.jme3.network.kernel.ConnectorException; +import java.io.IOException; +import java.net.*; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; + + +/** + * A straight forward datagram socket-based UDP connector + * implementation. + * + * @version $Revision$ + * @author Paul Speed + */ +public class UdpConnector implements Connector +{ + private DatagramSocket sock = new DatagramSocket(); + private SocketAddress remoteAddress; + private byte[] buffer = new byte[65535]; + private AtomicBoolean connected = new AtomicBoolean(false); + + /** + * In order to provide proper available() checking, we + * potentially queue one datagram. + */ + private DatagramPacket pending; + + /** + * Creates a new UDP connection that send datagrams to the + * specified address and port. + */ + public UdpConnector( InetAddress remote, int remotePort ) throws IOException + { + InetSocketAddress localSocketAddress = new InetSocketAddress(0); + this.sock = new DatagramSocket( localSocketAddress ); + remoteAddress = new InetSocketAddress( remote, remotePort ); + + // Setup to receive only from the remote address + sock.connect( remoteAddress ); + + connected.set(true); + } + + protected void checkClosed() + { + if( sock == null ) + throw new ConnectorException( "Connection is closed:" + remoteAddress ); + } + + public boolean isConnected() + { + if( sock == null ) + return false; + return sock.isConnected(); + } + + public void close() + { + checkClosed(); + DatagramSocket temp = sock; + sock = null; + connected.set(false); + temp.close(); + } + + /** + * This always returns false since the simple DatagramSocket usage + * cannot be run in a non-blocking way. + */ + public boolean available() + { + // It would take a separate thread or an NIO Selector based implementation to get this + // to work. If a polling strategy is never employed by callers then it doesn't + // seem worth it to implement all of that just for this method. + checkClosed(); + return false; + } + + public ByteBuffer read() + { + checkClosed(); + + try { + DatagramPacket packet = new DatagramPacket( buffer, buffer.length ); + sock.receive(packet); + + // Wrap it in a ByteBuffer for the caller + return ByteBuffer.wrap( buffer, 0, packet.getLength() ); + } catch( IOException e ) { + if( !connected.get() ) { + // Nothing to see here... just move along + return null; + } + throw new ConnectorException( "Error reading from connection to:" + remoteAddress, e ); + } + } + + public void write( ByteBuffer data ) + { + checkClosed(); + + try { + DatagramPacket p = new DatagramPacket( data.array(), data.position(), data.remaining(), + remoteAddress ); + sock.send(p); + } catch( IOException e ) { + throw new ConnectorException( "Error writing to connection:" + remoteAddress, e ); + } + } +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpEndpoint.java b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpEndpoint.java new file mode 100644 index 000000000..f1a97a29f --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpEndpoint.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel.udp; + +import com.jme3.network.kernel.Endpoint; +import com.jme3.network.kernel.Kernel; +import com.jme3.network.kernel.KernelException; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; + + +/** + * Endpoint implementation that encapsulates the + * UDP connection information for return messaging, + * identification of envelope sources, etc. + * + * @version $Revision$ + * @author Paul Speed + */ +public class UdpEndpoint implements Endpoint +{ + private long id; + private SocketAddress address; + private DatagramSocket socket; + private UdpKernel kernel; + private boolean connected = true; // it's connectionless but we track logical state + + public UdpEndpoint( UdpKernel kernel, long id, SocketAddress address, DatagramSocket socket ) + { + this.id = id; + this.address = address; + this.socket = socket; + this.kernel = kernel; + } + + public Kernel getKernel() + { + return kernel; + } + + protected SocketAddress getRemoteAddress() + { + return address; + } + + public void close() + { + close( false ); + } + + public void close( boolean flush ) + { + // No real reason to flush UDP traffic yet... especially + // when considering that the outbound UDP isn't even + // queued. + + try { + kernel.closeEndpoint(this); + connected = false; + } catch( IOException e ) { + throw new KernelException( "Error closing endpoint for socket:" + socket, e ); + } + } + + public long getId() + { + return id; + } + + public String getAddress() + { + return String.valueOf(address); + } + + public boolean isConnected() + { + // The socket is always unconnected anyway so we track our + // own logical state for the kernel's benefit. + return connected; + } + + public void send( ByteBuffer data ) + { + if( !isConnected() ) { + throw new KernelException( "Endpoint is not connected:" + this ); + } + + + try { + DatagramPacket p = new DatagramPacket( data.array(), data.position(), + data.remaining(), address ); + + // Just queue it up for the kernel threads to write + // out + kernel.enqueueWrite( this, p ); + + //socket.send(p); + } catch( IOException e ) { + throw new KernelException( "Error sending datagram to:" + address, e ); + } + } + + public String toString() + { + return "UdpEndpoint[" + id + ", " + address + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpKernel.java b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpKernel.java new file mode 100644 index 000000000..198b915dc --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpKernel.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.kernel.udp; + +import com.jme3.network.Filter; +import com.jme3.network.kernel.*; +import java.io.IOException; +import java.net.*; +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A Kernel implementation using UDP packets. + * + * @version $Revision$ + * @author Paul Speed + */ +public class UdpKernel extends AbstractKernel +{ + static Logger log = Logger.getLogger(UdpKernel.class.getName()); + + private InetSocketAddress address; + private HostThread thread; + + private ExecutorService writer; + + // The nature of UDP means that even through a firewall, + // a user would have to have a unique address+port since UDP + // can't really be NAT'ed. + private Map socketEndpoints = new ConcurrentHashMap(); + + public UdpKernel( InetAddress host, int port ) + { + this( new InetSocketAddress(host, port) ); + } + + public UdpKernel( int port ) throws IOException + { + this( new InetSocketAddress(port) ); + } + + public UdpKernel( InetSocketAddress address ) + { + this.address = address; + } + + protected HostThread createHostThread() + { + return new HostThread(); + } + + public void initialize() + { + if( thread != null ) + throw new IllegalStateException( "Kernel already initialized." ); + + writer = Executors.newFixedThreadPool(2, new NamedThreadFactory(toString() + "-writer")); + + thread = createHostThread(); + + try { + thread.connect(); + thread.start(); + } catch( IOException e ) { + throw new KernelException( "Error hosting:" + address, e ); + } + } + + public void terminate() throws InterruptedException + { + if( thread == null ) + throw new IllegalStateException( "Kernel not initialized." ); + + try { + thread.close(); + writer.shutdown(); + thread = null; + } catch( IOException e ) { + throw new KernelException( "Error closing host connection:" + address, e ); + } + } + + /** + * Dispatches the data to all endpoints managed by the + * kernel. 'routing' is currently ignored. + */ + public void broadcast( Filter filter, ByteBuffer data, boolean reliable, + boolean copy ) + { + if( reliable ) + throw new UnsupportedOperationException( "Reliable send not supported by this kernel." ); + + if( copy ) { + // Copy the data just once + byte[] temp = new byte[data.remaining()]; + System.arraycopy(data.array(), data.position(), temp, 0, data.remaining()); + data = ByteBuffer.wrap(temp); + } + + // Hand it to all of the endpoints that match our routing + for( UdpEndpoint p : socketEndpoints.values() ) { + // Does it match the filter? + if( filter != null && !filter.apply(p) ) + continue; + + // Send the data + p.send( data ); + } + } + + protected Endpoint getEndpoint( SocketAddress address, boolean create ) + { + UdpEndpoint p = socketEndpoints.get(address); + if( p == null && create ) { + p = new UdpEndpoint( this, nextEndpointId(), address, thread.getSocket() ); + socketEndpoints.put( address, p ); + + // Add an event for it. + addEvent( EndpointEvent.createAdd( this, p ) ); + } + return p; + } + + /** + * Called by the endpoints when they need to be closed. + */ + protected void closeEndpoint( UdpEndpoint p ) throws IOException + { + // Just book-keeping to do here. + if( socketEndpoints.remove( p.getRemoteAddress() ) == null ) + return; + + log.log( Level.FINE, "Closing endpoint:{0}.", p ); + log.log( Level.FINE, "Socket endpoints size:{0}", socketEndpoints.size() ); + + addEvent( EndpointEvent.createRemove( this, p ) ); + + // If there are no pending messages then add one so that the + // kernel-user knows to wake up if it is only listening for + // envelopes. + if( !hasEnvelopes() ) { + // Note: this is not really a race condition. At worst, our + // event has already been handled by now and it does no harm + // to check again. + addEnvelope( EVENTS_PENDING ); + } + } + + protected void newData( DatagramPacket packet ) + { + // So the tricky part here is figuring out the endpoint and + // whether it's new or not. In these UDP schemes, firewalls have + // to be ported back to a specific machine so we will consider + // the address + port (ie: SocketAddress) the defacto unique + // ID. + Endpoint p = getEndpoint( packet.getSocketAddress(), true ); + + // We'll copy the data to trim it. + byte[] data = new byte[packet.getLength()]; + System.arraycopy(packet.getData(), 0, data, 0, data.length); + + Envelope env = new Envelope( p, data, false ); + addEnvelope( env ); + } + + protected void enqueueWrite( Endpoint endpoint, DatagramPacket packet ) + { + writer.execute( new MessageWriter(endpoint, packet) ); + } + + protected class MessageWriter implements Runnable + { + private Endpoint endpoint; + private DatagramPacket packet; + + public MessageWriter( Endpoint endpoint, DatagramPacket packet ) + { + this.endpoint = endpoint; + this.packet = packet; + } + + public void run() + { + // Not guaranteed to always work but an extra datagram + // to a dead connection isn't so big of a deal. + if( !endpoint.isConnected() ) { + return; + } + + try { + thread.getSocket().send(packet); + } catch( Exception e ) { + KernelException exc = new KernelException( "Error sending datagram to:" + address, e ); + exc.fillInStackTrace(); + reportError(exc); + } + } + } + + protected class HostThread extends Thread + { + private DatagramSocket socket; + private AtomicBoolean go = new AtomicBoolean(true); + + private byte[] buffer = new byte[65535]; // slightly bigger than needed. + + public HostThread() + { + setName( "UDP Host@" + address ); + setDaemon(true); + } + + protected DatagramSocket getSocket() + { + return socket; + } + + public void connect() throws IOException + { + socket = new DatagramSocket( address ); + log.log( Level.FINE, "Hosting UDP connection:{0}.", address ); + } + + public void close() throws IOException, InterruptedException + { + // Set the thread to stop + go.set(false); + + // Make sure the channel is closed + socket.close(); + + // And wait for it + join(); + } + + public void run() + { + log.log( Level.FINE, "Kernel started for connection:{0}.", address ); + + // An atomic is safest and costs almost nothing + while( go.get() ) { + try { + // Could reuse the packet but I don't see the + // point and it may lead to subtle bugs if not properly + // reset. + DatagramPacket packet = new DatagramPacket( buffer, buffer.length ); + socket.receive(packet); + + newData( packet ); + } catch( IOException e ) { + if( !go.get() ) + return; + reportError( e ); + } + } + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/message/ChannelInfoMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/ChannelInfoMessage.java new file mode 100644 index 000000000..cbf435565 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/message/ChannelInfoMessage.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.message; + +import com.jme3.network.AbstractMessage; +import com.jme3.network.serializing.Serializable; +import java.util.Arrays; +import java.util.List; + +/** + * Contains information about any extra server channels (if they exist). + * + * @author Paul Speed + */ +@Serializable() +public class ChannelInfoMessage extends AbstractMessage { + private long id; + private int[] ports; + + public ChannelInfoMessage() { + super( true ); + } + + public ChannelInfoMessage( long id, List ports ) { + super( true ); + this.id = id; + this.ports = new int[ports.size()]; + for( int i = 0; i < ports.size(); i++ ) { + this.ports[i] = ports.get(i); + } + } + + public long getId() { + return id; + } + + public int[] getPorts() { + return ports; + } + + public String toString() { + return "ChannelInfoMessage[" + id + ", " + Arrays.asList(ports) + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/message/ClientRegistrationMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/ClientRegistrationMessage.java new file mode 100644 index 000000000..d8c19a20c --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/message/ClientRegistrationMessage.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.message; + +import com.jme3.network.AbstractMessage; +import com.jme3.network.serializing.*; +import com.jme3.network.serializing.serializers.StringSerializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * 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, Paul Speed + */ +@Serializable() +public class ClientRegistrationMessage extends AbstractMessage { + + public static final short SERIALIZER_ID = -44; + + private long id; + private String gameName; + private int version; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public void setGameName( String name ) { + this.gameName = name; + } + + public String getGameName() { + return gameName; + } + + public void setVersion( int version ) { + this.version = version; + } + + public int getVersion() { + return version; + } + + public String toString() { + return getClass().getName() + "[id=" + id + ", gameName=" + gameName + ", version=" + version + "]"; + } + + /** + * A message-specific serializer to avoid compatibility issues + * between versions. This serializer is registered to the specific + * SERIALIZER_ID which is compatible with previous versions of the + * SM serializer registrations... and now will be forever. + */ + public static class ClientRegistrationSerializer extends Serializer { + + public ClientRegistrationMessage readObject( ByteBuffer data, Class c ) throws IOException { + + // Read the null/non-null marker + if (data.get() == 0x0) + return null; + + ClientRegistrationMessage msg = new ClientRegistrationMessage(); + + msg.gameName = StringSerializer.readString(data); + msg.id = data.getLong(); + msg.version = data.getInt(); + + return msg; + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + + // Add the null/non-null marker + buffer.put( (byte)(object != null ? 0x1 : 0x0) ); + if (object == null) { + // Nothing left to do + return; + } + + ClientRegistrationMessage msg = (ClientRegistrationMessage)object; + StringSerializer.writeString( msg.gameName, buffer ); + + buffer.putLong(msg.id); + buffer.putInt(msg.version); + } + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/message/CompressedMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/CompressedMessage.java new file mode 100644 index 000000000..40f20bf6e --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/message/CompressedMessage.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.message; + +import com.jme3.network.AbstractMessage; +import com.jme3.network.Message; +import com.jme3.network.serializing.Serializable; + +/** + * CompressedMessage is a base class for all messages that + * compress others. + * + * @author Lars Wesselius + */ +@Serializable() +public class CompressedMessage extends AbstractMessage { + private Message message; + + public CompressedMessage() { } + + public CompressedMessage(Message msg) { + this.message = msg; + } + + public void setMessage(Message message) { + this.message = message; + } + + public Message getMessage() { + return message; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/message/DisconnectMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/DisconnectMessage.java new file mode 100644 index 000000000..d514232f4 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/message/DisconnectMessage.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.message; + +import com.jme3.network.AbstractMessage; +import com.jme3.network.serializing.*; +import com.jme3.network.serializing.serializers.StringSerializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Represents a disconnect message. + * + * @author Lars Wesselius, Paul Speed + */ +@Serializable() +public class DisconnectMessage extends AbstractMessage { + + public static final short SERIALIZER_ID = -42; + + 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; + } + + public String toString() { + return getClass().getName() + "[reason=" + reason + ", type=" + type + "]"; + } + + /** + * A message-specific serializer to avoid compatibility issues + * between versions. This serializer is registered to the specific + * SERIALIZER_ID which is compatible with previous versions of the + * SM serializer registrations... and now will be forever. + */ + public static class DisconnectSerializer extends Serializer { + + public DisconnectMessage readObject( ByteBuffer data, Class c ) throws IOException { + + // Read the null/non-null marker + if (data.get() == 0x0) + return null; + + DisconnectMessage msg = new DisconnectMessage(); + + msg.reason = StringSerializer.readString(data); + msg.type = StringSerializer.readString(data); + + return msg; + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + + // Add the null/non-null marker + buffer.put( (byte)(object != null ? 0x1 : 0x0) ); + if (object == null) { + // Nothing left to do + return; + } + + DisconnectMessage msg = (DisconnectMessage)object; + StringSerializer.writeString( msg.reason, buffer ); + StringSerializer.writeString( msg.type, buffer ); + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/message/GZIPCompressedMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/GZIPCompressedMessage.java new file mode 100644 index 000000000..a3065589a --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/message/GZIPCompressedMessage.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.message; + +import com.jme3.network.Message; +import com.jme3.network.serializing.Serializable; + +/** + * GZIPCompressedMessage is the class that you need to use should you want to + * compress a message using Gzip. + * + * @author Lars Wesselius + */ +@Serializable() +public class GZIPCompressedMessage extends CompressedMessage { + public GZIPCompressedMessage() { + super(); + } + + public GZIPCompressedMessage(Message msg) { + super(msg); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/message/ZIPCompressedMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/ZIPCompressedMessage.java new file mode 100644 index 000000000..c39e8b226 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/message/ZIPCompressedMessage.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.message; + +import com.jme3.network.Message; +import com.jme3.network.serializing.Serializable; + +/** + * Compress a message using this ZIPCompressedMessage class + * + * @author Lars Wesselius + */ +@Serializable() +public class ZIPCompressedMessage extends CompressedMessage { + private static int compressionLevel = 6; + + public ZIPCompressedMessage() { + super(); + } + + public ZIPCompressedMessage(Message msg) { + super(msg); + } + + public ZIPCompressedMessage(Message msg, int level) { + super(msg); + setLevel(level); + } + + /** + * Set the compression level, where 1 is the best compression but slower and 9 is the weakest + * compression but the quickest. Default is 6. + * + * @param level The level. + */ + public static void setLevel(int level) { + compressionLevel = level; + } + + public int getLevel() { return compressionLevel; } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/package.html b/jme3-networking/src/main/java/com/jme3/network/package.html new file mode 100644 index 000000000..46dfa80c1 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/package.html @@ -0,0 +1,14 @@ + + + + + + + +The network package contains the public API for the jME3 +SpiderMonkey networking module. The {@link com.jme3.network.Network} +class is the entry point for creating default implementations +of {@link com.jme3.network.Client} and {@link com.jme3.network.Server} +implementations. + + diff --git a/jme3-networking/src/main/java/com/jme3/network/rmi/LocalObject.java b/jme3-networking/src/main/java/com/jme3/network/rmi/LocalObject.java new file mode 100644 index 000000000..3c3a22530 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/LocalObject.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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/jme3-networking/src/main/java/com/jme3/network/rmi/MethodDef.java b/jme3-networking/src/main/java/com/jme3/network/rmi/MethodDef.java new file mode 100644 index 000000000..1b04c8a2a --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/MethodDef.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.rmi; + + +/** + * Method definition is used to map methods on an RMI interface + * to an implementation on a remote machine. + * + * @author Kirill Vainer + */ +public class MethodDef { + + /** + * Method name + */ + public String name; + + /** + * Return type + */ + public Class retType; + + /** + * Parameter types + */ + public Class[] paramTypes; +} diff --git a/jme3-networking/src/main/java/com/jme3/network/rmi/ObjectDef.java b/jme3-networking/src/main/java/com/jme3/network/rmi/ObjectDef.java new file mode 100644 index 000000000..64ffe3057 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/ObjectDef.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.rmi; + + +import com.jme3.network.serializing.Serializable; +import java.lang.reflect.Method; + +@Serializable +public class ObjectDef { + + /** + * The object name, can be null if undefined. + */ + public String objectName; + + /** + * Object ID + */ + public int objectId; + + /** + * Methods of the implementation on the local client. Set to null + * on remote clients. + */ + public Method[] methods; + + /** + * Method definitions of the implementation. Set to null on + * the local client. + */ + public MethodDef[] methodDefs; + + @Override + public String toString(){ + return "ObjectDef[name=" + objectName + ", objectId=" + objectId+"]"; + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/rmi/ObjectStore.java b/jme3-networking/src/main/java/com/jme3/network/rmi/ObjectStore.java new file mode 100644 index 000000000..05634b3ab --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/ObjectStore.java @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.rmi; + +import com.jme3.network.*; +import com.jme3.network.ClientStateListener.DisconnectInfo; +import com.jme3.network.serializing.Serializer; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ObjectStore { + + private static final Logger logger = Logger.getLogger(ObjectStore.class.getName()); + + private static final class Invocation { + + Object retVal; + boolean available = false; + + @Override + public String toString(){ + return "Invocation[" + retVal + "]"; + } + } + + private Client client; + private Server server; + + private ClientEventHandler clientEventHandler = new ClientEventHandler(); + private ServerEventHandler serverEventHandler = new ServerEventHandler(); + + // Local object ID counter + private volatile short objectIdCounter = 0; + + // Local invocation ID counter + private volatile short invocationIdCounter = 0; + + // Invocations waiting .. + private IntMap 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(); + + public class ServerEventHandler implements MessageListener, + ConnectionListener { + + public void messageReceived(HostedConnection source, Message m) { + onMessage(source, m); + } + + public void connectionAdded(Server server, HostedConnection conn) { + onConnection(conn); + } + + public void connectionRemoved(Server server, HostedConnection conn) { + } + + } + + public class ClientEventHandler implements MessageListener, + ClientStateListener { + + public void messageReceived(Object source, Message m) { + onMessage(null, m); + } + + public void clientConnected(Client c) { + onConnection(null); + } + + public void clientDisconnected(Client c, DisconnectInfo info) { + } + + } + + static { + Serializer s = new RmiSerializer(); + Serializer.registerClass(RemoteObjectDefMessage.class, s); + Serializer.registerClass(RemoteMethodCallMessage.class, s); + Serializer.registerClass(RemoteMethodReturnMessage.class, s); + } + + public ObjectStore(Client client) { + this.client = client; + client.addMessageListener(clientEventHandler, + RemoteObjectDefMessage.class, + RemoteMethodCallMessage.class, + RemoteMethodReturnMessage.class); + client.addClientStateListener(clientEventHandler); + } + + public ObjectStore(Server server) { + this.server = server; + server.addMessageListener(serverEventHandler, + RemoteObjectDefMessage.class, + RemoteMethodCallMessage.class, + RemoteMethodReturnMessage.class); + server.addConnectionListener(serverEventHandler); + } + + private ObjectDef makeObjectDef(LocalObject localObj){ + ObjectDef def = new ObjectDef(); + def.objectName = localObj.objectName; + def.objectId = localObj.objectId; + def.methods = localObj.methods; + return def; + } + + public void exposeObject(String name, Object obj) throws IOException{ + // Create a local object + LocalObject localObj = new LocalObject(); + localObj.objectName = name; + localObj.objectId = objectIdCounter++; + localObj.theObject = obj; + //localObj.methods = obj.getClass().getMethods(); + + ArrayList methodList = new ArrayList(); + for (Method method : obj.getClass().getMethods()){ + if (method.getDeclaringClass() == obj.getClass()){ + methodList.add(method); + } + } + localObj.methods = methodList.toArray(new Method[methodList.size()]); + + // Put it in the store + localObjects.put(localObj.objectId, localObj); + + // Inform the others of its existence + RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage(); + defMsg.objects = new ObjectDef[]{ makeObjectDef(localObj) }; + + if (client != null) { + client.send(defMsg); + logger.log(Level.FINE, "Client: Sending {0}", defMsg); + } else { + server.broadcast(defMsg); + logger.log(Level.FINE, "Server: Sending {0}", 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(); + // Note: could cause threading issues if used from multiple threads + pendingInvocations.put(call.invocationId, invoke); + } + + if (server != null){ + remoteObj.client.send(call); + logger.log(Level.FINE, "Server: Sending {0}", call); + }else{ + client.send(call); + logger.log(Level.FINE, "Client: Sending {0}", call); + } + + if (invoke != null){ + synchronized(invoke){ + while (!invoke.available){ + try { + invoke.wait(); + } catch (InterruptedException ex){ + ex.printStackTrace(); + } + } + } + // Note: could cause threading issues if used from multiple threads + pendingInvocations.remove(call.invocationId); + return invoke.retVal; + }else{ + return null; + } + } + + private void onMessage(HostedConnection source, Message message) { + // Might want to do more strict validation of the data + // in the message to prevent crashes + + if (message instanceof RemoteObjectDefMessage){ + RemoteObjectDefMessage defMsg = (RemoteObjectDefMessage) message; + + ObjectDef[] defs = defMsg.objects; + for (ObjectDef def : defs){ + RemoteObject remoteObject = new RemoteObject(this, source); + remoteObject.objectId = (short)def.objectId; + remoteObject.methodDefs = def.methodDefs; + remoteObjects.put(def.objectName, remoteObject); + remoteObjectsById.put(def.objectId, remoteObject); + } + + synchronized (receiveObjectLock){ + receiveObjectLock.notifyAll(); + } + }else if (message instanceof RemoteMethodCallMessage){ + RemoteMethodCallMessage call = (RemoteMethodCallMessage) message; + LocalObject localObj = localObjects.get(call.objectId); + if (localObj == null) + return; + + if (call.methodId < 0 || call.methodId >= localObj.methods.length) + return; + + Object obj = localObj.theObject; + Method method = localObj.methods[call.methodId]; + Object[] args = call.args; + Object ret = null; + try { + ret = method.invoke(obj, args); + } catch (IllegalAccessException ex){ + logger.log(Level.WARNING, "RMI: Error accessing method", ex); + } catch (IllegalArgumentException ex){ + logger.log(Level.WARNING, "RMI: Invalid arguments", ex); + } catch (InvocationTargetException ex){ + logger.log(Level.WARNING, "RMI: Invocation exception", ex); + } + + if (method.getReturnType() != void.class){ + // send return value back + RemoteMethodReturnMessage retMsg = new RemoteMethodReturnMessage(); + retMsg.invocationID = call.invocationId; + retMsg.retVal = ret; + if (server != null){ + source.send(retMsg); + logger.log(Level.FINE, "Server: Sending {0}", retMsg); + } else{ + client.send(retMsg); + logger.log(Level.FINE, "Client: Sending {0}", retMsg); + } + } + }else if (message instanceof RemoteMethodReturnMessage){ + RemoteMethodReturnMessage retMsg = (RemoteMethodReturnMessage) message; + Invocation invoke = pendingInvocations.get(retMsg.invocationID); + if (invoke == null){ + logger.log(Level.WARNING, "Cannot find invocation ID: {0}", retMsg.invocationID); + return; + } + + synchronized (invoke){ + invoke.retVal = retMsg.retVal; + invoke.available = true; + invoke.notifyAll(); + } + } + } + + private void onConnection(HostedConnection conn) { + if (localObjects.size() > 0){ + // send a object definition message + ObjectDef[] defs = new ObjectDef[localObjects.size()]; + int i = 0; + for (Entry entry : localObjects){ + defs[i] = makeObjectDef(entry.getValue()); + i++; + } + + RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage(); + defMsg.objects = defs; + if (this.client != null){ + this.client.send(defMsg); + logger.log(Level.FINE, "Client: Sending {0}", defMsg); + } else{ + conn.send(defMsg); + logger.log(Level.FINE, "Server: Sending {0}", defMsg); + } + } + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteMethodCallMessage.java b/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteMethodCallMessage.java new file mode 100644 index 000000000..8c43c2842 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteMethodCallMessage.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.rmi; + +import com.jme3.network.AbstractMessage; +import com.jme3.network.serializing.Serializable; + +/** + * Sent to a remote client to make a remote method invocation. + * + * @author Kirill Vainer + */ +@Serializable +public class RemoteMethodCallMessage extends AbstractMessage { + + public RemoteMethodCallMessage(){ + super(true); + } + + /** + * The object ID on which the call is being made. + */ + public int objectId; + + /** + * The method ID used for look-up in the LocalObject.methods array. + */ + public short methodId; + + /** + * Invocation ID is used to identify a particular call if the calling + * client needs the return value of the called RMI method. + * This is set to zero if the method does not return a value. + */ + public short invocationId; + + /** + * Arguments of the remote method invocation. + */ + public Object[] args; + + + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + sb.append("RemoteMethodCallMessage[objectID=").append(objectId).append(", methodID=") + .append(methodId); + if (args != null && args.length > 0){ + sb.append(", args={"); + for (Object arg : args){ + sb.append(arg.toString()).append(", "); + } + sb.setLength(sb.length()-2); + sb.append("}"); + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteMethodReturnMessage.java b/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteMethodReturnMessage.java new file mode 100644 index 000000000..31f2b396d --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteMethodReturnMessage.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.rmi; + +import com.jme3.network.AbstractMessage; +import com.jme3.network.serializing.Serializable; + +/** + * Contains the return value for a remote method invocation, sent as a response + * to a {@link RemoteMethodCallMessage} with a non-zero invocationID. + * + * @author Kirill Vainer. + */ +@Serializable +public class RemoteMethodReturnMessage extends AbstractMessage { + + public RemoteMethodReturnMessage(){ + super(true); + } + + /** + * Invocation ID that was set in the {@link RemoteMethodCallMessage}. + */ + public short invocationID; + + /** + * The return value, could be null. + */ + public Object retVal; + + + @Override + public String toString(){ + return "RemoteMethodReturnMessage[ID="+invocationID+", Value="+retVal.toString()+"]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteObject.java b/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteObject.java new file mode 100644 index 000000000..66f6c3531 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteObject.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.rmi; + +import com.jme3.network.HostedConnection; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Contains various meta-data about an RMI interface. + * + * @author Kirill Vainer + */ +public class RemoteObject implements InvocationHandler { + + /** + * Object ID + */ + short objectId; + + /** + * Contains {@link MethodDef method definitions} for all exposed + * RMI methods in the remote RMI interface. + */ + MethodDef[] methodDefs; + + /** + * Maps from methods locally retrieved from the RMI interface to + * a method ID. + */ + HashMap 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. + */ + HostedConnection client; + + public RemoteObject(ObjectStore store, HostedConnection client){ + this.store = store; + this.client = client; + } + + private boolean methodEquals(MethodDef methodDef, Method method){ + Class[] interfaceTypes = method.getParameterTypes(); + Class[] defTypes = methodDef.paramTypes; + + if (interfaceTypes.length == defTypes.length){ + for (int i = 0; i < interfaceTypes.length; i++){ + if (!defTypes[i].isAssignableFrom(interfaceTypes[i])){ + return false; + } + } + return true; + } + return false; + } + + /** + * Generates mappings from the given interface into the remote RMI + * interface's implementation. + * + * @param interfaceClass The interface class to use. + */ + public void loadMethods(Class interfaceClass){ + HashMap> 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/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteObjectDefMessage.java b/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteObjectDefMessage.java new file mode 100644 index 000000000..a15ca5ccb --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteObjectDefMessage.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.rmi; + +import com.jme3.network.AbstractMessage; +import com.jme3.network.serializing.Serializable; + +/** + * Sent to expose RMI interfaces on the local client to other clients. + * @author Kirill Vainer + */ +@Serializable +public class RemoteObjectDefMessage extends AbstractMessage { + + public ObjectDef[] objects; + + public RemoteObjectDefMessage(){ + super(true); + } + + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + sb.append("RemoteObjectDefMessage[\n"); + for (ObjectDef def : objects){ + sb.append("\t").append(def).append("\n"); + } + sb.append("]"); + return sb.toString(); + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/rmi/RmiSerializer.java b/jme3-networking/src/main/java/com/jme3/network/rmi/RmiSerializer.java new file mode 100644 index 000000000..8f55178d0 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/RmiSerializer.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.rmi; + +import com.jme3.network.serializing.Serializer; +import com.jme3.network.serializing.SerializerRegistration; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * {@link RmiSerializer} is responsible for serializing RMI messages + * like define object, call, and return. + * + * @author Kirill Vainer + */ +public class RmiSerializer extends Serializer { + + private static final Logger logger = Logger.getLogger(RmiSerializer.class.getName()); + + // not good for multithread applications + private char[] chrBuf = new char[256]; + + private void writeString(ByteBuffer buffer, String string) throws IOException{ + int length = string.length(); + if (length > 255){ + logger.log(Level.WARNING, "The string length exceeds the limit! {0} > 255", length); + buffer.put( (byte) 0 ); + return; + } + + buffer.put( (byte) length ); + for (int i = 0; i < length; i++){ + buffer.put( (byte) string.charAt(i) ); + } + } + + private String readString(ByteBuffer buffer){ + int length = buffer.get() & 0xff; + for (int i = 0; i < length; i++){ + chrBuf[i] = (char) (buffer.get() & 0xff); + } + return String.valueOf(chrBuf, 0, length); + } + + private void writeType(ByteBuffer buffer, Class clazz) throws IOException{ + if (clazz == void.class){ + buffer.putShort((short)0); + } else { + SerializerRegistration reg = Serializer.getSerializerRegistration(clazz); + if (reg == null){ + logger.log(Level.WARNING, "Unknown class: {0}", clazz); + throw new IOException(); // prevents message from being serialized + } + buffer.putShort(reg.getId()); + } + } + + private Class readType(ByteBuffer buffer) throws IOException{ + SerializerRegistration reg = Serializer.readClass(buffer); + if (reg == null){ + // either "void" or unknown val + short id = buffer.getShort(buffer.position()-2); + if (id == 0){ + return void.class; + } else{ + logger.log(Level.WARNING, "Undefined class ID: {0}", id); + throw new IOException(); // prevents message from being serialized + } + } + return reg.getType(); + } + + private void writeMethod(ByteBuffer buffer, Method method) throws IOException{ + String name = method.getName(); + Class[] paramTypes = method.getParameterTypes(); + Class returnType = method.getReturnType(); + + writeString(buffer, name); + writeType(buffer, returnType); + buffer.put((byte)paramTypes.length); + for (Class paramType : paramTypes) + writeType(buffer, paramType); + } + + private MethodDef readMethod(ByteBuffer buffer) throws IOException{ + String name = readString(buffer); + Class retType = readType(buffer); + + int numParams = buffer.get() & 0xff; + Class[] paramTypes = new Class[numParams]; + for (int i = 0; i < numParams; i++){ + paramTypes[i] = readType(buffer); + } + + MethodDef def = new MethodDef(); + def.name = name; + def.paramTypes = paramTypes; + def.retType = retType; + return def; + } + + private void writeObjectDef(ByteBuffer buffer, ObjectDef def) throws IOException{ + buffer.putShort((short)def.objectId); + writeString(buffer, def.objectName); + Method[] methods = def.methods; + buffer.put( (byte) methods.length ); + for (Method method : methods){ + writeMethod(buffer, method); + } + } + + private ObjectDef readObjectDef(ByteBuffer buffer) throws IOException{ + ObjectDef def = new ObjectDef(); + + def.objectId = buffer.getShort(); + def.objectName = readString(buffer); + + int numMethods = buffer.get() & 0xff; + MethodDef[] methodDefs = new MethodDef[numMethods]; + for (int i = 0; i < numMethods; i++){ + methodDefs[i] = readMethod(buffer); + } + def.methodDefs = methodDefs; + return def; + } + + private void writeObjectDefs(ByteBuffer buffer, RemoteObjectDefMessage defMsg) throws IOException{ + ObjectDef[] defs = defMsg.objects; + buffer.put( (byte) defs.length ); + for (ObjectDef def : defs) + writeObjectDef(buffer, def); + } + + private RemoteObjectDefMessage readObjectDefs(ByteBuffer buffer) throws IOException{ + RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage(); + int numObjs = buffer.get() & 0xff; + ObjectDef[] defs = new ObjectDef[numObjs]; + for (int i = 0; i < numObjs; i++){ + defs[i] = readObjectDef(buffer); + } + defMsg.objects = defs; + return defMsg; + } + + private void writeMethodCall(ByteBuffer buffer, RemoteMethodCallMessage call) throws IOException{ + buffer.putShort((short)call.objectId); + buffer.putShort(call.methodId); + buffer.putShort(call.invocationId); + if (call.args == null){ + buffer.put((byte)0); + }else{ + buffer.put((byte)call.args.length); + + // Right now it writes 0 for every null argument + // and 1 for every non-null argument followed by the serialized + // argument. For the future, using a bit set should be considered. + for (Object obj : call.args){ + if (obj != null){ + buffer.put((byte)0x01); + Serializer.writeClassAndObject(buffer, obj); + }else{ + buffer.put((byte)0x00); + } + } + } + } + + private RemoteMethodCallMessage readMethodCall(ByteBuffer buffer) throws IOException{ + RemoteMethodCallMessage call = new RemoteMethodCallMessage(); + call.objectId = buffer.getShort(); + call.methodId = buffer.getShort(); + call.invocationId = buffer.getShort(); + int numArgs = buffer.get() & 0xff; + if (numArgs > 0){ + Object[] args = new Object[numArgs]; + for (int i = 0; i < numArgs; i++){ + if (buffer.get() == (byte)0x01){ + args[i] = Serializer.readClassAndObject(buffer); + } + } + call.args = args; + } + return call; + } + + private void writeMethodReturn(ByteBuffer buffer, RemoteMethodReturnMessage ret) throws IOException{ + buffer.putShort(ret.invocationID); + if (ret.retVal != null){ + buffer.put((byte)0x01); + Serializer.writeClassAndObject(buffer, ret.retVal); + }else{ + buffer.put((byte)0x00); + } + } + + private RemoteMethodReturnMessage readMethodReturn(ByteBuffer buffer) throws IOException{ + RemoteMethodReturnMessage ret = new RemoteMethodReturnMessage(); + ret.invocationID = buffer.getShort(); + if (buffer.get() == (byte)0x01){ + ret.retVal = Serializer.readClassAndObject(buffer); + } + return ret; + } + + @Override + public T 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/jme3-networking/src/main/java/com/jme3/network/serializing/Serializable.java b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializable.java new file mode 100644 index 000000000..456a7f432 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializable.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java new file mode 100644 index 000000000..a7d20da54 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing; + +import com.jme3.math.Vector3f; +import com.jme3.network.message.ChannelInfoMessage; +import com.jme3.network.message.ClientRegistrationMessage; +import com.jme3.network.message.DisconnectMessage; +import com.jme3.network.message.GZIPCompressedMessage; +import com.jme3.network.message.ZIPCompressedMessage; +import com.jme3.network.serializing.serializers.*; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.jar.Attributes; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The main serializer class, which will serialize objects such that + * they can be sent across the network. Serializing classes should extend + * this to provide their own serialization. + * + * @author Lars Wesselius + */ +public abstract class Serializer { + protected static final Logger log = Logger.getLogger(Serializer.class.getName()); + + private static final SerializerRegistration NULL_CLASS = new SerializerRegistration( null, Void.class, (short)-1 ); + + private static final Map idRegistrations = new HashMap(); + private static final Map classRegistrations = new HashMap(); + private static final List registrations = new ArrayList(); + + private static final Serializer fieldSerializer = new FieldSerializer(); + private static final Serializer serializableSerializer = new SerializableSerializer(); + private static final Serializer arraySerializer = new ArraySerializer(); + + private static short nextAvailableId = -2; // historically the first ID was always -2 + + private static boolean strictRegistration = true; + + + // Registers the classes we already have serializers for. + static { + + // Preregister some fixed serializers so that they don't move + // if the list below is modified. Automatic ID generation will + // skip these IDs. + registerClassForId( DisconnectMessage.SERIALIZER_ID, DisconnectMessage.class, + new DisconnectMessage.DisconnectSerializer() ); + registerClassForId( ClientRegistrationMessage.SERIALIZER_ID, ClientRegistrationMessage.class, + new ClientRegistrationMessage.ClientRegistrationSerializer() ); + + + + 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(HashSet.class, new CollectionSerializer()); + registerClass(LinkedHashSet.class, new CollectionSerializer()); + registerClass(LinkedList.class, new CollectionSerializer()); + registerClass(TreeSet.class, new CollectionSerializer()); + registerClass(Vector.class, new CollectionSerializer()); + + // All the Map classes go here + registerClass(AbstractMap.class, new MapSerializer()); + registerClass(Attributes.class, new MapSerializer()); + registerClass(HashMap.class, new MapSerializer()); + registerClass(Hashtable.class, new MapSerializer()); + registerClass(IdentityHashMap.class, new MapSerializer()); + registerClass(TreeMap.class, new MapSerializer()); + registerClass(WeakHashMap.class, new MapSerializer()); + + registerClass(Enum.class, new EnumSerializer()); + registerClass(GZIPCompressedMessage.class, new GZIPSerializer()); + registerClass(ZIPCompressedMessage.class, new ZIPSerializer()); + + registerClass(ChannelInfoMessage.class); + } + + /** + * When set to true, classes that do not have intrinsic IDs in their + * @Serializable will not be auto-registered during write. Defaults + * to true since this is almost never desired behavior with the way + * this code works. Set to false to get the old permissive behavior. + */ + public static void setStrictRegistration( boolean b ) { + strictRegistration = b; + } + + public static SerializerRegistration registerClass(Class cls) { + return registerClass(cls, true); + } + + public static void registerClasses(Class... classes) { + for( Class c : classes ) { + registerClass(c); + } + } + + private static short nextId() { + + // If the ID we are about to return is already in use + // then skip it. + while (idRegistrations.containsKey(nextAvailableId) ) { + nextAvailableId--; + } + + // Return the available ID and post-decrement to get + // ready for next time. + return nextAvailableId--; + } + + /** + * Directly registers a class for a specific ID. Generally, use the regular + * registerClass() method. This method is intended for framework code that might + * be maintaining specific ID maps across client and server. + */ + public static SerializerRegistration registerClassForId( short id, Class cls, Serializer serializer ) { + + SerializerRegistration reg = new SerializerRegistration(serializer, cls, id); + + idRegistrations.put(id, reg); + classRegistrations.put(cls, reg); + + log.log( Level.FINE, "Registered class[" + id + "]:{0} to:" + serializer, cls ); + + serializer.initialize(cls); + + // Add the class after so that dependency order is preserved if the + // serializer registers its own classes. + registrations.add(reg); + + return reg; + } + + /** + * Registers the specified class. The failOnMiss flag controls whether or + * not this method returns null for failed registration or throws an exception. + */ + @SuppressWarnings("unchecked") + public static SerializerRegistration registerClass(Class cls, boolean failOnMiss) { + if (cls.isAnnotationPresent(Serializable.class)) { + Serializable serializable = (Serializable)cls.getAnnotation(Serializable.class); + + Class serializerClass = serializable.serializer(); + short classId = serializable.id(); + if (classId == 0) classId = nextId(); + + Serializer serializer = getSerializer(serializerClass, false); + + if (serializer == null) serializer = fieldSerializer; + + SerializerRegistration existingReg = getExactSerializerRegistration(cls); + + if (existingReg != null) classId = existingReg.getId(); + + SerializerRegistration reg = new SerializerRegistration(serializer, cls, classId); + + return registerClassForId( classId, cls, serializer ); + } + if (failOnMiss) { + throw new IllegalArgumentException( "Class is not marked @Serializable:" + cls ); + } + return null; + } + + /** + * @deprecated This cannot be implemented in a reasonable way that works in + * all deployment methods. + */ + @Deprecated + public static SerializerRegistration[] registerPackage(String pkgName) { + try { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + String path = pkgName.replace('.', '/'); + Enumeration 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, false); + } + 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; + if (existingReg != null) { + id = existingReg.getId(); + } else { + id = nextId(); + } + return registerClassForId( id, cls, serializer ); + } + + public static Serializer getExactSerializer(Class cls) { + return classRegistrations.get(cls).getSerializer(); + } + + public static Serializer getSerializer(Class cls) { + return getSerializer(cls, true); + } + + public static Serializer getSerializer(Class cls, boolean failOnMiss) { + return getSerializerRegistration(cls, failOnMiss).getSerializer(); + } + + public static Collection getSerializerRegistrations() { + return registrations; + } + + public static SerializerRegistration getExactSerializerRegistration(Class cls) { + return classRegistrations.get(cls); + } + + public static SerializerRegistration getSerializerRegistration(Class cls) { + return getSerializerRegistration(cls, strictRegistration); + } + + @SuppressWarnings("unchecked") + public static SerializerRegistration getSerializerRegistration(Class cls, boolean failOnMiss) { + SerializerRegistration reg = classRegistrations.get(cls); + + if (reg != null) return reg; + + for (Map.Entry entry : classRegistrations.entrySet()) { + if (entry.getKey().isAssignableFrom(Serializable.class)) continue; + if (entry.getKey().isAssignableFrom(cls)) return entry.getValue(); + } + + if (cls.isArray()) return registerClass(cls, arraySerializer); + + if (Serializable.class.isAssignableFrom(cls)) { + return getExactSerializerRegistration(java.io.Serializable.class); + } + + // See if the class could be safely auto-registered + if (cls.isAnnotationPresent(Serializable.class)) { + Serializable serializable = (Serializable)cls.getAnnotation(Serializable.class); + short classId = serializable.id(); + if( classId != 0 ) { + // No reason to fail because the ID is fixed + failOnMiss = false; + } + } + + if( failOnMiss ) { + throw new IllegalArgumentException( "Class has not been registered:" + cls ); + } + return registerClass(cls, fieldSerializer); + } + + + /////////////////////////////////////////////////////////////////////////////////// + + + /** + * Read the class from given buffer and return its SerializerRegistration. + * + * @param buffer The buffer to read from. + * @return The SerializerRegistration, or null if non-existent. + */ + public static SerializerRegistration readClass(ByteBuffer buffer) { + short classID = buffer.getShort(); + if (classID == -1) return NULL_CLASS; + return idRegistrations.get(classID); + } + + /** + * Read the class and the object. + * + * @param buffer Buffer to read from. + * @return The Object that was read. + * @throws IOException If serialization failed. + */ + @SuppressWarnings("unchecked") + public static Object readClassAndObject(ByteBuffer buffer) throws IOException { + SerializerRegistration reg = readClass(buffer); + if (reg == NULL_CLASS) return null; + if (reg == null) throw new SerializerException( "Class not found for buffer data." ); + return reg.getSerializer().readObject(buffer, reg.getType()); + } + + /** + * Write a class and return its SerializerRegistration. + * + * @param buffer The buffer to write the given class to. + * @param type The class to write. + * @return The SerializerRegistration that's registered to the class. + */ + public static SerializerRegistration writeClass(ByteBuffer buffer, Class type) throws IOException { + SerializerRegistration reg = getSerializerRegistration(type); + if (reg == null) { + throw new SerializerException( "Class not registered:" + type ); + } + buffer.putShort(reg.getId()); + return reg; + } + + /** + * Write the class and object. + * + * @param buffer The buffer to write to. + * @param object The object to write. + * @throws IOException If serializing fails. + */ + public static void writeClassAndObject(ByteBuffer buffer, Object object) throws IOException { + if (object == null) { + buffer.putShort((short)-1); + return; + } + SerializerRegistration reg = writeClass(buffer, object.getClass()); + reg.getSerializer().writeObject(buffer, object); + } + + /** + * Read an object from the buffer, effectively deserializing it. + * + * @param data The buffer to read from. + * @param c The class of the object. + * @return The object read. + * @throws IOException If deserializing fails. + */ + public abstract T 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/jme3-networking/src/main/java/com/jme3/network/serializing/SerializerException.java b/jme3-networking/src/main/java/com/jme3/network/serializing/SerializerException.java new file mode 100644 index 000000000..655e31d7a --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/SerializerException.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing; + +import java.io.IOException; + +/** + * A general exception from the serialization routines. + * + * @version $Revision$ + * @author Paul Speed + */ +public class SerializerException extends IOException +{ + public SerializerException( String msg, Throwable cause ) + { + super( msg ); + initCause(cause); + } + + public SerializerException( String msg ) + { + super( msg ); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/SerializerRegistration.java b/jme3-networking/src/main/java/com/jme3/network/serializing/SerializerRegistration.java new file mode 100644 index 000000000..373d969eb --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/SerializerRegistration.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing; + +/** + * A SerializerRegistration represents a connection between a class, and + * its serializer. It also includes the class ID, as a short. + * + * @author Lars Wesselius + */ +public final class SerializerRegistration { + private Serializer serializer; + private short id; + private Class type; + + public SerializerRegistration(Serializer serializer, Class cls, short id) { + this.serializer = serializer; + type = cls; + this.id = id; + } + + /** + * Get the serializer. + * + * @return The serializer. + */ + public Serializer getSerializer() { + return serializer; + } + + /** + * Get the ID. + * + * @return The ID. + */ + public short getId() { + return id; + } + + /** + * Get the class type. + * + * @return The class type. + */ + public Class getType() { + return type; + } + + public String toString() { + return "SerializerRegistration[" + id + ", " + type + ", " + serializer + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ArraySerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ArraySerializer.java new file mode 100644 index 000000000..564f4a06e --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ArraySerializer.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine, Java Game Networking + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.Modifier; +import java.nio.ByteBuffer; + +/** + * Array serializer + * + * @author Nathan Sweet + */ +@SuppressWarnings("unchecked") +public class ArraySerializer extends Serializer { + private int[] getDimensions (Object array) { + int depth = 0; + Class nextClass = array.getClass().getComponentType(); + while (nextClass != null) { + depth++; + nextClass = nextClass.getComponentType(); + } + int[] dimensions = new int[depth]; + dimensions[0] = Array.getLength(array); + if (depth > 1) collectDimensions(array, 1, dimensions); + return dimensions; + } + + private void collectDimensions (Object array, int dimension, int[] dimensions) { + boolean elementsAreArrays = dimension < dimensions.length - 1; + for (int i = 0, s = Array.getLength(array); i < s; i++) { + Object element = Array.get(array, i); + if (element == null) continue; + dimensions[dimension] = Math.max(dimensions[dimension], Array.getLength(element)); + if (elementsAreArrays) collectDimensions(element, dimension + 1, dimensions); + } + } + + public T 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/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/BooleanSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/BooleanSerializer.java new file mode 100644 index 000000000..2bfb8e62b --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/BooleanSerializer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Boolean serializer. + * + * @author Lars Wesselius + */ +@SuppressWarnings("unchecked") +public class BooleanSerializer extends Serializer { + + public Boolean readObject(ByteBuffer data, Class c) throws IOException { + return data.get() == 1; + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + buffer.put(((Boolean)object) ? (byte)1 : (byte)0); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ByteSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ByteSerializer.java new file mode 100644 index 000000000..4e085bbe6 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ByteSerializer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Byte serializer. + * + * @author Lars Wesselius + */ +@SuppressWarnings("unchecked") +public class ByteSerializer extends Serializer { + + public Byte readObject(ByteBuffer data, Class c) throws IOException { + return data.get(); + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + buffer.put((Byte)object); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CharSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CharSerializer.java new file mode 100644 index 000000000..274bc30db --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CharSerializer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Char serializer. + * + * @author Lars Wesselius + */ +@SuppressWarnings("unchecked") +public class CharSerializer extends Serializer { + + public Character readObject(ByteBuffer data, Class c) throws IOException { + return data.getChar(); + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + buffer.putChar((Character)object); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CollectionSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CollectionSerializer.java new file mode 100644 index 000000000..960e8be10 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CollectionSerializer.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import com.jme3.network.serializing.SerializerRegistration; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.logging.Level; + +/** + * Serializes collections. + * + * @author Lars Wesselius + */ +public class CollectionSerializer extends Serializer { + + @SuppressWarnings("unchecked") + public T 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/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DateSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DateSerializer.java new file mode 100644 index 000000000..5bd8c465a --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DateSerializer.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Date; + +/** + * Date serializer. + * + * @author Lars Wesselius + */ +@SuppressWarnings("unchecked") +public class DateSerializer extends Serializer { + + public Date readObject(ByteBuffer data, Class c) throws IOException { + return new Date(data.getLong()); + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + buffer.putLong(((Date)object).getTime()); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DoubleSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DoubleSerializer.java new file mode 100644 index 000000000..cb7494d9e --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DoubleSerializer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Double serializer. + * + * @author Lars Wesselius + */ +@SuppressWarnings("unchecked") +public class DoubleSerializer extends Serializer { + + public Double readObject(ByteBuffer data, Class c) throws IOException { + return data.getDouble(); + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + buffer.putDouble((Double)object); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/EnumSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/EnumSerializer.java new file mode 100644 index 000000000..017544b2a --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/EnumSerializer.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import com.jme3.network.serializing.SerializerException; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Enum serializer. + * + * @author Lars Wesselius + */ +public class EnumSerializer extends Serializer { + public T readObject(ByteBuffer data, Class c) throws IOException { + try { + int ordinal = data.getInt(); + + if (ordinal == -1) return null; + T[] enumConstants = c.getEnumConstants(); + if (enumConstants == null) + throw new SerializerException( "Class has no enum constants:" + c ); + return enumConstants[ordinal]; + } catch (IndexOutOfBoundsException ex) { + return null; + } + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + if (object == null) { + buffer.putInt(-1); + } else { + buffer.putInt(((Enum)object).ordinal()); + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FieldSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FieldSerializer.java new file mode 100644 index 000000000..0c6143073 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FieldSerializer.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine, Java Game Networking + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import com.jme3.network.serializing.SerializerException; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.util.*; + +/** + * 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(); + + protected void checkClass(Class clazz) { + + // See if the class has a public no-arg constructor + try { + clazz.getConstructor(); + } catch( NoSuchMethodException e ) { + throw new RuntimeException( "Registration error: no-argument constructor not found on:" + clazz ); + } + } + + public void initialize(Class clazz) { + + checkClass(clazz); + + List fields = new ArrayList(); + + Class processingClass = clazz; + while (processingClass != Object.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())) { + // The type of this field is implicit in the outer class + // definition and because the type is final, it can confidently + // be determined on the other end. + // Note: passing false to this method has the side-effect that field.getType() + // will be registered as a real class that can then be read/written + // directly as any other registered class. It should be safe to take + // an ID like this because Serializer.initialize() is only called + // during registration... so this is like nested registration and + // doesn't have any ordering problems. + // ...well, as long as the order of fields is consistent from one + // end to the next. + cachedField.serializer = Serializer.getSerializer(field.getType(), false); + } + + cachedFields.add(cachedField); + } + + Collections.sort(cachedFields, new Comparator() { + public int compare (SavedField o1, SavedField o2) { + return o1.field.getName().compareTo(o2.field.getName()); + } + }); + savedFields.put(clazz, cachedFields.toArray(new SavedField[cachedFields.size()])); + + + } + + @SuppressWarnings("unchecked") + public T readObject(ByteBuffer data, Class c) throws IOException { + + // Read the null/non-null marker + if (data.get() == 0x0) + return null; + + SavedField[] fields = savedFields.get(c); + + T object; + try { + object = c.newInstance(); + } catch (Exception e) { + throw new SerializerException( "Error creating object of type:" + c, e ); + } + + for (SavedField savedField : fields) { + Field field = savedField.field; + Serializer serializer = savedField.serializer; + Object value; + + if (serializer != null) { + value = serializer.readObject(data, field.getType()); + } else { + value = Serializer.readClassAndObject(data); + } + try { + field.set(object, value); + } catch (IllegalAccessException e) { + throw new SerializerException( "Error reading object", e); + } + } + return object; + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + + // Add the null/non-null marker + buffer.put( (byte)(object != null ? 0x1 : 0x0) ); + if (object == null) { + // Nothing left to do + return; + } + + SavedField[] fields = savedFields.get(object.getClass()); + if (fields == null) + throw new IOException("The " + object.getClass() + " is not registered" + + " in the serializer!"); + + for (SavedField savedField : fields) { + Object val = null; + try { + val = savedField.field.get(object); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + Serializer serializer = savedField.serializer; + + try { + if (serializer != null) { + serializer.writeObject(buffer, val); + } else { + Serializer.writeClassAndObject(buffer, val); + } + } catch (BufferOverflowException boe) { + throw boe; + } catch (Exception e) { + throw new SerializerException( "Error writing object for field:" + savedField.field, e ); + } + } + } + + private final class SavedField { + public Field field; + public Serializer serializer; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FloatSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FloatSerializer.java new file mode 100644 index 000000000..519b43550 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FloatSerializer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Float serializer. + * + * @author Lars Wesselius + */ +@SuppressWarnings("unchecked") +public class FloatSerializer extends Serializer { + + public Float readObject(ByteBuffer data, Class c) throws IOException { + return data.getFloat(); + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + buffer.putFloat((Float)object); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java new file mode 100644 index 000000000..01dbd1d41 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.Message; +import com.jme3.network.message.GZIPCompressedMessage; +import com.jme3.network.serializing.Serializer; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * Serializes GZIP messages. + * + * @author Lars Wesselius + */ +public class GZIPSerializer extends Serializer { + + @SuppressWarnings("unchecked") + public T 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/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/IntSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/IntSerializer.java new file mode 100644 index 000000000..db8a4954c --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/IntSerializer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * The Integer serializer serializes...integers. Big surprise. + * + * @author Lars Wesselius + */ +@SuppressWarnings("unchecked") +public class IntSerializer extends Serializer { + + public Integer readObject(ByteBuffer data, Class c) throws IOException { + return data.getInt(); + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + buffer.putInt((Integer)object); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/LongSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/LongSerializer.java new file mode 100644 index 000000000..28ebb28ca --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/LongSerializer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * The Long serializer. + * + * @author Lars Wesselius + */ +@SuppressWarnings("unchecked") +public class LongSerializer extends Serializer { + + public Long readObject(ByteBuffer data, Class c) throws IOException { + return data.getLong(); + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + buffer.putLong((Long)object); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/MapSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/MapSerializer.java new file mode 100644 index 000000000..4d41c57f0 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/MapSerializer.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import com.jme3.network.serializing.SerializerRegistration; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Level; + +public class MapSerializer extends Serializer { + + /* + + Structure: + + struct Map { + INT length + BYTE flags = { 0x01 = all keys have the same type, + 0x02 = all values have the same type } + if (flags has 0x01 set) + SHORT keyType + if (flags has 0x02 set) + SHORT valType + + struct MapEntry[length] entries { + if (flags does not have 0x01 set) + SHORT keyType + OBJECT key + + if (flags does not have 0x02 set) + SHORT valType + OBJECT value + } + } + + */ + + @SuppressWarnings("unchecked") + public T 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; + } + + @SuppressWarnings("unchecked") + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + Map map = (Map)object; + int length = map.size(); + + buffer.putInt(length); + if (length == 0) return; + + + Set 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/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/SavableSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/SavableSerializer.java new file mode 100644 index 000000000..47be44129 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/SavableSerializer.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.export.Savable; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +public class SavableSerializer extends Serializer { + + private BinaryExporter exporter = new BinaryExporter(); + private BinaryImporter importer = new BinaryImporter(); + + private static class BufferOutputStream extends OutputStream { + + ByteBuffer output; + + public BufferOutputStream(ByteBuffer output){ + this.output = output; + } + + @Override + public void write(int b) throws IOException { + output.put( (byte) b ); + } + + @Override + public void write(byte[] b){ + output.put(b); + } + + @Override + public void write(byte[] b, int off, int len){ + output.put(b, off, len); + } + } + + private static class BufferInputStream extends InputStream { + + ByteBuffer input; + + public BufferInputStream(ByteBuffer input){ + this.input = input; + } + + @Override + public int read() throws IOException { + if (input.remaining() == 0) + return -1; + else + return input.get() & 0xff; + } + + @Override + public int read(byte[] b){ + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len){ + int toRead = len > input.remaining() ? input.remaining() : len; + input.get(b, off, len); + return toRead; + } + + } + + @Override + @SuppressWarnings("unchecked") + public T 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/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/SerializableSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/SerializableSerializer.java new file mode 100644 index 000000000..32eb2af2e --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/SerializableSerializer.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.io.Serializable; +import java.nio.ByteBuffer; + +/** + * Serializes uses Java built-in method. + * + * TODO + * @author Lars Wesselius + */ +@SuppressWarnings("unchecked") +public class SerializableSerializer extends Serializer { + + public Serializable readObject(ByteBuffer data, Class c) throws IOException { + throw new UnsupportedOperationException( "Serializable serialization not supported." ); + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + throw new UnsupportedOperationException( "Serializable serialization not supported." ); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ShortSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ShortSerializer.java new file mode 100644 index 000000000..513162666 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ShortSerializer.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Short serializer. + * + * @author Lars Wesselius + */ +@SuppressWarnings("unchecked") +public class ShortSerializer extends Serializer { + public Short readObject(ByteBuffer data, Class c) throws IOException { + return data.getShort(); + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + buffer.putShort((Short)object); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/StringSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/StringSerializer.java new file mode 100644 index 000000000..3452bfa37 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/StringSerializer.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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, Paul Speed + */ +@SuppressWarnings("unchecked") +public class StringSerializer extends Serializer { + + public static void writeString( String s, ByteBuffer buffer ) throws IOException { + if (s == null) { + // Write that it's 0. + buffer.put((byte)0); + return; + } + byte[] stringBytes = s.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(); + } + } + + public static String readString( ByteBuffer data ) 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 String readObject(ByteBuffer data, Class c) throws IOException { + return readString(data); + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + String string = (String)object; + + writeString(string, buffer); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/Vector3Serializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/Vector3Serializer.java new file mode 100644 index 000000000..685dc5b9e --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/Vector3Serializer.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.math.Vector3f; +import com.jme3.network.serializing.Serializer; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * @author Kirill Vainer + */ +@SuppressWarnings("unchecked") +public class Vector3Serializer extends Serializer { + + public Vector3f readObject(ByteBuffer data, Class c) throws IOException { + Vector3f vec3 = new Vector3f(); + vec3.x = data.getFloat(); + vec3.y = data.getFloat(); + vec3.z = data.getFloat(); + return vec3; + } + + public void writeObject(ByteBuffer buffer, Object object) throws IOException { + Vector3f vec3 = (Vector3f) object; + buffer.putFloat(vec3.x); + buffer.putFloat(vec3.y); + buffer.putFloat(vec3.z); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ZIPSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ZIPSerializer.java new file mode 100644 index 000000000..1241c1341 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ZIPSerializer.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.network.serializing.serializers; + +import com.jme3.network.Message; +import com.jme3.network.message.ZIPCompressedMessage; +import com.jme3.network.serializing.Serializer; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/** + * Serializes ZIP messages. + * + * @author Lars Wesselius + */ +public class ZIPSerializer extends Serializer { + + @SuppressWarnings("unchecked") + public T 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/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java b/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java new file mode 100644 index 000000000..de685d445 --- /dev/null +++ b/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic.events; + +import com.jme3.animation.LoopMode; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.screen.NullScreen; +import java.io.IOException; + +/** + * + * @author Nehon + */ +public class GuiEvent extends AbstractCinematicEvent { + + protected String screen; + protected Nifty nifty; + + public GuiEvent() { + } + + public GuiEvent(Nifty nifty, String screen) { + this.screen = screen; + this.nifty = nifty; + } + + public GuiEvent(Nifty nifty, String screen, float initialDuration) { + super(initialDuration); + this.screen = screen; + this.nifty = nifty; + } + + public GuiEvent(Nifty nifty, String screen, LoopMode loopMode) { + super(loopMode); + this.screen = screen; + this.nifty = nifty; + } + + public GuiEvent(Nifty nifty, String screen, float initialDuration, LoopMode loopMode) { + super(initialDuration, loopMode); + this.screen = screen; + this.nifty = nifty; + } + + @Override + public void onPlay() { + System.out.println("screen should be " + screen); + nifty.gotoScreen(screen); + } + + @Override + public void onStop() { if (!(nifty.getCurrentScreen() instanceof NullScreen)) { + nifty.getCurrentScreen().endScreen(null); + } + } + + @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/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiTrack.java b/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiTrack.java new file mode 100644 index 000000000..04230e287 --- /dev/null +++ b/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiTrack.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.cinematic.events; + +import com.jme3.animation.LoopMode; +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 de.lessvoid.nifty.screen.NullScreen; +import java.io.IOException; + +/** + * + * @author Nehon + * @deprecated use GuiEvent instead + */ +@Deprecated +public class GuiTrack extends AbstractCinematicEvent { + + protected String screen; + protected Nifty nifty; + + public GuiTrack() { + } + + public GuiTrack(Nifty nifty, String screen) { + this.screen = screen; + this.nifty = nifty; + } + + public GuiTrack(Nifty nifty, String screen, float initialDuration) { + super(initialDuration); + this.screen = screen; + this.nifty = nifty; + } + + public GuiTrack(Nifty nifty, String screen, LoopMode loopMode) { + super(loopMode); + this.screen = screen; + this.nifty = nifty; + } + + public GuiTrack(Nifty nifty, String screen, float initialDuration, LoopMode loopMode) { + super(initialDuration, loopMode); + this.screen = screen; + this.nifty = nifty; + } + + @Override + public void onPlay() { + System.out.println("screen should be " + screen); + nifty.gotoScreen(screen); + } + + @Override + public void onStop() { if (!(nifty.getCurrentScreen() instanceof NullScreen)) { + nifty.getCurrentScreen().endScreen(null); + } + } + + @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/jme3-niftygui/src/main/java/com/jme3/niftygui/InputSystemJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/InputSystemJme.java new file mode 100644 index 000000000..6c0e52566 --- /dev/null +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/InputSystemJme.java @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.niftygui; + +import com.jme3.input.InputManager; +import com.jme3.input.KeyInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.SoftTextDialogInput; +import com.jme3.input.controls.SoftTextDialogInputListener; +import com.jme3.input.event.*; +import com.jme3.system.JmeSystem; +import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.NiftyInputConsumer; +import de.lessvoid.nifty.controls.TextField; +import de.lessvoid.nifty.controls.nullobjects.TextFieldNull; +import de.lessvoid.nifty.elements.Element; +import de.lessvoid.nifty.input.keyboard.KeyboardInputEvent; +import de.lessvoid.nifty.spi.input.InputSystem; +import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class InputSystemJme implements InputSystem, RawInputListener { + + private final ArrayList inputQueue = new ArrayList(); + private InputManager inputManager; + private boolean[] niftyOwnsDragging = new boolean[3]; + private int inputPointerId = -1; + private int x, y; + private int height; + private boolean shiftDown = false; + private boolean ctrlDown = false; + private Nifty nifty; + + public InputSystemJme(InputManager inputManager) { + this.inputManager = inputManager; + } + + public void setResourceLoader(NiftyResourceLoader niftyResourceLoader) { + } + + /** + * Must be set in order for nifty events to be forwarded correctly. + * + * @param nifty + */ + public void setNifty(Nifty nifty) { + this.nifty = nifty; + } + + /** + * Reset internal state of the input system. + * Must be called when the display is reinitialized + * or when the internal state becomes invalid. + */ + public void reset() { + x = 0; + y = 0; + inputPointerId = -1; + for (int i = 0; i < niftyOwnsDragging.length; i++) { + niftyOwnsDragging[i] = false; + } + shiftDown = false; + ctrlDown = false; + } + + /** + * @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) { + // TODO: When does nifty use this? + } + + public void beginInput() { + } + + public void endInput() { + boolean result = nifty.update(); + } + + private void handleMouseEvent(int button, boolean value, NiftyInputConsumer nic, InputEvent evt) { + if (value) { + // If nifty consumed the mouse down event, then + // it now owns the next mouse up event which + // won't be forwarded to jME3. + + // processMouseEvent doesn't return true even if cursor is above + // a nifty element (bug). + boolean consumed = nic.processMouseEvent(x, y, 0, button, true) + | nifty.getCurrentScreen().isMouseOverElement(); + niftyOwnsDragging[button] = consumed; + if (consumed) { + evt.setConsumed(); + } + //System.out.format("niftyMouse(%d, %d, %d, true) = %b\n", x, y, button, consumed); + } else { + // Forward the event if nifty owns it or if the cursor is visible. + // + // 2013-10-06: void256 was here and changed stuff ;-) Explanation: + // + // Currently Nifty remembers any mouse down event internally as "mouse button now down" regardless of it + // hitting a Nifty element. As long as it does not receive a mouse up event, Nifty will think of the mouse + // button as being pressed. + // + // The original code: + // -> if (niftyOwnsDragging[button] || inputManager.isCursorVisible()){ + // + // forwarded mouse up events to Nifty when: + // a) Nifty owns dragging, e.g. there was a mouse down event that actually hit a Nifty element before OR + // b) when the jme mouse cursor is visible. + // + // That's ok but the "Nifty remembers the mouse down event" thing had the following consequences in one + // special case: + // 1) You click on the jme scene (not Nifty) and Nifty will correctly return false (event not consumed) but + // internally it remembers: "mouse button is now down". Note that the jme mouse cursor is now hidden. + // 2) You release the mouse button but the mouse down event will not be forwarded to Nifty because it did + // owned the mouse and the jme mouse cursor is not visible. + // + // Nifty now still thinks that the mouse button is down although it's not. The result is that the next click + // on any Nifty element will not be recognized as an initial click by Nifty. So you need an additional click + // on the Nifty element to activate it correctly. In case of drag and drop this additional click was quite + // irritating. + // + // To fix that we'll now forward the mouse button up event ALWAYS to Nifty regardless of it owning the mouse + // or the jme mouse cursor visibility. + // + // Please note: Compared to the original version a side effect is that jme will now always send mouse move + // events to Nifty even when the mouse cursor is hidden. So in theory it could happen that input events are + // handled by both: jme and Nifty when f.i. you move around your scene with the mouse cursor hidden and that + // invisible cursor is moved over some Nifty element. I've not been able to reproduce that case though, + // which is good ;-) If that ever happens to someone there is an easy fix possible: + // nifty.setIgnoreMouseEvents() to completely stop Nifty from processing events. + + boolean consumed = nic.processMouseEvent(x, y, 0, button, false); + + // Only consume event if it ORIGINATED in nifty! + if (niftyOwnsDragging[button] && consumed) { + evt.setConsumed(); + processSoftKeyboard(); + } + + niftyOwnsDragging[button] = false; + //System.out.format("niftyMouse(%d, %d, %d, false) = %b\n", x, y, button, consumed); + } + } + + private void onTouchEventQueued(TouchEvent evt, NiftyInputConsumer nic) { + if (inputManager.getSimulateMouse()) { + return; + } + + x = (int) evt.getX(); + y = (int) (height - evt.getY()); + + // Input manager will not convert touch events to mouse events, + // thus we must do it ourselves.. + switch (evt.getType()) { + case DOWN: + if (inputPointerId != -1) { + // Another touch was done by the user + // while the other interacts with nifty, ignore. + break; + } + + inputPointerId = evt.getPointerId(); + handleMouseEvent(0, true, nic, evt); + + break; + case UP: + if (inputPointerId != evt.getPointerId()) { + // Another touch was done by the user + // while the other interacts with nifty, ignore. + break; + } + + inputPointerId = -1; + handleMouseEvent(0, false, nic, evt); + + break; + } + } + + private void onMouseMotionEventQueued(MouseMotionEvent evt, NiftyInputConsumer nic) { + int wheel = evt.getDeltaWheel() / 120; + x = evt.getX(); + y = height - evt.getY(); + nic.processMouseEvent(x, y, wheel, -1, false); +// if (nic.processMouseEvent(niftyEvt) /*|| nifty.getCurrentScreen().isMouseOverElement()*/){ + // Do not consume motion events + //evt.setConsumed(); +// } + } + + private void onMouseButtonEventQueued(MouseButtonEvent evt, NiftyInputConsumer nic) { + x = (int) evt.getX(); + y = (int) (height - evt.getY()); + handleMouseEvent(evt.getButtonIndex(), evt.isPressed(), nic, 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 onMouseMotionEvent(MouseMotionEvent evt) { + // Only forward the event if there's actual motion involved. + if (inputManager.isCursorVisible() && (evt.getDX() != 0 + || evt.getDY() != 0 + || evt.getDeltaWheel() != 0)) { + inputQueue.add(evt); + } + } + + public void onMouseButtonEvent(MouseButtonEvent evt) { + if (evt.getButtonIndex() >= 0 && evt.getButtonIndex() <= 2) { + if (evt.isReleased() || inputManager.isCursorVisible()) { + // Always pass mouse button release events to nifty, + // even if the mouse cursor is invisible. + inputQueue.add(evt); + } + } + } + + public void onJoyAxisEvent(JoyAxisEvent evt) { + } + + public void onJoyButtonEvent(JoyButtonEvent evt) { + } + + public void onKeyEvent(KeyInputEvent evt) { + inputQueue.add(evt); + } + + public void onTouchEvent(TouchEvent evt) { + inputQueue.add(evt); + } + + public void forwardEvents(NiftyInputConsumer nic) { + int queueSize = inputQueue.size(); + + for (int i = 0; i < queueSize; i++) { + InputEvent evt = inputQueue.get(i); + if (evt instanceof MouseMotionEvent) { + onMouseMotionEventQueued((MouseMotionEvent) evt, nic); + } else if (evt instanceof MouseButtonEvent) { + onMouseButtonEventQueued((MouseButtonEvent) evt, nic); + } else if (evt instanceof KeyInputEvent) { + onKeyEventQueued((KeyInputEvent) evt, nic); + } else if (evt instanceof TouchEvent) { + onTouchEventQueued((TouchEvent) evt, nic); + } + } + + inputQueue.clear(); + } + + private void processSoftKeyboard() { + SoftTextDialogInput softTextDialogInput = JmeSystem.getSoftTextDialogInput(); + if (softTextDialogInput != null) { + Element element = nifty.getCurrentScreen().getFocusHandler().getKeyboardFocusElement(); + if (element != null) { + final TextField textField = element.getNiftyControl(TextField.class); + if (textField != null && !(textField instanceof TextFieldNull)) { + Logger.getLogger(InputSystemJme.class.getName()).log(Level.FINE, "Current TextField: {0}", textField.getId()); + String initialValue = textField.getText(); + if (initialValue == null) { + initialValue = ""; + } + + softTextDialogInput.requestDialog(SoftTextDialogInput.TEXT_ENTRY_DIALOG, "Enter Text", initialValue, new SoftTextDialogInputListener() { + public void onSoftText(int action, String text) { + if (action == SoftTextDialogInputListener.COMPLETE) { + textField.setText(text); + } + } + }); + } + } + } + + } +} diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java new file mode 100644 index 000000000..4282c6b17 --- /dev/null +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.niftygui; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.asset.TextureKey; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.Matrix4f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; + +import de.lessvoid.nifty.batch.spi.BatchRenderBackend; +import de.lessvoid.nifty.render.BlendMode; +import de.lessvoid.nifty.spi.render.MouseCursor; +import de.lessvoid.nifty.tools.Color; +import de.lessvoid.nifty.tools.ObjectPool; +import de.lessvoid.nifty.tools.ObjectPool.Factory; +import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader; + +/** + * Nifty GUI BatchRenderBackend Implementation for jMonkeyEngine. + * @author void + */ +public class JmeBatchRenderBackend implements BatchRenderBackend { + private static Logger log = Logger.getLogger(JmeBatchRenderBackend.class.getName()); + + private final ObjectPool batchPool; + private final List batches = new ArrayList(); + + // a modify texture call needs a jme Renderer to execute. if we're called to modify a texture but don't + // have a Renderer yet - since it was not initialized on the jme side - we'll cache the modify texture calls + // in here and execute them later (at the next beginFrame() call). + private final List modifyTextureCalls = new ArrayList(); + + private RenderManager renderManager; + private NiftyJmeDisplay display; + private Texture2D textureAtlas; + private Batch currentBatch; + private Matrix4f tempMat = new Matrix4f(); + private ByteBuffer initialData; + + // this is only used for debugging purpose and will make the removed textures filled with a color + private boolean fillRemovedTexture = + Boolean.getBoolean(System.getProperty(JmeBatchRenderBackend.class.getName() + ".fillRemovedTexture", "false")); + + public JmeBatchRenderBackend(final NiftyJmeDisplay display) { + this.display = display; + this.batchPool = new ObjectPool(2, new Factory() { + @Override + public Batch createNew() { + return new Batch(); + } + }); + } + + public void setRenderManager(final RenderManager rm) { + this.renderManager = rm; + } + + @Override + public void setResourceLoader(final NiftyResourceLoader resourceLoader) { + } + + @Override + public int getWidth() { + return display.getWidth(); + } + + @Override + public int getHeight() { + return display.getHeight(); + } + + @Override + public void beginFrame() { + log.fine("beginFrame()"); + + for (int i=0; i key = new AssetKey(path); + AssetInfo info = assetManager.locateAsset(key); + if (info != null) { + return info.openStream(); + } else { + throw new AssetNotFoundException(path); + } + } + + public URL getResource(String path) { + throw new UnsupportedOperationException(); + } + } + + //Empty constructor needed for jMP to create replacement input system + public NiftyJmeDisplay() { + } + + /** + * Create a new NiftyJmeDisplay for use with the Batched Nifty Renderer (improved Nifty rendering performance). + * + * Nifty will use a single texture of the given dimensions (see atlasWidth and atlasHeight parameters). Every + * graphical asset you're rendering through Nifty will be placed into this big texture. The goal is to render + * all Nifty components in a single (or at least very few) draw calls. This should speed up rendering quite a + * bit. + * + * Currently you have to make sure to not use more image space than this single texture provides. However, Nifty + * tries to be smart about this and internally will make sure that only the images are uploaded that your GUI + * really needs. So in general this shoudln't be an issue. + * + * A complete re-organisation of the texture atlas happens when a Nifty screen ends and another begins. Dynamically + * adding images while a screen is running is supported as well. + * + * @param assetManager jME AssetManager + * @param inputManager jME InputManager + * @param audioRenderer jME AudioRenderer + * @param viewport Viewport to use + * @param atlasWidth the width of the texture atlas Nifty uses to speed up rendering (2048 is a good value) + * @param atlasHeight the height of the texture atlas Nifty uses to speed up rendering (2048 is a good value) + */ + public NiftyJmeDisplay( + final AssetManager assetManager, + final InputManager inputManager, + final AudioRenderer audioRenderer, + final ViewPort viewport, + final int atlasWidth, + final int atlasHeight){ + initialize(assetManager, inputManager, audioRenderer, viewport); + + this.renderDev = null; + this.batchRendererBackend = new JmeBatchRenderBackend(this); + + nifty = new Nifty( + new BatchRenderDevice(batchRendererBackend, atlasWidth, atlasHeight), + soundDev, + inputSys, + new TimeProvider()); + inputSys.setNifty(nifty); + + resourceLocation = new ResourceLocationJme(); + nifty.getResourceLoader().removeAllResourceLocations(); + nifty.getResourceLoader().addResourceLocation(resourceLocation); + } + + /** + * Create a standard NiftyJmeDisplay. This uses the old Nifty renderer. It's probably slower then the batched + * renderer and is mainly here for backwards compatibility. + * + * @param assetManager jME AssetManager + * @param inputManager jME InputManager + * @param audioRenderer jME AudioRenderer + * @param viewport Viewport to use + */ + public NiftyJmeDisplay(AssetManager assetManager, + InputManager inputManager, + AudioRenderer audioRenderer, + ViewPort vp){ + initialize(assetManager, inputManager, audioRenderer, vp); + + this.renderDev = new RenderDeviceJme(this); + this.batchRendererBackend = null; + + nifty = new Nifty(renderDev, soundDev, inputSys, new TimeProvider()); + inputSys.setNifty(nifty); + + resourceLocation = new ResourceLocationJme(); + nifty.getResourceLoader().removeAllResourceLocations(); + nifty.getResourceLoader().addResourceLocation(resourceLocation); + } + + private void initialize( + final AssetManager assetManager, + final InputManager inputManager, + final AudioRenderer audioRenderer, + final ViewPort viewport) { + this.assetManager = assetManager; + this.inputManager = inputManager; + this.w = viewport.getCamera().getWidth(); + this.h = viewport.getCamera().getHeight(); + this.soundDev = new SoundDeviceJme(assetManager, audioRenderer); + this.inputSys = new InputSystemJme(inputManager); + } + + public void initialize(RenderManager rm, ViewPort vp) { + this.renderManager = rm; + if (renderDev != null) { + renderDev.setRenderManager(rm); + } else { + batchRendererBackend.setRenderManager(rm); + } + + if (inputManager != null) { +// inputSys.setInputManager(inputManager); + inputManager.addRawInputListener(inputSys); + } + inited = true; + this.vp = vp; + this.renderer = rm.getRenderer(); + + inputSys.reset(); + inputSys.setHeight(vp.getCamera().getHeight()); + } + + public Nifty getNifty() { + return nifty; + } + + public void simulateKeyEvent( KeyInputEvent event ) { + inputSys.onKeyEvent(event); + } + + AssetManager getAssetManager() { + return assetManager; + } + + RenderManager getRenderManager() { + return renderManager; + } + + int getHeight() { + return h; + } + + int getWidth() { + return w; + } + + Renderer getRenderer(){ + return renderer; + } + + public void reshape(ViewPort vp, int w, int h) { + this.w = w; + this.h = h; + inputSys.setHeight(h); + nifty.resolutionChanged(); + } + + public boolean isInitialized() { + return inited; + } + + public void preFrame(float tpf) { + } + + public void postQueue(RenderQueue rq) { + // render nifty before anything else + renderManager.setCamera(vp.getCamera(), true); + //nifty.update(); + nifty.render(false); + renderManager.setCamera(vp.getCamera(), false); + } + + public void postFrame(FrameBuffer out) { + } + + public void cleanup() { + inited = false; + inputSys.reset(); + if (inputManager != null) { + inputManager.removeRawInputListener(inputSys); + } + } + +} diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderDeviceJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderDeviceJme.java new file mode 100644 index 000000000..e8ee63c06 --- /dev/null +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderDeviceJme.java @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.niftygui; + +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.scene.Geometry; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; +import de.lessvoid.nifty.elements.render.TextRenderer.RenderFontNull; +import de.lessvoid.nifty.render.BlendMode; +import de.lessvoid.nifty.spi.render.MouseCursor; +import de.lessvoid.nifty.spi.render.RenderDevice; +import de.lessvoid.nifty.spi.render.RenderFont; +import de.lessvoid.nifty.spi.render.RenderImage; +import de.lessvoid.nifty.tools.Color; +import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.HashMap; + +public class RenderDeviceJme implements RenderDevice { + + private NiftyJmeDisplay display; + private RenderManager rm; + private Renderer r; + private HashMap textCacheLastFrame = new HashMap(); + private HashMap textCacheCurrentFrame = new HashMap(); + private final Quad quad = new Quad(1, -1, true); + private final Geometry quadGeom = new Geometry("nifty-quad", quad); + private boolean clipWasSet = false; + 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(); + private RenderState renderState = new RenderState(); + + private Material colorMaterial; + private Material textureColorMaterial; + private Material vertexColorMaterial; + + private static class CachedTextKey { + + BitmapFont font; + String text; +// ColorRGBA color; + + public CachedTextKey(BitmapFont font, String text/*, ColorRGBA color*/) { + this.font = font; + this.text = text; +// this.color = color; + } + + @Override + public boolean equals(Object other) { + CachedTextKey otherKey = (CachedTextKey) other; + return font.equals(otherKey.font) && + text.equals(otherKey.text)/* && + color.equals(otherKey.color)*/; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 53 * hash + font.hashCode(); + hash = 53 * hash + text.hashCode(); +// hash = 53 * hash + color.hashCode(); + return hash; + } + } + + 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); + + // Load the 3 material types separately to avoid + // reloading the shader when the defines change. + + // Material with a single color (no texture or vertex color) + colorMaterial = new Material(display.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + + // Material with a texture and a color (no vertex color) + textureColorMaterial = new Material(display.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + + // Material with vertex color, used for gradients (no texture) + vertexColorMaterial = new Material(display.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + vertexColorMaterial.setBoolean("VertexColor", true); + + // Shared render state for all materials + renderState.setDepthTest(false); + renderState.setDepthWrite(false); + } + + public void setResourceLoader(NiftyResourceLoader niftyResourceLoader) { + } + + public void setRenderManager(RenderManager rm) { + this.rm = rm; + this.r = rm.getRenderer(); + } + + // TODO: Cursor support + public MouseCursor createMouseCursor(String str, int x, int y) { + return new MouseCursor() { + public void dispose() { + } + }; + } + + public void enableMouseCursor(MouseCursor cursor) { + } + + public void disableMouseCursor() { + } + + public RenderImage createImage(String filename, boolean linear) { + //System.out.println("createImage(" + filename + ", " + linear + ")"); + return new RenderImageJme(filename, linear, display); + } + + public RenderFont createFont(String filename) { + return new RenderFontJme(filename, display); + } + + public void beginFrame() { + } + + public void endFrame() { + HashMap temp = textCacheLastFrame; + textCacheLastFrame = textCacheCurrentFrame; + textCacheCurrentFrame = temp; + textCacheCurrentFrame.clear(); + rm.setForcedRenderState(null); + } + + public int getWidth() { + return display.getWidth(); + } + + public int getHeight() { + return display.getHeight(); + } + + public void clear() { + } + + public void setBlendMode(BlendMode blendMode) { + renderState.setBlendMode(convertBlend(blendMode)); + } + + private RenderState.BlendMode convertBlend(BlendMode blendMode) { + 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()); + } + + @Override + public void renderFont(RenderFont font, String str, int x, int y, Color color, float sizeX, float sizeY) { + if (str.length() == 0 || font instanceof RenderFontNull) { + return; + } + + RenderFontJme jmeFont = (RenderFontJme) font; + + ColorRGBA colorRgba = convertColor(color, tempColor); + CachedTextKey key = new CachedTextKey(jmeFont.getFont(), str/*, colorRgba*/); + BitmapText text = textCacheLastFrame.get(key); + if (text == null) { + text = jmeFont.createText(); + text.setText(str); + text.updateLogicalState(0); + } + textCacheCurrentFrame.put(key, text); + +// float width = text.getLineWidth(); +// float height = text.getLineHeight(); + float x0 = x; //+ 0.5f * width * (1f - sizeX); + float y0 = y; // + 0.5f * height * (1f - sizeY); + + tempMat.loadIdentity(); + tempMat.setTranslation(x0, getHeight() - y0, 0); + tempMat.setScale(sizeX, sizeY, 0); + + rm.setWorldMatrix(tempMat); + rm.setForcedRenderState(renderState); + text.setColor(colorRgba); + text.updateLogicalState(0); + text.render(rm, colorRgba); + +// System.out.format("renderFont(%s, %s, %d, %d, %s, %f, %f)\n", jmeFont.getFont(), str, x, y, color.toString(), sizeX, sizeY); + } + + 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(); + + textureColorMaterial.setColor("Color", convertColor(color, tempColor)); + textureColorMaterial.setTexture("ColorMap", texture); + + float imageWidth = jmeImage.getWidth(); + float imageHeight = jmeImage.getHeight(); + FloatBuffer texCoords = (FloatBuffer) quadModTC.getData(); + + float startX = srcX / imageWidth; + float startY = srcY / imageHeight; + float endX = startX + (srcW / imageWidth); + float endY = startY + (srcH / imageHeight); + + startY = 1f - startY; + endY = 1f - endY; + + texCoords.rewind(); + texCoords.put(startX).put(startY); + texCoords.put(endX).put(startY); + texCoords.put(endX).put(endY); + texCoords.put(startX).put(endY); + texCoords.flip(); + quadModTC.updateData(texCoords); + + quad.clearBuffer(Type.TexCoord); + quad.setBuffer(quadModTC); + + float x0 = centerX + (x - centerX) * scale; + float y0 = centerY + (y - centerY) * scale; + + tempMat.loadIdentity(); + tempMat.setTranslation(x0, getHeight() - y0, 0); + tempMat.setScale(w * scale, h * scale, 0); + + rm.setWorldMatrix(tempMat); + rm.setForcedRenderState(renderState); + textureColorMaterial.render(quadGeom, rm); + + //System.out.format("renderImage2(%s, %d, %d, %d, %d, %d, %d, %d, %d, %s, %f, %d, %d)\n", texture.getKey().toString(), + // x, y, w, h, srcX, srcY, srcW, srcH, + // color.toString(), scale, centerX, centerY); + } + + public void renderImage(RenderImage image, int x, int y, int width, int height, + Color color, float imageScale) { + + RenderImageJme jmeImage = (RenderImageJme) image; + + textureColorMaterial.setColor("Color", convertColor(color, tempColor)); + textureColorMaterial.setTexture("ColorMap", jmeImage.getTexture()); + + 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); + rm.setForcedRenderState(renderState); + textureColorMaterial.render(quadGeom, rm); + + //System.out.format("renderImage1(%s, %d, %d, %d, %d, %s, %f)\n", jmeImage.getTexture().getKey().toString(), x, y, width, height, color.toString(), imageScale); + } + + public void renderQuad(int x, int y, int width, int height, Color color) { + //We test for alpha >0 as an optimization to prevent the render of completely transparent quads. + //Nifty use layers that are often used for logical positionning and not rendering. + //each layer is rendered as a quad, but that can bump up the number of geometry rendered by a lot. + //Since we disable depth write, there is absolutely no point in rendering those quads + //This optimization can result in a huge increase of perfs on complex Nifty UIs. + if(color.getAlpha() >0){ + colorMaterial.setColor("Color", convertColor(color, tempColor)); + + tempMat.loadIdentity(); + tempMat.setTranslation(x, getHeight() - y, 0); + tempMat.setScale(width, height, 0); + + rm.setWorldMatrix(tempMat); + rm.setForcedRenderState(renderState); + colorMaterial.render(quadGeom, rm); + } + + //System.out.format("renderQuad1(%d, %d, %d, %d, %s)\n", x, y, width, height, color.toString()); + } + + 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); + + tempMat.loadIdentity(); + tempMat.setTranslation(x, getHeight() - y, 0); + tempMat.setScale(width, height, 0); + + rm.setWorldMatrix(tempMat); + rm.setForcedRenderState(renderState); + vertexColorMaterial.render(quadGeom, rm); + + //System.out.format("renderQuad2(%d, %d, %d, %d, %s, %s, %s, %s)\n", x, y, width, height, topLeft.toString(), + // topRight.toString(), + // bottomRight.toString(), + // bottomLeft.toString()); + } + + 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/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderFontJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderFontJme.java new file mode 100644 index 000000000..48a995e5b --- /dev/null +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderFontJme.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.niftygui; + +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import de.lessvoid.nifty.spi.render.RenderFont; + +public class RenderFontJme implements RenderFont { + + private NiftyJmeDisplay display; + private BitmapFont font; + private BitmapText text; + private float actualSize; + + /** + * Initialize the font. + * @param name font filename + */ + public RenderFontJme(String name, NiftyJmeDisplay display) { + this.display = display; + font = display.getAssetManager().loadFont(name); + text = new BitmapText(font); + actualSize = font.getPreferredSize(); + text.setSize(actualSize); + } + + public BitmapText createText() { + return new BitmapText(font); + } + + public BitmapFont getFont() { + return font; + } + + public BitmapText getText(){ + return text; + } + + /** + * get font height. + * @return height + */ + public int getHeight() { + return (int) text.getLineHeight(); + } + + /** + * get font width of the given string. + * @param str text + * @return width of the given text for the current font + */ + public int getWidth(final String str) { + if (str.length() == 0) { + return 0; + } + + // Note: BitmapFont is now fixed to return the proper line width + // at least for now. The older commented out (by someone else, not me) + // code below is arguably 'more accurate' if BitmapFont gets + // buggy again. The issue is that the BitmapText and BitmapFont + // use a different algorithm for calculating size and both must + // be modified in sync. + int result = (int) font.getLineWidth(str); +// text.setText(str); +// text.updateLogicalState(0); +// int result = (int) text.getLineWidth(); + + return result; + } + + public int getWidth(final String str, final float size) { + // Note: This is supposed to return the width of the String when scaled + // with the size factor. Since I don't know how to do that with + // the font rendering in jme this will only work correctly with + // a size value of 1.f and will return inaccurate values otherwise. + return getWidth(str); + } + + /** + * Return the width of the given character including kerning information. + * @param currentCharacter current character + * @param nextCharacter next character + * @param size font size + * @return width of the character or null when no information for the character is available + */ + public int getCharacterAdvance(final char currentCharacter, final char nextCharacter, final float size) { + return Math.round(font.getCharacterAdvance(currentCharacter, nextCharacter, size)); + } + + public void dispose() { + } +} diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderImageJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderImageJme.java new file mode 100644 index 000000000..5d4a132e8 --- /dev/null +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderImageJme.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.niftygui; + +import com.jme3.asset.TextureKey; +import com.jme3.texture.Image; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.Texture2D; +import de.lessvoid.nifty.spi.render.RenderImage; + +public class RenderImageJme implements RenderImage { + + private Texture2D texture; + private Image image; + private int width; + private int height; + + public RenderImageJme(String filename, boolean linear, NiftyJmeDisplay display){ + TextureKey key = new TextureKey(filename, true); + + key.setAnisotropy(0); + key.setAsCube(false); + key.setGenerateMips(false); + + texture = (Texture2D) display.getAssetManager().loadTexture(key); + texture.setMagFilter(linear ? MagFilter.Bilinear : MagFilter.Nearest); + texture.setMinFilter(linear ? MinFilter.BilinearNoMipMaps : MinFilter.NearestNoMipMaps); + image = texture.getImage(); + + width = image.getWidth(); + height = image.getHeight(); + } + + public RenderImageJme(Texture2D texture){ + if (texture.getImage() == null) { + throw new IllegalArgumentException("texture.getImage() cannot be null"); + } + + this.texture = texture; + this.image = texture.getImage(); + width = image.getWidth(); + height = image.getHeight(); + } + + public Texture2D getTexture(){ + return texture; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public void dispose() { + } +} diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundDeviceJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundDeviceJme.java new file mode 100644 index 000000000..d7eeea71d --- /dev/null +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundDeviceJme.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.niftygui; + +import com.jme3.asset.AssetManager; +import com.jme3.audio.AudioNode; +import com.jme3.audio.AudioRenderer; +import de.lessvoid.nifty.sound.SoundSystem; +import de.lessvoid.nifty.spi.sound.SoundDevice; +import de.lessvoid.nifty.spi.sound.SoundHandle; +import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader; + +public class SoundDeviceJme implements SoundDevice { + + protected AssetManager assetManager; + protected AudioRenderer ar; + + public SoundDeviceJme(AssetManager assetManager, AudioRenderer ar){ + this.assetManager = assetManager; + this.ar = ar; + } + + public void setResourceLoader(NiftyResourceLoader niftyResourceLoader) { + } + + public SoundHandle loadSound(SoundSystem soundSystem, String filename) { + AudioNode an = new AudioNode(assetManager, filename, false); + an.setPositional(false); + return new SoundHandleJme(ar, an); + } + + public SoundHandle loadMusic(SoundSystem soundSystem, String filename) { + return new SoundHandleJme(ar, assetManager, filename); + } + + public void update(int delta) { + } + +} diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundHandleJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundHandleJme.java new file mode 100644 index 000000000..97f84bc56 --- /dev/null +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundHandleJme.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.niftygui; + +import com.jme3.asset.AssetManager; +import com.jme3.audio.AudioNode; +import com.jme3.audio.AudioSource.Status; +import com.jme3.audio.AudioRenderer; +import de.lessvoid.nifty.spi.sound.SoundHandle; + +public class SoundHandleJme implements SoundHandle { + + private AudioNode node; + private AssetManager am; + private String fileName; + private float volume = 1; + + public SoundHandleJme(AudioRenderer ar, AudioNode node){ + if (ar == null || node == null) { + throw new NullPointerException(); + } + + this.node = node; + } + + /** + * For streaming music only. (May need to loop..) + * @param ar + * @param am + * @param fileName + */ + public SoundHandleJme(AudioRenderer ar, AssetManager am, String fileName){ + if (ar == null || am == null) { + throw new NullPointerException(); + } + + this.am = am; + if (fileName == null) { + throw new NullPointerException(); + } + + this.fileName = fileName; + } + + public void play() { + if (fileName != null){ + if (node != null){ + node.stop(); + } + + node = new AudioNode(am, fileName, true); + node.setPositional(false); + node.setVolume(volume); + node.play(); + }else{ + node.playInstance(); + } + } + + public void stop() { + if (node != null){ + node.stop(); + // Do not nullify the node for non-streaming nodes! + if (fileName != null) { + // Causes play() to reload the stream on the next playback + 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/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.frag b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.frag new file mode 100644 index 000000000..31f49d6e5 --- /dev/null +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.frag @@ -0,0 +1,6 @@ +uniform vec4 m_Color; + +void main() { + gl_FragColor = m_Color; +} + diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.j3md b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.j3md new file mode 100644 index 000000000..bea82f0da --- /dev/null +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.j3md @@ -0,0 +1,18 @@ +MaterialDef Default GUI { + + MaterialParameters { + Vector4 Color (Color) + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Nifty/NiftyQuad.vert + FragmentShader GLSL100: Common/MatDefs/Nifty/NiftyQuad.frag + + WorldParameters { + WorldViewProjectionMatrix + } + } + + Technique { + } +} \ No newline at end of file diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.vert b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.vert new file mode 100644 index 000000000..1eb1616c2 --- /dev/null +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.vert @@ -0,0 +1,9 @@ +uniform mat4 g_WorldViewProjectionMatrix; + +attribute vec4 inPosition; + + +void main() { + vec2 pos = (g_WorldViewProjectionMatrix * inPosition).xy; + gl_Position = vec4(pos, 0.0, 1.0); +} \ No newline at end of file diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.frag b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.frag new file mode 100644 index 000000000..1f0a645e5 --- /dev/null +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.frag @@ -0,0 +1,6 @@ +varying vec4 color; + +void main() { + gl_FragColor = color; +} + diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.j3md b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.j3md new file mode 100644 index 000000000..9412ba380 --- /dev/null +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.j3md @@ -0,0 +1,18 @@ +MaterialDef Default GUI { + + MaterialParameters { + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Nifty/NiftyQuadGrad.vert + FragmentShader GLSL100: Common/MatDefs/Nifty/NiftyQuadGrad.frag + + WorldParameters { + WorldViewProjectionMatrix + } + } + + Technique { + } + +} \ No newline at end of file diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.vert b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.vert new file mode 100644 index 000000000..e62b2b4cb --- /dev/null +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.vert @@ -0,0 +1,14 @@ +uniform mat4 g_WorldViewProjectionMatrix; + +attribute vec4 inPosition; +attribute vec4 inColor; +attribute vec4 inIndex; + +varying vec4 color; + +void main() { + vec2 pos = (g_WorldViewProjectionMatrix * inPosition).xy; + gl_Position = vec4(pos, 0.0, 1.0); + + color = inColor; +} \ No newline at end of file diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.frag b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.frag new file mode 100644 index 000000000..b2b4b95d6 --- /dev/null +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.frag @@ -0,0 +1,10 @@ +uniform sampler2D m_Texture; +uniform vec4 m_Color; + +varying vec2 texCoord; + +void main() { + vec4 texVal = texture2D(m_Texture, texCoord); + gl_FragColor = texVal * m_Color ; +} + diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.j3md b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.j3md new file mode 100644 index 000000000..8d3b91998 --- /dev/null +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.j3md @@ -0,0 +1,20 @@ +MaterialDef Default GUI { + + MaterialParameters { + Texture2D Texture + Vector4 Color (Color) + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Nifty/NiftyTex.vert + FragmentShader GLSL100: Common/MatDefs/Nifty/NiftyTex.frag + + WorldParameters { + WorldViewProjectionMatrix + } + } + + Technique { + } + +} \ No newline at end of file diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.vert b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.vert new file mode 100644 index 000000000..c5c3dc1b3 --- /dev/null +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.vert @@ -0,0 +1,13 @@ +uniform mat4 g_WorldViewProjectionMatrix; + +attribute vec4 inPosition; +attribute vec2 inTexCoord; + +varying vec2 texCoord; + +void main() { + vec2 pos = (g_WorldViewProjectionMatrix * inPosition).xy; + gl_Position = vec4(pos, 0.0, 1.0); + + texCoord = inTexCoord; +} \ No newline at end of file diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/AnimData.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/AnimData.java new file mode 100644 index 000000000..1b1eb2a9b --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/AnimData.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.ogre; + +import com.jme3.animation.Animation; +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/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MaterialLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MaterialLoader.java new file mode 100644 index 000000000..74e871b36 --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MaterialLoader.java @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.ogre; + +import com.jme3.asset.*; +import com.jme3.material.Material; +import com.jme3.material.MaterialList; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.plugins.ogre.matext.MaterialExtensionLoader; +import com.jme3.scene.plugins.ogre.matext.MaterialExtensionSet; +import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.util.PlaceholderAssets; +import com.jme3.util.blockparser.BlockLanguageParser; +import com.jme3.util.blockparser.Statement; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class MaterialLoader implements AssetLoader { + + private static final Logger logger = Logger.getLogger(MaterialLoader.class.getName()); + + private String folderName; + private AssetManager assetManager; + private ColorRGBA ambient, diffuse, specular, emissive; + private Texture[] textures = new Texture[4]; + private String texName; + private String matName; + private float shinines; + private boolean vcolor = false; + private boolean blend = false; + private boolean twoSide = false; + private boolean noLight = false; + private boolean separateTexCoord = false; + private int texUnit = 0; + + private ColorRGBA readColor(String content){ + String[] split = content.split("\\s"); + + ColorRGBA color = new ColorRGBA(); + color.r = Float.parseFloat(split[0]); + color.g = Float.parseFloat(split[1]); + color.b = Float.parseFloat(split[2]); + if (split.length >= 4){ + color.a = Float.parseFloat(split[3]); + } + return color; + } + + private void readTextureImage(String content){ + // texture image def + String path = null; + + // find extension + int extStart = content.lastIndexOf("."); + for (int i = extStart; i < content.length(); i++){ + char c = content.charAt(i); + if (Character.isWhitespace(c)){ + // extension ends here + path = content.substring(0, i).trim(); + content = content.substring(i+1).trim(); + break; + } + } + if (path == null){ + path = content.trim(); + content = ""; + } + + Scanner lnScan = new Scanner(content); + String mips = null; + String type = null; + if (lnScan.hasNext()){ + // more params + type = lnScan.next(); +// if (!lnScan.hasNext("\n") && lnScan.hasNext()){ +// mips = lnScan.next(); +// if (lnScan.hasNext()){ + // even more params.. + // will have to ignore +// } +// } + } + + boolean genMips = true; + boolean cubic = false; + if (type != null && type.equals("0")) + genMips = false; + + if (type != null && type.equals("cubic")){ + cubic = true; + } + + TextureKey texKey = new TextureKey(folderName + path, false); + texKey.setGenerateMips(genMips); + texKey.setAsCube(cubic); + + try { + Texture loadedTexture = assetManager.loadTexture(texKey); + + textures[texUnit].setImage(loadedTexture.getImage()); + textures[texUnit].setMinFilter(loadedTexture.getMinFilter()); + textures[texUnit].setKey(loadedTexture.getKey()); + + // XXX: Is this really neccessary? + textures[texUnit].setWrap(WrapMode.Repeat); + if (texName != null){ + textures[texUnit].setName(texName); + texName = null; + }else{ + textures[texUnit].setName(texKey.getName()); + } + } catch (AssetNotFoundException ex){ + logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, matName}); + textures[texUnit].setImage(PlaceholderAssets.getPlaceholderImage()); + } + } + + private void readTextureUnitStatement(Statement statement){ + String[] split = statement.getLine().split(" ", 2); + String keyword = split[0]; + if (keyword.equals("texture")){ + readTextureImage(split[1]); + }else if (keyword.equals("tex_address_mode")){ + String mode = split[1]; + if (mode.equals("wrap")){ + textures[texUnit].setWrap(WrapMode.Repeat); + }else if (mode.equals("clamp")){ + textures[texUnit].setWrap(WrapMode.Clamp); + }else if (mode.equals("mirror")){ + textures[texUnit].setWrap(WrapMode.MirroredRepeat); + }else if (mode.equals("border")){ + textures[texUnit].setWrap(WrapMode.BorderClamp); + } + }else if (keyword.equals("filtering")){ + // ignored.. only anisotropy is considered + }else if (keyword.equals("tex_coord_set")){ + int texCoord = Integer.parseInt(split[1]); + if (texCoord == 1){ + separateTexCoord = true; + } + }else if (keyword.equals("max_anisotropy")){ + int amount = Integer.parseInt(split[1]); + textures[texUnit].setAnisotropicFilter(amount); + }else{ + logger.log(Level.WARNING, "Unsupported texture_unit directive: {0}", keyword); + } + } + + private void readTextureUnit(Statement statement){ + String[] split = statement.getLine().split(" ", 2); + // name is optional + if (split.length == 2){ + texName = split[1]; + }else{ + texName = null; + } + + textures[texUnit] = new Texture2D(); + for (Statement texUnitStat : statement.getContents()){ + readTextureUnitStatement(texUnitStat); + } + if (textures[texUnit].getImage() != null){ + texUnit++; + }else{ + // no image was loaded, ignore + textures[texUnit] = null; + } + } + + private void readPassStatement(Statement statement){ + // read until newline + String[] split = statement.getLine().split(" ", 2); + String keyword = split[0]; + if (keyword.equals("diffuse")){ + if (split[1].equals("vertexcolour")){ + // use vertex colors + diffuse = ColorRGBA.White; + vcolor = true; + }else{ + diffuse = readColor(split[1]); + } + }else if(keyword.equals("ambient")) { + if (split[1].equals("vertexcolour")){ + // use vertex colors + ambient = ColorRGBA.White; + }else{ + ambient = readColor(split[1]); + } + }else if (keyword.equals("emissive")){ + emissive = readColor(split[1]); + }else if (keyword.equals("specular")){ + String[] subsplit = split[1].split("\\s"); + specular = new ColorRGBA(); + specular.r = Float.parseFloat(subsplit[0]); + specular.g = Float.parseFloat(subsplit[1]); + specular.b = Float.parseFloat(subsplit[2]); + float unknown = Float.parseFloat(subsplit[3]); + if (subsplit.length >= 5){ + // using 5 float values + specular.a = unknown; + shinines = Float.parseFloat(subsplit[4]); + }else{ + // using 4 float values + specular.a = 1f; + shinines = unknown; + } + }else if (keyword.equals("texture_unit")){ + readTextureUnit(statement); + }else if (keyword.equals("scene_blend")){ + String mode = split[1]; + if (mode.equals("alpha_blend")){ + blend = true; + } + }else if (keyword.equals("cull_hardware")){ + String mode = split[1]; + if (mode.equals("none")){ + twoSide = true; + } + }else if (keyword.equals("cull_software")){ + // ignore + }else if (keyword.equals("lighting")){ + String isOn = split[1]; + if (isOn.equals("on")){ + noLight = false; + }else if (isOn.equals("off")){ + noLight = true; + } + }else{ + logger.log(Level.WARNING, "Unsupported pass directive: {0}", keyword); + } + } + + private void readPass(Statement statement){ + String name; + String[] split = statement.getLine().split(" ", 2); + if (split.length == 1){ + // no name + name = null; + }else{ + name = split[1]; + } + + for (Statement passStat : statement.getContents()){ + readPassStatement(passStat); + } + + texUnit = 0; + } + + private void readTechnique(Statement statement){ + String[] split = statement.getLine().split(" ", 2); + String name; + if (split.length == 1){ + // no name + name = null; + }else{ + name = split[1]; + } + for (Statement techStat : statement.getContents()){ + readPass(techStat); + } + } + + private void readMaterialStatement(Statement statement){ + if (statement.getLine().startsWith("technique")){ + readTechnique(statement); + }else if (statement.getLine().startsWith("receive_shadows")){ + String isOn = statement.getLine().split("\\s")[1]; + if (isOn != null && isOn.equals("true")){ + } + } + } + + @SuppressWarnings("empty-statement") + private void readMaterial(Statement statement){ + for (Statement materialStat : statement.getContents()){ + readMaterialStatement(materialStat); + } + } + + private Material compileMaterial(){ + Material mat; + if (noLight){ + mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + }else{ + mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + } + if (blend){ + RenderState rs = mat.getAdditionalRenderState(); + rs.setAlphaTest(true); + rs.setAlphaFallOff(0.01f); + rs.setBlendMode(RenderState.BlendMode.Alpha); + + if (twoSide){ + rs.setFaceCullMode(RenderState.FaceCullMode.Off); + } + +// rs.setDepthWrite(false); + mat.setTransparent(true); + if (!noLight){ + mat.setBoolean("UseAlpha", true); + } + }else{ + if (twoSide){ + RenderState rs = mat.getAdditionalRenderState(); + rs.setFaceCullMode(RenderState.FaceCullMode.Off); + } + } + + if (!noLight){ + if (shinines > 0f) { + mat.setFloat("Shininess", shinines); + } else { + mat.setFloat("Shininess", 16f); // set shininess to some value anyway.. + } + + if (vcolor) + mat.setBoolean("UseVertexColor", true); + + if (textures[0] != null) + mat.setTexture("DiffuseMap", textures[0]); + + mat.setBoolean("UseMaterialColors", true); + if(diffuse != null){ + mat.setColor("Diffuse", diffuse); + }else{ + mat.setColor("Diffuse", ColorRGBA.White); + } + + if(ambient != null){ + mat.setColor("Ambient", ambient); + }else{ + mat.setColor("Ambient", ColorRGBA.DarkGray); + } + + if(specular != null){ + mat.setColor("Specular", specular); + }else{ + mat.setColor("Specular", ColorRGBA.Black); + } + + if (emissive != null){ + mat.setColor("GlowColor", emissive); + } + }else{ + if (vcolor) { + mat.setBoolean("VertexColor", true); + } + + if (textures[0] != null && textures[1] == null){ + if (separateTexCoord){ + mat.setTexture("LightMap", textures[0]); + mat.setBoolean("SeparateTexCoord", true); + }else{ + mat.setTexture("ColorMap", textures[0]); + } + }else if (textures[1] != null){ + mat.setTexture("ColorMap", textures[0]); + mat.setTexture("LightMap", textures[1]); + if (separateTexCoord){ + mat.setBoolean("SeparateTexCoord", true); + } + } + + if(diffuse != null){ + mat.setColor("Color", diffuse); + } + + if (emissive != null){ + mat.setColor("GlowColor", emissive); + } + } + + noLight = false; + Arrays.fill(textures, null); + diffuse = null; + specular = null; + shinines = 0f; + vcolor = false; + blend = false; + texUnit = 0; + separateTexCoord = false; + return mat; + } + + private MaterialList load(AssetManager assetManager, AssetKey key, InputStream in) throws IOException{ + folderName = key.getFolder(); + this.assetManager = assetManager; + + MaterialList list = null; + List statements = BlockLanguageParser.parse(in); + + for (Statement statement : statements){ + if (statement.getLine().startsWith("import")){ + MaterialExtensionSet matExts = null; + if (key instanceof OgreMaterialKey){ + matExts = ((OgreMaterialKey)key).getMaterialExtensionSet(); + } + + if (matExts == null){ + throw new IOException("Must specify MaterialExtensionSet when loading\n"+ + "Ogre3D materials with extended materials"); + } + + list = new MaterialExtensionLoader().load(assetManager, key, matExts, statements); + break; + }else if (statement.getLine().startsWith("material")){ + if (list == null){ + list = new MaterialList(); + } + String[] split = statement.getLine().split(" ", 2); + matName = split[1].trim(); + readMaterial(statement); + Material mat = compileMaterial(); + list.put(matName, mat); + } + } + + return list; + } + + public Object load(AssetInfo info) throws IOException { + InputStream in = null; + try { + in = info.openStream(); + return load(info.getManager(), info.getKey(), in); + } finally { + if (in != null){ + in.close(); + } + } + } + +} diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshAnimationLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshAnimationLoader.java new file mode 100644 index 000000000..3d198fd85 --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshAnimationLoader.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.ogre; + +//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/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshLoader.java new file mode 100644 index 000000000..5f2653281 --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshLoader.java @@ -0,0 +1,904 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.ogre; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.Animation; +import com.jme3.animation.SkeletonControl; +import com.jme3.asset.*; +import com.jme3.material.Material; +import com.jme3.material.MaterialList; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.*; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import com.jme3.util.PlaceholderAssets; +import static com.jme3.util.xml.SAXUtil.*; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Loads Ogre3D mesh.xml files. + */ +public class MeshLoader extends DefaultHandler implements AssetLoader { + + private static final Logger logger = Logger.getLogger(MeshLoader.class.getName()); + public static boolean AUTO_INTERLEAVE = true; + private static final Type[] TEXCOORD_TYPES = + new Type[]{ + Type.TexCoord, + Type.TexCoord2, + Type.TexCoord3, + Type.TexCoord4, + Type.TexCoord5, + Type.TexCoord6, + Type.TexCoord7, + Type.TexCoord8,}; + private AssetKey key; + private String meshName; + private String folderName; + private AssetManager assetManager; + private MaterialList materialList; + // Data per submesh/sharedgeom + private ShortBuffer sb; + private IntBuffer ib; + private FloatBuffer fb; + private VertexBuffer vb; + private Mesh mesh; + private Geometry geom; + private ByteBuffer indicesData; + private FloatBuffer weightsFloatData; + private boolean actuallyHasWeights = false; + private int vertCount; + private boolean usesSharedVerts; + private boolean usesBigIndices; + private boolean submeshNamesHack; + // Global data + private Mesh sharedMesh; + private int meshIndex = 0; + private int texCoordIndex = 0; + private String ignoreUntilEnd = null; + private List geoms = new ArrayList(); + private ArrayList usesSharedMesh = new ArrayList(); + private IntMap> lodLevels = new IntMap>(); + private AnimData animData; + + public MeshLoader() { + super(); + } + + @Override + public void startDocument() { + geoms.clear(); + lodLevels.clear(); + + sb = null; + ib = null; + fb = null; + vb = null; + mesh = null; + geom = null; + sharedMesh = null; + + usesSharedMesh.clear(); + usesSharedVerts = false; + vertCount = 0; + meshIndex = 0; + texCoordIndex = 0; + ignoreUntilEnd = null; + + animData = null; + + actuallyHasWeights = false; + submeshNamesHack = false; + indicesData = null; + weightsFloatData = null; + } + + @Override + public void endDocument() { + } + + private void pushIndex(int index) { + if (ib != null) { + ib.put(index); + } else { + sb.put((short) index); + } + } + + private void pushFace(String v1, String v2, String v3) throws SAXException { + // TODO: fan/strip support + switch (mesh.getMode()) { + case Triangles: + pushIndex(parseInt(v1)); + pushIndex(parseInt(v2)); + pushIndex(parseInt(v3)); + break; + case Lines: + pushIndex(parseInt(v1)); + pushIndex(parseInt(v2)); + break; + case Points: + pushIndex(parseInt(v1)); + break; + } + } + +// private boolean isUsingSharedVerts(Geometry geom) { + // Old code for buffer sharer + //return geom.getUserData(UserData.JME_SHAREDMESH) != null; +// } + private void startFaces(String count) throws SAXException { + int numFaces = parseInt(count); + int indicesPerFace = 0; + + switch (mesh.getMode()) { + case Triangles: + indicesPerFace = 3; + break; + case Lines: + indicesPerFace = 2; + break; + case Points: + indicesPerFace = 1; + break; + default: + throw new SAXException("Strips or fans not supported!"); + } + + int numIndices = indicesPerFace * numFaces; + + vb = new VertexBuffer(VertexBuffer.Type.Index); + if (!usesBigIndices) { + sb = BufferUtils.createShortBuffer(numIndices); + ib = null; + vb.setupData(Usage.Static, indicesPerFace, Format.UnsignedShort, sb); + } else { + ib = BufferUtils.createIntBuffer(numIndices); + sb = null; + vb.setupData(Usage.Static, indicesPerFace, Format.UnsignedInt, ib); + } + mesh.setBuffer(vb); + } + + private void applyMaterial(Geometry geom, String matName) { + Material mat = null; + if (matName.endsWith(".j3m")) { + // load as native jme3 material instance + try { + mat = assetManager.loadMaterial(matName); + } catch (AssetNotFoundException ex) { + // Warning will be raised (see below) + if (!ex.getMessage().equals(matName)) { + throw ex; + } + } + } else { + if (materialList != null) { + mat = materialList.get(matName); + } + } + + if (mat == null) { + logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{matName, key}); + mat = PlaceholderAssets.getPlaceholderMaterial(assetManager); + //mat.setKey(new MaterialKey(matName)); + } + + if (mat.isTransparent()) { + geom.setQueueBucket(Bucket.Transparent); + } + + geom.setMaterial(mat); + } + + private void startSubMesh(String matName, String usesharedvertices, String use32bitIndices, String opType) throws SAXException { + mesh = new Mesh(); + if (opType == null || opType.equals("triangle_list")) { + mesh.setMode(Mesh.Mode.Triangles); + //} else if (opType.equals("triangle_strip")) { + // mesh.setMode(Mesh.Mode.TriangleStrip); + //} else if (opType.equals("triangle_fan")) { + // mesh.setMode(Mesh.Mode.TriangleFan); + } else if (opType.equals("line_list")) { + mesh.setMode(Mesh.Mode.Lines); + } else { + throw new SAXException("Unsupported operation type: " + opType); + } + + usesBigIndices = parseBool(use32bitIndices, false); + usesSharedVerts = parseBool(usesharedvertices, false); + if (usesSharedVerts) { + usesSharedMesh.add(true); + + // Old code for buffer sharer + // import vertexbuffers from shared geom +// IntMap sharedBufs = sharedMesh.getBuffers(); +// for (Entry entry : sharedBufs) { +// mesh.setBuffer(entry.getValue()); +// } + } else { + usesSharedMesh.add(false); + } + + if (meshName == null) { + geom = new Geometry("OgreSubmesh-" + (++meshIndex), mesh); + } else { + geom = new Geometry(meshName + "-geom-" + (++meshIndex), mesh); + } + + if (usesSharedVerts) { + // Old code for buffer sharer + // this mesh is shared! + //geom.setUserData(UserData.JME_SHAREDMESH, sharedMesh); + } + + applyMaterial(geom, matName); + geoms.add(geom); + } + + private void startSharedGeom(String vertexcount) throws SAXException { + sharedMesh = new Mesh(); + vertCount = parseInt(vertexcount); + usesSharedVerts = false; + + geom = null; + mesh = sharedMesh; + } + + private void startGeometry(String vertexcount) throws SAXException { + vertCount = parseInt(vertexcount); + } + + /** + * Normalizes weights if needed and finds largest amount of weights used for + * all vertices in the buffer. + */ + private void endBoneAssigns() { +// if (mesh != sharedMesh && isUsingSharedVerts(geom)) { +// return; +// } + + if (!actuallyHasWeights) { + // No weights were actually written (the tag didn't have any entries) + // remove those buffers + mesh.clearBuffer(Type.BoneIndex); + mesh.clearBuffer(Type.BoneWeight); + + weightsFloatData = null; + indicesData = null; + + return; + } + + //int vertCount = mesh.getVertexCount(); + int maxWeightsPerVert = 0; + weightsFloatData.rewind(); + for (int v = 0; v < vertCount; v++) { + float w0 = weightsFloatData.get(), + w1 = weightsFloatData.get(), + w2 = weightsFloatData.get(), + w3 = weightsFloatData.get(); + + if (w3 != 0) { + maxWeightsPerVert = Math.max(maxWeightsPerVert, 4); + } else if (w2 != 0) { + maxWeightsPerVert = Math.max(maxWeightsPerVert, 3); + } else if (w1 != 0) { + maxWeightsPerVert = Math.max(maxWeightsPerVert, 2); + } else if (w0 != 0) { + maxWeightsPerVert = Math.max(maxWeightsPerVert, 1); + } + + float sum = w0 + w1 + w2 + w3; + if (sum != 1f) { + weightsFloatData.position(weightsFloatData.position() - 4); + // compute new vals based on sum + float sumToB = sum == 0 ? 0 : 1f / sum; + weightsFloatData.put(w0 * sumToB); + weightsFloatData.put(w1 * sumToB); + weightsFloatData.put(w2 * sumToB); + weightsFloatData.put(w3 * sumToB); + } + } + weightsFloatData.rewind(); + + actuallyHasWeights = false; + weightsFloatData = null; + indicesData = null; + + mesh.setMaxNumWeights(maxWeightsPerVert); + } + + private void startBoneAssigns() { + if (mesh != sharedMesh && usesSharedVerts) { + // will use bone assignments from shared mesh (?) + return; + } + + // current mesh will have bone assigns + //int vertCount = mesh.getVertexCount(); + // each vertex has + // - 4 bone weights + // - 4 bone indices + // create array-backed buffers for 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); + + weights.setupData(Usage.CpuOnly, 4, Format.Float, weightsFloatData); + indices.setupData(Usage.CpuOnly, 4, Format.UnsignedByte, indicesData); + + mesh.setBuffer(weights); + mesh.setBuffer(indices); + + //creating empty buffers for HW skinning + //the buffers will be setup if ever used. + VertexBuffer weightsHW = new VertexBuffer(Type.HWBoneWeight); + VertexBuffer indicesHW = new VertexBuffer(Type.HWBoneIndex); + //setting usage to cpuOnly so that the buffer is not send empty to the GPU + indicesHW.setUsage(Usage.CpuOnly); + weightsHW.setUsage(Usage.CpuOnly); + mesh.setBuffer(weightsHW); + mesh.setBuffer(indicesHW); + } + + private void startVertexBuffer(Attributes attribs) throws SAXException { + if (parseBool(attribs.getValue("positions"), false)) { + vb = new VertexBuffer(Type.Position); + fb = BufferUtils.createFloatBuffer(vertCount * 3); + vb.setupData(Usage.Static, 3, Format.Float, fb); + mesh.setBuffer(vb); + } + if (parseBool(attribs.getValue("normals"), false)) { + vb = new VertexBuffer(Type.Normal); + fb = BufferUtils.createFloatBuffer(vertCount * 3); + vb.setupData(Usage.Static, 3, Format.Float, fb); + mesh.setBuffer(vb); + } + if (parseBool(attribs.getValue("colours_diffuse"), false)) { + vb = new VertexBuffer(Type.Color); + fb = BufferUtils.createFloatBuffer(vertCount * 4); + vb.setupData(Usage.Static, 4, Format.Float, fb); + mesh.setBuffer(vb); + } + if (parseBool(attribs.getValue("tangents"), false)) { + int dimensions = parseInt(attribs.getValue("tangent_dimensions"), 3); + vb = new VertexBuffer(Type.Tangent); + fb = BufferUtils.createFloatBuffer(vertCount * dimensions); + vb.setupData(Usage.Static, dimensions, Format.Float, fb); + mesh.setBuffer(vb); + } + if (parseBool(attribs.getValue("binormals"), false)) { + vb = new VertexBuffer(Type.Binormal); + fb = BufferUtils.createFloatBuffer(vertCount * 3); + vb.setupData(Usage.Static, 3, Format.Float, fb); + mesh.setBuffer(vb); + } + + int texCoords = parseInt(attribs.getValue("texture_coords"), 0); + for (int i = 0; i < texCoords; i++) { + int dims = parseInt(attribs.getValue("texture_coord_dimensions_" + i), 2); + if (dims < 1 || dims > 4) { + throw new SAXException("Texture coord dimensions must be 1 <= dims <= 4"); + } + + if (i <= 7) { + vb = new VertexBuffer(TEXCOORD_TYPES[i]); + } else { + // more than 8 texture coordinates are not supported by ogre. + throw new SAXException("More than 8 texture coordinates not supported"); + } + fb = BufferUtils.createFloatBuffer(vertCount * dims); + vb.setupData(Usage.Static, dims, Format.Float, fb); + mesh.setBuffer(vb); + } + } + + private void startVertex() { + texCoordIndex = 0; + } + + private void pushAttrib(Type type, Attributes attribs) throws SAXException { + try { + FloatBuffer buf = (FloatBuffer) mesh.getBuffer(type).getData(); + buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z"))); + } catch (Exception ex) { + throw new SAXException("Failed to push attrib", ex); + } + } + + private void pushTangent(Attributes attribs) throws SAXException { + try { + VertexBuffer tangentBuf = mesh.getBuffer(Type.Tangent); + FloatBuffer buf = (FloatBuffer) tangentBuf.getData(); + buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z"))); + if (tangentBuf.getNumComponents() == 4) { + buf.put(parseFloat(attribs.getValue("w"))); + } + } catch (Exception ex) { + throw new SAXException("Failed to push attrib", ex); + } + } + + private void pushTexCoord(Attributes attribs) throws SAXException { + if (texCoordIndex >= 8) { + return; // More than 8 not supported by ogre. + } + Type type = TEXCOORD_TYPES[texCoordIndex]; + + VertexBuffer tcvb = mesh.getBuffer(type); + FloatBuffer buf = (FloatBuffer) tcvb.getData(); + + buf.put(parseFloat(attribs.getValue("u"))); + if (tcvb.getNumComponents() >= 2) { + buf.put(parseFloat(attribs.getValue("v"))); + if (tcvb.getNumComponents() >= 3) { + buf.put(parseFloat(attribs.getValue("w"))); + if (tcvb.getNumComponents() == 4) { + buf.put(parseFloat(attribs.getValue("x"))); + } + } + } + + texCoordIndex++; + } + + private void pushColor(Attributes attribs) throws SAXException { + FloatBuffer buf = (FloatBuffer) mesh.getBuffer(Type.Color).getData(); + String value = parseString(attribs.getValue("value")); + String[] vals = value.split("\\s"); + if (vals.length != 3 && vals.length != 4) { + throw new SAXException("Color value must contain 3 or 4 components"); + } + + ColorRGBA color = new ColorRGBA(); + color.r = parseFloat(vals[0]); + color.g = parseFloat(vals[1]); + color.b = parseFloat(vals[2]); + if (vals.length == 3) { + color.a = 1f; + } else { + color.a = parseFloat(vals[3]); + } + + buf.put(color.r).put(color.g).put(color.b).put(color.a); + } + + private void startLodFaceList(String submeshindex, String numfaces) { + int index = Integer.parseInt(submeshindex); + mesh = geoms.get(index).getMesh(); + int faceCount = Integer.parseInt(numfaces); + + VertexBuffer originalIndexBuffer = mesh.getBuffer(Type.Index); + vb = new VertexBuffer(VertexBuffer.Type.Index); + if (originalIndexBuffer.getFormat() == Format.UnsignedInt) { + // LOD buffer should also be integer + ib = BufferUtils.createIntBuffer(faceCount * 3); + sb = null; + vb.setupData(Usage.Static, 3, Format.UnsignedInt, ib); + } else { + sb = BufferUtils.createShortBuffer(faceCount * 3); + ib = null; + vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb); + } + + List levels = lodLevels.get(index); + if (levels == null) { + // Create the LOD levels list + levels = new ArrayList(); + + // Add the first LOD level (always the original index buffer) + levels.add(originalIndexBuffer); + lodLevels.put(index, levels); + } + levels.add(vb); + } + + private void startLevelOfDetail(String numlevels) { +// numLevels = Integer.parseInt(numlevels); + } + + private void endLevelOfDetail() { + // set the lod data for each mesh + for (Entry> 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) { + } + + private void pushBoneAssign(String vertIndex, String boneIndex, String weight) throws SAXException { + int vert = parseInt(vertIndex); + float w = parseFloat(weight); + byte bone = (byte) parseInt(boneIndex); + + assert bone >= 0; + assert vert >= 0 && vert < mesh.getVertexCount(); + + int i; + float v = 0; + // see which weights are unused for a given bone + for (i = vert * 4; i < vert * 4 + 4; i++) { + v = weightsFloatData.get(i); + if (v == 0) { + break; + } + } + if (v != 0) { + logger.log(Level.WARNING, "Vertex {0} has more than 4 weights per vertex! Ignoring..", vert); + return; + } + + weightsFloatData.put(i, w); + indicesData.put(i, bone); + actuallyHasWeights = true; + } + + private void startSkeleton(String name) { + AssetKey assetKey = new AssetKey(folderName + name + ".xml"); + try { + animData = (AnimData) assetManager.loadAsset(assetKey); + } catch (AssetNotFoundException ex) { + logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{assetKey, key}); + animData = null; + } + } + + private void startSubmeshName(String indexStr, String nameStr) { + int index = Integer.parseInt(indexStr); + if (index >= geoms.size()) { + logger.log(Level.WARNING, "Submesh name index is larger than number of geometries: {0} >= {1}", + new Object[]{index, geoms.size()}); + } else { + 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")) { + if (submeshNamesHack) { + // Hack for blender2ogre only + startSubmeshName(attribs.getValue("index"), attribs.getValue("name")); + } else { + startSubMesh(attribs.getValue("material"), + attribs.getValue("usesharedvertices"), + attribs.getValue("use32bitindexes"), + attribs.getValue("operationtype")); + } + } else if (qName.equals("sharedgeometry")) { + String count = attribs.getValue("vertexcount"); + if (count == null) { + count = attribs.getValue("count"); + } + + if (count != null && !count.equals("0")) { + startSharedGeom(count); + } + } else if (qName.equals("submeshes")) { + // ok + } else if (qName.equals("skeletonlink")) { + startSkeleton(attribs.getValue("name")); + } else if (qName.equals("submeshnames")) { + // ok + // setting submeshNamesHack to true will make "submesh" tag be interpreted + // as a "submeshname" tag. + submeshNamesHack = true; + } 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 submesh hack is enabled, ignore any submesh/submeshes + // end tags. + if (qName.equals("submesh") && !submeshNamesHack) { + usesBigIndices = false; + geom = null; + mesh = null; + } else if (qName.equals("submeshes") && !submeshNamesHack) { + // IMPORTANT: restore sharedmesh, for use with shared boneweights + geom = null; + mesh = sharedMesh; + usesSharedVerts = false; + } else if (qName.equals("faces")) { + if (ib != null) { + ib.flip(); + } else { + sb.flip(); + } + + vb = null; + ib = null; + sb = null; + } else if (qName.equals("vertexbuffer")) { + fb = null; + vb = null; + } else if (qName.equals("geometry") + || qName.equals("sharedgeometry")) { + // finish writing to buffers + for (VertexBuffer buf : mesh.getBufferList().getArray()) { + Buffer data = buf.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(); + } else if (qName.equals("submeshnames")) { + // Restore default handling for "submesh" tag. + submeshNamesHack = false; + } + } + + @Override + public void characters(char ch[], int start, int length) { + } + + private Node compileModel() { + Node model = new Node(meshName + "-ogremesh"); + + for (int i = 0; i < geoms.size(); i++) { + Geometry g = geoms.get(i); + Mesh m = g.getMesh(); + + // New code for buffer extract + if (sharedMesh != null && usesSharedMesh.get(i)) { + m.extractVertexData(sharedMesh); + } + + model.attachChild(geoms.get(i)); + } + + // Do not attach shared geometry to the node! + + if (animData != null) { + // This model uses animation + + for (int i = 0; i < geoms.size(); i++) { + Geometry g = geoms.get(i); + Mesh m = geoms.get(i).getMesh(); + + //FIXME the parameter is now useless. + //It was !HADWARE_SKINNING before, but since toggleing + //HW skinning does not happen at load time it was always true. + //We should use something similar as for the HWBoneIndex and + //HWBoneWeight : create the vertex buffers empty so that they + //are put in the cache, and really populate them the first time + //software skinning is used on the mesh. + m.generateBindPose(true); + + } + + // Put the animations in the AnimControl + HashMap anims = new HashMap(); + ArrayList animList = animData.anims; + for (int i = 0; i < animList.size(); i++) { + Animation anim = animList.get(i); + anims.put(anim.getName(), anim); + } + + AnimControl ctrl = new AnimControl(animData.skeleton); + ctrl.setAnimations(anims); + model.addControl(ctrl); + + // Put the skeleton in the skeleton control + SkeletonControl skeletonControl = new SkeletonControl(animData.skeleton); + + // This will acquire the targets from the node + model.addControl(skeletonControl); + } + + return model; + } + + public Object load(AssetInfo info) throws IOException { + try { + key = info.getKey(); + meshName = key.getName(); + folderName = key.getFolder(); + String ext = key.getExtension(); + meshName = meshName.substring(0, meshName.length() - ext.length() - 1); + if (folderName != null && folderName.length() > 0) { + meshName = meshName.substring(folderName.length()); + } + assetManager = info.getManager(); + + if (key instanceof OgreMeshKey) { + // OgreMeshKey is being used, try getting the material list + // from it + OgreMeshKey meshKey = (OgreMeshKey) key; + materialList = meshKey.getMaterialList(); + String materialName = meshKey.getMaterialName(); + + // Material list not set but material name is available + if (materialList == null && materialName != null) { + OgreMaterialKey materialKey = new OgreMaterialKey(folderName + materialName + ".material"); + try { + materialList = (MaterialList) assetManager.loadAsset(materialKey); + } catch (AssetNotFoundException e) { + logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{materialKey, key}); + } + } + } else { + // Make sure to reset it to null so that previous state + // doesn't leak onto this one + materialList = null; + } + + // If for some reason material list could not be found through + // OgreMeshKey, or if regular ModelKey specified, load using + // default method. + if (materialList == null) { + OgreMaterialKey materialKey = new OgreMaterialKey(folderName + meshName + ".material"); + try { + materialList = (MaterialList) assetManager.loadAsset(materialKey); + } catch (AssetNotFoundException e) { + logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{materialKey, key}); + } + } + + // Added by larynx 25.06.2011 + // Android needs the namespace aware flag set to true + // Kirill 30.06.2011 + // Now, hack is applied for both desktop and android to avoid + // checking with JmeSystem. + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + + XMLReader xr = factory.newSAXParser().getXMLReader(); + xr.setContentHandler(this); + xr.setErrorHandler(this); + + InputStreamReader r = null; + try { + r = new InputStreamReader(info.openStream()); + xr.parse(new InputSource(r)); + } finally { + if (r != null) { + r.close(); + } + } + + return compileModel(); + } catch (SAXException ex) { + IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml"); + ioEx.initCause(ex); + throw ioEx; + } catch (ParserConfigurationException ex) { + IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml"); + ioEx.initCause(ex); + throw ioEx; + } + + } +} diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/OgreMeshKey.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/OgreMeshKey.java new file mode 100644 index 000000000..d0458899b --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/OgreMeshKey.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.ogre; + +import com.jme3.asset.ModelKey; +import com.jme3.material.MaterialList; + +/** + * OgreMeshKey is used to load Ogre3D mesh.xml models with a specific + * material file or list. This allows customizing from where the materials + * are retrieved, instead of loading the material file as the same + * name as the model (the default). + * + * @author Kirill Vainer + */ +public class OgreMeshKey extends ModelKey { + + private MaterialList materialList; + private String materialName; + + public OgreMeshKey(){ + super(); + } + + public OgreMeshKey(String name){ + super(name); + } + + public OgreMeshKey(String name, MaterialList materialList){ + super(name); + this.materialList = materialList; + } + + public OgreMeshKey(String name, String materialName){ + super(name); + this.materialName = materialName; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final OgreMeshKey other = (OgreMeshKey) obj; + if (!super.equals(other)) { + return false; + } + if (this.materialList != other.materialList && (this.materialList == null || !this.materialList.equals(other.materialList))) { + return false; + } + if ((this.materialName == null) ? (other.materialName != null) : !this.materialName.equals(other.materialName)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 31 * hash + (super.hashCode()); + hash = 31 * hash + (this.materialList != null ? this.materialList.hashCode() : 0); + hash = 31 * hash + (this.materialName != null ? this.materialName.hashCode() : 0); + return hash; + } + + public MaterialList getMaterialList() { + return materialList; + } + + public void setMaterialList(MaterialList materialList){ + this.materialList = materialList; + } + + public String getMaterialName() { + return materialName; + } + + public void setMaterialName(String name) { + materialName = name; + } + +} diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneLoader.java new file mode 100644 index 000000000..a15104e1b --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneLoader.java @@ -0,0 +1,550 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.ogre; + +import com.jme3.asset.*; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.material.MaterialList; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.CameraNode; +import com.jme3.scene.LightNode; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.CameraControl.ControlDirection; +import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey; +import com.jme3.util.PlaceholderAssets; +import com.jme3.util.xml.SAXUtil; +import static com.jme3.util.xml.SAXUtil.*; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Stack; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +public class SceneLoader extends DefaultHandler implements AssetLoader { + + private static final int DEFAULT_CAM_WIDTH = 640; + private static final int DEFAULT_CAM_HEIGHT = 480; + + private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); + private SceneMaterialLoader materialLoader = new SceneMaterialLoader(); + private Stack elementStack = new Stack(); + private AssetKey key; + private String sceneName; + private String folderName; + private AssetManager assetManager; + private MaterialList materialList; + private com.jme3.scene.Node root; + private com.jme3.scene.Node node; + private com.jme3.scene.Node entityNode; + private Light light; + private Camera camera; + private CameraNode cameraNode; + private int nodeIdx = 0; + private static volatile int sceneIdx = 0; + + public SceneLoader() { + super(); + } + + @Override + public void startDocument() { + } + + @Override + public void endDocument() { + } + + private void reset() { + elementStack.clear(); + nodeIdx = 0; + + // NOTE: Setting some of those to null is only needed + // if the parsed file had an error e.g. startElement was called + // but not endElement + root = null; + node = null; + entityNode = null; + light = null; + camera = null; + cameraNode = null; + } + + private void checkTopNode(String topNode) throws SAXException { + if (!elementStack.peek().equals(topNode)) { + throw new SAXException("dotScene parse error: Expected parent node to be " + topNode); + } + } + + private Quaternion parseQuat(Attributes attribs) throws SAXException { + if (attribs.getValue("x") != null) { + // defined as quaternion + float x = parseFloat(attribs.getValue("x")); + float y = parseFloat(attribs.getValue("y")); + float z = parseFloat(attribs.getValue("z")); + float w = parseFloat(attribs.getValue("w")); + return new Quaternion(x, y, z, w); + } else if (attribs.getValue("qx") != null) { + // defined as quaternion with prefix "q" + float x = parseFloat(attribs.getValue("qx")); + float y = parseFloat(attribs.getValue("qy")); + float z = parseFloat(attribs.getValue("qz")); + float w = parseFloat(attribs.getValue("qw")); + return new Quaternion(x, y, z, w); + } else if (attribs.getValue("angle") != null) { + // defined as angle + axis + float angle = parseFloat(attribs.getValue("angle")); + float axisX = parseFloat(attribs.getValue("axisX")); + float axisY = parseFloat(attribs.getValue("axisY")); + float axisZ = parseFloat(attribs.getValue("axisZ")); + Quaternion q = new Quaternion(); + q.fromAngleAxis(angle, new Vector3f(axisX, axisY, axisZ)); + return q; + } else { + // defines as 3 angles along XYZ axes + float angleX = parseFloat(attribs.getValue("angleX")); + float angleY = parseFloat(attribs.getValue("angleY")); + float angleZ = parseFloat(attribs.getValue("angleZ")); + Quaternion q = new Quaternion(); + q.fromAngles(angleX, angleY, angleZ); + return q; + } + } + + private void parseLightNormal(Attributes attribs) throws SAXException { + checkTopNode("light"); + + // SpotLight will be supporting a direction-normal, too. + if (light instanceof DirectionalLight) { + ((DirectionalLight) light).setDirection(parseVector3(attribs)); + } else if (light instanceof SpotLight) { + ((SpotLight) light).setDirection(parseVector3(attribs)); + } + } + + private void parseLightAttenuation(Attributes attribs) throws SAXException { + // NOTE: Derives range based on "linear" if it is used solely + // for the attenuation. Otherwise derives it from "range" + checkTopNode("light"); + + if (light instanceof PointLight || light instanceof SpotLight) { + float range = parseFloat(attribs.getValue("range")); + float constant = parseFloat(attribs.getValue("constant")); + float linear = parseFloat(attribs.getValue("linear")); + + String quadraticStr = attribs.getValue("quadratic"); + if (quadraticStr == null) { + quadraticStr = attribs.getValue("quadric"); + } + + float quadratic = parseFloat(quadraticStr); + + if (constant == 1 && quadratic == 0 && linear > 0) { + range = 1f / linear; + } + + if (light instanceof PointLight) { + ((PointLight) light).setRadius(range); + } else { + ((SpotLight) light).setSpotRange(range); + } + } + } + + private void parseLightSpotLightRange(Attributes attribs) throws SAXException { + checkTopNode("light"); + + float outer = SAXUtil.parseFloat(attribs.getValue("outer")); + float inner = SAXUtil.parseFloat(attribs.getValue("inner")); + + if (!(light instanceof SpotLight)) { + throw new SAXException("dotScene parse error: spotLightRange " + + "can only appear under 'spot' light elements"); + } + + SpotLight sl = (SpotLight) light; + sl.setSpotInnerAngle(inner * 0.5f); + sl.setSpotOuterAngle(outer * 0.5f); + } + + private void parseLight(Attributes attribs) throws SAXException { + if (node == null || node.getParent() == null) { + throw new SAXException("dotScene parse error: light can only appear under a node"); + } + + checkTopNode("node"); + + String lightType = parseString(attribs.getValue("type"), "point"); + if (lightType.equals("point")) { + light = new PointLight(); + } else if (lightType.equals("directional") || lightType.equals("sun")) { + light = new DirectionalLight(); + // Assuming "normal" property is not provided + ((DirectionalLight) light).setDirection(Vector3f.UNIT_Z); + } else if (lightType.equals("spotLight") || lightType.equals("spot")) { + light = new SpotLight(); + } else if (lightType.equals("omni")) { + // XXX: It doesn't seem any exporters actually emit this type? + light = new AmbientLight(); + } 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); + } + } + + private void parseCameraClipping(Attributes attribs) throws SAXException { + if (attribs.getValue("near") != null) { + camera.setFrustumNear(SAXUtil.parseFloat(attribs.getValue("near"))); + camera.setFrustumFar(SAXUtil.parseFloat(attribs.getValue("far"))); + } else { + camera.setFrustumNear(SAXUtil.parseFloat(attribs.getValue("nearPlaneDist"))); + camera.setFrustumFar(SAXUtil.parseFloat(attribs.getValue("farPlaneDist"))); + } + } + + private void parseCamera(Attributes attribs) throws SAXException { + camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT); + if (SAXUtil.parseString(attribs.getValue("projectionType"), "perspective").equals("parallel")){ + camera.setParallelProjection(true); + } + float fov = SAXUtil.parseFloat(attribs.getValue("fov"), 45f); + if (fov < FastMath.PI) { + // XXX: Most likely, it is in radians.. + fov = fov * FastMath.RAD_TO_DEG; + } + camera.setFrustumPerspective(fov, (float)DEFAULT_CAM_WIDTH / DEFAULT_CAM_HEIGHT, 1, 1000); + + cameraNode = new CameraNode(attribs.getValue("name"), camera); + cameraNode.setControlDir(ControlDirection.SpatialToCamera); + + node.attachChild(cameraNode); + node = null; + } + + private void parseEntity(Attributes attribs) throws SAXException { + String name = attribs.getValue("name"); + if (name == null) { + name = "OgreEntity-" + (++nodeIdx); + } else { + name += "-entity"; + } + + String meshFile = attribs.getValue("meshFile"); + if (meshFile == null) { + throw new SAXException("Required attribute 'meshFile' missing for 'entity' node"); + } + + // TODO: Not currently used + String materialName = attribs.getValue("materialName"); + + if (folderName != null) { + meshFile = folderName + meshFile; + } + + // NOTE: append "xml" since its assumed mesh files are binary in dotScene + meshFile += ".xml"; + + entityNode = new com.jme3.scene.Node(name); + OgreMeshKey meshKey = new OgreMeshKey(meshFile, materialList); + try { + Spatial ogreMesh = assetManager.loadModel(meshKey); + entityNode.attachChild(ogreMesh); + } catch (AssetNotFoundException ex) { + if (ex.getMessage().equals(meshFile)) { + logger.log(Level.WARNING, "Cannot locate {0} for scene {1}", new Object[]{meshKey, key}); + // Attach placeholder asset. + Spatial model = PlaceholderAssets.getPlaceholderModel(assetManager); + model.setKey(key); + entityNode.attachChild(model); + } else { + throw ex; + } + } + + node.attachChild(entityNode); + node = null; + } + + private void parseNode(Attributes attribs) throws SAXException { + String name = attribs.getValue("name"); + if (name == null) { + name = "OgreNode-" + (++nodeIdx); + } + + com.jme3.scene.Node newNode = new com.jme3.scene.Node(name); + if (node != null) { + node.attachChild(newNode); + } + node = newNode; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException { + if (qName.equals("scene")) { + if (elementStack.size() != 0) { + throw new SAXException("dotScene parse error: 'scene' element must be the root XML element"); + } + + String version = attribs.getValue("formatVersion"); + if (version == null || (!version.equals("1.0.0") && !version.equals("1.0.1"))) { + logger.log(Level.WARNING, "Unrecognized version number" + + " in dotScene file: {0}", version); + } + } else if (qName.equals("nodes")) { + if (root != null) { + throw new SAXException("dotScene parse error: nodes element was specified twice"); + } + if (sceneName == null) { + root = new com.jme3.scene.Node("OgreDotScene" + (++sceneIdx)); + } else { + root = new com.jme3.scene.Node(sceneName + "-scene_node"); + } + + node = root; + } else if (qName.equals("externals")) { + checkTopNode("scene"); + } else if (qName.equals("item")) { + checkTopNode("externals"); + } else if (qName.equals("file")) { + checkTopNode("item"); + + // NOTE: This part of the file is ignored, it is parsed + // by SceneMaterialLoader in the first pass. + } else if (qName.equals("node")) { + String curElement = elementStack.peek(); + if (!curElement.equals("node") && !curElement.equals("nodes")) { + throw new SAXException("dotScene parse error: " + + "node element can only appear under 'node' or 'nodes'"); + } + + parseNode(attribs); + } else if (qName.equals("property")) { + if (node != null) { + String type = attribs.getValue("type"); + String name = attribs.getValue("name"); + String data = attribs.getValue("data"); + if (type.equals("BOOL")) { + node.setUserData(name, Boolean.parseBoolean(data) || data.equals("1")); + } else if (type.equals("FLOAT")) { + node.setUserData(name, Float.parseFloat(data)); + } else if (type.equals("STRING")) { + node.setUserData(name, data); + } else if (type.equals("INT")) { + node.setUserData(name, Integer.parseInt(data)); + } + } + } else if (qName.equals("entity")) { + checkTopNode("node"); + parseEntity(attribs); + } else if (qName.equals("camera")) { + checkTopNode("node"); + parseCamera(attribs); + } else if (qName.equals("clipping")) { + checkTopNode("camera"); + parseCameraClipping(attribs); + } else if (qName.equals("position")) { + if (elementStack.peek().equals("node")) { + node.setLocalTranslation(SAXUtil.parseVector3(attribs)); + } else if (elementStack.peek().equals("camera")) { + cameraNode.setLocalTranslation(SAXUtil.parseVector3(attribs)); + } + } else if (qName.equals("quaternion") || qName.equals("rotation")) { + node.setLocalRotation(parseQuat(attribs)); + } else if (qName.equals("scale")) { + node.setLocalScale(SAXUtil.parseVector3(attribs)); + } else if (qName.equals("light")) { + parseLight(attribs); + } else if (qName.equals("colourDiffuse") || qName.equals("colorDiffuse")) { + if (elementStack.peek().equals("light")) { + if (light != null) { + light.setColor(parseColor(attribs)); + } + } else { + checkTopNode("environment"); + } + } else if (qName.equals("colourAmbient") || qName.equals("colorAmbient")) { + if (elementStack.peek().equals("environment")) { + ColorRGBA color = parseColor(attribs); + if (!color.equals(ColorRGBA.Black) && !color.equals(ColorRGBA.BlackNoAlpha)) { + // Lets add an ambient light to the scene. + AmbientLight al = new AmbientLight(); + al.setColor(color); + root.addLight(al); + } + } + } else if (qName.equals("normal") || qName.equals("direction")) { + checkTopNode("light"); + parseLightNormal(attribs); + } else if (qName.equals("lightAttenuation")) { + parseLightAttenuation(attribs); + } else if (qName.equals("spotLightRange") || qName.equals("lightRange")) { + parseLightSpotLightRange(attribs); + } + + elementStack.push(qName); + } + + @Override + public void endElement(String uri, String name, String qName) throws SAXException { + if (qName.equals("node")) { + node = node.getParent(); + } else if (qName.equals("nodes")) { + node = null; + } else if (qName.equals("entity")) { + node = entityNode.getParent(); + entityNode = null; + } else if (qName.equals("camera")) { + node = cameraNode.getParent(); + cameraNode = null; + } else if (qName.equals("light")) { + // apply the node's world transform on the light.. + root.updateGeometricState(); + if (light != null) { + if (light instanceof DirectionalLight) { + DirectionalLight dl = (DirectionalLight) light; + Quaternion q = node.getWorldRotation(); + Vector3f dir = dl.getDirection(); + q.multLocal(dir); + dl.setDirection(dir); + } else if (light instanceof PointLight) { + PointLight pl = (PointLight) light; + Vector3f pos = node.getWorldTranslation(); + pl.setPosition(pos); + } else if (light instanceof SpotLight) { + SpotLight sl = (SpotLight) light; + + Vector3f pos = node.getWorldTranslation(); + sl.setPosition(pos); + + Quaternion q = node.getWorldRotation(); + Vector3f dir = sl.getDirection(); + q.multLocal(dir); + sl.setDirection(dir); + } + } + light = null; + } + checkTopNode(qName); + elementStack.pop(); + } + + @Override + public void characters(char ch[], int start, int length) { + } + + public Object load(AssetInfo info) throws IOException { + try { + key = info.getKey(); + assetManager = info.getManager(); + sceneName = key.getName(); + String ext = key.getExtension(); + folderName = key.getFolder(); + sceneName = sceneName.substring(0, sceneName.length() - ext.length() - 1); + + reset(); + + // == Run 1st pass over XML file to determine material list == + materialList = materialLoader.load(assetManager, folderName, info.openStream()); + + if (materialList == null || materialList.isEmpty()) { + // NOTE: No materials were found by searching the externals section. + // Try finding a similarly named material file in the same folder. + // (Backward compatibility only!) + OgreMaterialKey materialKey = new OgreMaterialKey(sceneName + ".material"); + try { + materialList = (MaterialList) assetManager.loadAsset(materialKey); + } catch (AssetNotFoundException ex) { + logger.log(Level.WARNING, "Cannot locate {0} for scene {1}", new Object[]{materialKey, key}); + materialList = null; + } + } + + // == Run 2nd pass to load entities and other objects == + + // Added by larynx 25.06.2011 + // Android needs the namespace aware flag set to true + // Kirill 30.06.2011 + // Now, hack is applied for both desktop and android to avoid + // checking with JmeSystem. + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + XMLReader xr = factory.newSAXParser().getXMLReader(); + + xr.setContentHandler(this); + xr.setErrorHandler(this); + + InputStreamReader r = null; + + try { + r = new InputStreamReader(info.openStream()); + xr.parse(new InputSource(r)); + } finally { + if (r != null) { + r.close(); + } + } + + return root; + } catch (SAXException ex) { + IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); + ioEx.initCause(ex); + throw ioEx; + } catch (ParserConfigurationException ex) { + IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); + ioEx.initCause(ex); + throw ioEx; + } + } +} diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMaterialLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMaterialLoader.java new file mode 100644 index 000000000..3507de2f7 --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMaterialLoader.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.ogre; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.material.MaterialList; +import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Stack; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +/** + * This is a utility class to load a {@link MaterialList} from a + * .scene file. It is only needed because the parsing method + * used by the SceneLoader doesn't support reading bottom XML nodes + * before reading the top nodes. + * + * @author Kirill Vainer + */ +class SceneMaterialLoader extends DefaultHandler { + + private static final Logger logger = Logger.getLogger(SceneMaterialLoader.class.getName()); + private Stack elementStack = new Stack(); + private String folderName; + private MaterialList materialList; + private AssetManager assetManager; + private boolean ignoreItem = false; + + private void reset(){ + elementStack.clear(); + materialList = null; + ignoreItem = false; + } + + private void checkTopNode(String topNode) throws SAXException{ + if (!elementStack.peek().equals(topNode)){ + throw new SAXException("dotScene parse error: Expected parent node to be " + topNode); + } + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException { + if (qName.equals("externals")) { + checkTopNode("scene"); + + // Has an externals block, create material list. + materialList = new MaterialList(); + } else if (qName.equals("item")) { + checkTopNode("externals"); + if (!attribs.getValue("type").equals("material")) { + // This is not a material external. Ignore it. + ignoreItem = true; + } + } else if (qName.equals("file")) { + checkTopNode("item"); + + if (!ignoreItem) { + String materialPath = attribs.getValue("name"); + String materialName = new File(materialPath).getName(); + String matFile = folderName + materialName; + try { + MaterialList loadedMaterialList = (MaterialList) assetManager.loadAsset(new OgreMaterialKey(matFile)); + materialList.putAll(loadedMaterialList); + } catch (AssetNotFoundException ex) { + logger.log(Level.WARNING, "Cannot locate material file: {0}", matFile); + } + } + } + elementStack.push(qName); + } + + @Override + public void endElement(String uri, String name, String qName) throws SAXException { + if (qName.equals("item") && ignoreItem) { + ignoreItem = false; + } + checkTopNode(qName); + elementStack.pop(); + } + + public MaterialList load(AssetManager assetManager, String folderName, InputStream in) throws IOException { + try { + this.assetManager = assetManager; + this.folderName = folderName; + + reset(); + + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + XMLReader xr = factory.newSAXParser().getXMLReader(); + + xr.setContentHandler(this); + xr.setErrorHandler(this); + + InputStreamReader r = null; + + try { + r = new InputStreamReader(in); + xr.parse(new InputSource(r)); + } finally { + if (r != null){ + r.close(); + } + } + + return materialList; + } catch (SAXException ex) { + IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); + ioEx.initCause(ex); + throw ioEx; + } catch (ParserConfigurationException ex) { + IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); + ioEx.initCause(ex); + throw ioEx; + } + } +} diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java new file mode 100644 index 000000000..56dc95bb6 --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.ogre; + +import com.jme3.animation.Animation; +import com.jme3.animation.Bone; +import com.jme3.animation.BoneTrack; +import com.jme3.animation.Skeleton; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetManager; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.util.xml.SAXUtil; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; +import java.util.logging.Logger; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +public class SkeletonLoader extends DefaultHandler implements AssetLoader { + + private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); + private AssetManager assetManager; + private Stack elementStack = new Stack(); + private HashMap indexToBone = new HashMap(); + private HashMap nameToBone = new HashMap(); + private BoneTrack track; + private ArrayList tracks = new ArrayList(); + private Animation 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 ArrayList scales = 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 Animation(name, length); + } else if (qName.equals("bonehierarchy")) { + assert elementStack.peek().equals("skeleton"); + } else if (qName.equals("animations")) { + assert elementStack.peek().equals("skeleton"); + animations = new ArrayList(); + } else if (qName.equals("bones")) { + assert elementStack.peek().equals("skeleton"); + } else if (qName.equals("skeleton")) { + assert elementStack.size() == 0; + } + elementStack.add(qName); + } + + public void endElement(String uri, String name, String qName) { + if (qName.equals("translate") || qName.equals("position") || qName.equals("scale")) { + } else if (qName.equals("axis")) { + } else if (qName.equals("rotate") || qName.equals("rotation")) { + rotation = new Quaternion(); + axis.normalizeLocal(); + rotation.fromAngleNormalAxis(angle, axis); + angle = 0; + axis = null; + } else if (qName.equals("bone")) { + bone.setBindTransforms(position, rotation, scale); + bone = null; + position = null; + rotation = null; + scale = null; + } else if (qName.equals("bonehierarchy")) { + Bone[] bones = new Bone[indexToBone.size()]; + // find bones without a parent and attach them to the skeleton + // also assign the bones to the bonelist + for (Map.Entry entry : indexToBone.entrySet()) { + Bone bone = entry.getValue(); + bones[entry.getKey()] = bone; + } + indexToBone.clear(); + skeleton = new Skeleton(bones); + } else if (qName.equals("animation")) { + animations.add(animation); + animation = null; + } else if (qName.equals("track")) { + if (track != null) { // if track has keyframes + tracks.add(track); + track = null; + } + } else if (qName.equals("tracks")) { + BoneTrack[] trackList = tracks.toArray(new BoneTrack[tracks.size()]); + animation.setTracks(trackList); + tracks.clear(); + } else if (qName.equals("keyframe")) { + assert time >= 0; + assert position != null; + assert rotation != null; + + times.add(time); + translations.add(position); + rotations.add(rotation); + if (scale != null) { + scales.add(scale); + }else{ + scales.add(new Vector3f(1,1,1)); + } + + time = -1; + position = null; + rotation = null; + scale = null; + } else if (qName.equals("keyframes")) { + if (times.size() > 0) { + float[] timesArray = new float[times.size()]; + for (int i = 0; i < timesArray.length; i++) { + timesArray[i] = times.get(i); + } + + Vector3f[] transArray = translations.toArray(new Vector3f[translations.size()]); + Quaternion[] rotArray = rotations.toArray(new Quaternion[rotations.size()]); + Vector3f[] scalesArray = scales.toArray(new Vector3f[scales.size()]); + + track.setKeyframes(timesArray, transArray, rotArray, scalesArray); + //track.setKeyframes(timesArray, transArray, rotArray); + } else { + track = null; + } + + times.clear(); + translations.clear(); + rotations.clear(); + scales.clear(); + } else if (qName.equals("skeleton")) { + nameToBone.clear(); + } + assert elementStack.peek().equals(qName); + elementStack.pop(); + } + + /** + * Reset the SkeletonLoader in case an error occured while parsing XML. + * This allows future use of the loader even after an error. + */ + private void fullReset() { + elementStack.clear(); + indexToBone.clear(); + nameToBone.clear(); + track = null; + tracks.clear(); + animation = null; + if (animations != null) { + animations.clear(); + } + + bone = null; + skeleton = null; + times.clear(); + rotations.clear(); + translations.clear(); + time = -1; + position = null; + rotation = null; + scale = null; + angle = 0; + axis = null; + } + + public Object load(InputStream in) throws IOException { + try { + + // Added by larynx 25.06.2011 + // Android needs the namespace aware flag set to true + // Kirill 30.06.2011 + // Now, hack is applied for both desktop and android to avoid + // checking with JmeSystem. + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + XMLReader xr = factory.newSAXParser().getXMLReader(); + + xr.setContentHandler(this); + xr.setErrorHandler(this); + InputStreamReader r = new InputStreamReader(in); + xr.parse(new InputSource(r)); + if (animations == null) { + animations = new ArrayList(); + } + AnimData data = new AnimData(skeleton, animations); + skeleton = null; + animations = null; + return data; + } catch (SAXException ex) { + IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); + ioEx.initCause(ex); + fullReset(); + throw ioEx; + } catch (ParserConfigurationException ex) { + IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); + ioEx.initCause(ex); + fullReset(); + throw ioEx; + } + + } + + public Object load(AssetInfo info) throws IOException { + assetManager = info.getManager(); + InputStream in = null; + try { + in = info.openStream(); + return load(in); + } finally { + if (in != null){ + in.close(); + } + } + } +} diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtension.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtension.java new file mode 100644 index 000000000..192cbd909 --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtension.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.ogre.matext; + +import 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/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtensionLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtensionLoader.java new file mode 100644 index 000000000..da13751d8 --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtensionLoader.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.ogre.matext; + +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.asset.TextureKey; +import com.jme3.material.Material; +import com.jme3.material.MaterialList; +import com.jme3.scene.plugins.ogre.MaterialLoader; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.util.PlaceholderAssets; +import com.jme3.util.blockparser.Statement; +import java.io.IOException; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Used internally by {@link MaterialLoader} + */ +public class MaterialExtensionLoader { + + private static final Logger logger = Logger.getLogger(MaterialExtensionLoader.class.getName()); + + private AssetKey key; + private AssetManager assetManager; + private MaterialList list; + private MaterialExtensionSet matExts; + private MaterialExtension matExt; + private String matName; + private Material material; + + + private void readExtendingMaterialStatement(Statement statement) throws IOException { + if (statement.getLine().startsWith("set_texture_alias")){ + String[] split = statement.getLine().split(" ", 3); + String aliasName = split[1]; + String texturePath = split[2]; + + String jmeParamName = matExt.getTextureMapping(aliasName); + + TextureKey texKey = new TextureKey(texturePath, false); + texKey.setGenerateMips(true); + texKey.setAsCube(false); + Texture tex; + + try { + tex = assetManager.loadTexture(texKey); + tex.setWrap(WrapMode.Repeat); + } catch (AssetNotFoundException ex){ + logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key}); + tex = new Texture2D( PlaceholderAssets.getPlaceholderImage() ); + tex.setWrap(WrapMode.Repeat); + tex.setKey(texKey); + } + + material.setTexture(jmeParamName, tex); + } + } + + private Material readExtendingMaterial(Statement statement) throws IOException{ + String[] split = statement.getLine().split(" ", 2); + String[] subsplit = split[1].split(":"); + matName = subsplit[0].trim(); + String extendedMat = subsplit[1].trim(); + + matExt = matExts.getMaterialExtension(extendedMat); + if (matExt == null){ + logger.log(Level.WARNING, "Cannot find MaterialExtension for: {0}. Ignoring material.", extendedMat); + matExt = null; + return null; + } + + material = new Material(assetManager, matExt.getJmeMatDefName()); + for (Statement extMatStat : statement.getContents()){ + readExtendingMaterialStatement(extMatStat); + } + return material; + } + + public MaterialList load(AssetManager assetManager, AssetKey key, MaterialExtensionSet matExts, + List statements) throws IOException{ + this.assetManager = assetManager; + this.matExts = matExts; + this.key = key; + + list = new MaterialList(); + + for (Statement statement : statements){ + if (statement.getLine().startsWith("import")){ + // ignore + continue; + }else if (statement.getLine().startsWith("material")){ + Material material = readExtendingMaterial(statement); + list.put(matName, material); + List matAliases = matExts.getNameMappings(matName); + if(matAliases != null){ + for (String string : matAliases) { + list.put(string, material); + } + } + } + } + return list; + } +} diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtensionSet.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtensionSet.java new file mode 100644 index 000000000..b153da37e --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtensionSet.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.ogre.matext; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * 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(); + private HashMap> nameMappings = 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); + } + + /** + * Adds an alternative name for a material + * + * @param name The material name to be found in a .mesh.xml file + * @param alias The material name to be found in a .material file + */ + public void setNameMapping(String name, String alias) { + List list = nameMappings.get(name); + if (list == null) { + list = new ArrayList(); + nameMappings.put(name, list); + } + list.add(alias); + } + + public List getNameMappings(String name) { + return nameMappings.get(name); + } +} diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/OgreMaterialKey.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/OgreMaterialKey.java new file mode 100644 index 000000000..c300783ef --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/OgreMaterialKey.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.ogre.matext; + +import com.jme3.asset.AssetKey; +import com.jme3.material.MaterialList; + +/** + * 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(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final OgreMaterialKey other = (OgreMaterialKey) obj; + if (!super.equals(other)) { + return false; + } + if (this.matExts != other.matExts && (this.matExts == null || !this.matExts.equals(other.matExts))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 71 * hash + (super.hashCode()); + hash = 71 * hash + (this.matExts != null ? this.matExts.hashCode() : 0); + return hash; + } + + /** + * Set the {@link MaterialExtensionSet} to use for mapping base materials to + * jME3 matdefs when loading. Set to + * null to disable this functionality. + * + * @param matExts The {@link MaterialExtensionSet} to use + */ + public void setMaterialExtensionSet(MaterialExtensionSet matExts) { + this.matExts = matExts; + } + + /** + * Returns the {@link MaterialExtensionSet} previously set using + * {@link OgreMaterialKey#setMaterialExtensionSet(com.jme3.scene.plugins.ogre.matext.MaterialExtensionSet) + * } method. + * + * @return the {@link MaterialExtensionSet} + */ + public MaterialExtensionSet getMaterialExtensionSet() { + return matExts; + } +} diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/package.html b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/package.html new file mode 100644 index 000000000..0b2725cc0 --- /dev/null +++ b/jme3-plugins/src/ogre/java/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/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java new file mode 100644 index 000000000..a8ba4f7aa --- /dev/null +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java @@ -0,0 +1,1511 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.export.xml; + +import com.jme3.export.InputCapsule; +import com.jme3.export.Savable; +import com.jme3.export.SavableClassUtil; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.*; +import java.util.logging.Logger; +import org.w3c.dom.*; + +/** + * Part of the jME XML IO system as introduced in the google code jmexml project. + * + * @author Kai Rabien (hevee) - original author of the code.google.com jmexml project + * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5 + * @author blaine + */ +public class DOMInputCapsule implements InputCapsule { + private static final Logger logger = + Logger.getLogger(DOMInputCapsule.class .getName()); + + private Document doc; + private Element currentElem; + private XMLImporter importer; + private boolean isAtRoot = true; + private Map referencedSavables = new HashMap(); + + private int[] classHierarchyVersions; + private Savable savable; + + public DOMInputCapsule(Document doc, XMLImporter importer) { + this.doc = doc; + this.importer = importer; + currentElem = doc.getDocumentElement(); + + String version = currentElem.getAttribute("format_version"); + importer.formatVersion = version.equals("") ? 0 : Integer.parseInt(version); + } + + public int getSavableVersion(Class desiredClass) { + if (classHierarchyVersions != null){ + return SavableClassUtil.getSavedSavableVersion(savable, desiredClass, + classHierarchyVersions, importer.getFormatVersion()); + }else{ + return 0; + } + } + + private static String decodeString(String s) { + if (s == null) { + return null; + } + s = s.replaceAll("\\"", "\"").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 = SavableClassUtil.fromName(className, null); + + + String versionsStr = currentElem.getAttribute("savable_versions"); + if (versionsStr != null && !versionsStr.equals("")){ + String[] versionStr = versionsStr.split(","); + classHierarchyVersions = new int[versionStr.length]; + for (int i = 0; i < classHierarchyVersions.length; i++){ + classHierarchyVersions[i] = Integer.parseInt(versionStr[i].trim()); + } + }else{ + classHierarchyVersions = null; + } + + String refID = currentElem.getAttribute("reference_ID"); + if (refID.length() < 1) refID = currentElem.getAttribute("id"); + if (refID.length() > 0) referencedSavables.put(refID, tmp); + if (tmp != null) { + // Allows reading versions from this savable + savable = tmp; + tmp.read(importer); + ret = tmp; + } + } + return ret; + } + + public Savable[] readSavableArray(String name, Savable[] defVal) throws IOException { + Savable[] ret = defVal; + try { + Element tmpEl = findChildElement(currentElem, name); + if (tmpEl == null) { + return defVal; + } + + String sizeString = tmpEl.getAttribute("size"); + List 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/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java new file mode 100644 index 000000000..c3c0e6744 --- /dev/null +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java @@ -0,0 +1,825 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.export.xml; + +import com.jme3.export.FormatVersion; +import com.jme3.export.JmeExporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.export.SavableClassUtil; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.*; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Part of the jME XML IO system as introduced in the google code jmexml project. + * + * @author Kai Rabien (hevee) - original author of the code.google.com jmexml project + * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5 + */ +public class DOMOutputCapsule implements OutputCapsule { + + private static final String dataAttributeName = "data"; + private Document doc; + private Element currentElement; + private JmeExporter exporter; + private Map 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 = doc.createElement(name); + if (currentElement == null) { + ret.setAttribute("format_version", Integer.toString(FormatVersion.VERSION)); + doc.appendChild(ret); + } else { + currentElement.appendChild(ret); + } + currentElement = ret; + return ret; + } + + private static String encodeString(String s) { + if (s == null) { + return null; + } + s = s.replaceAll("\\&", "&") + .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); + + // jME3 NEW: Append version number(s) + int[] versions = SavableClassUtil.getSavableVersions(object.getClass()); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < versions.length; i++){ + sb.append(versions[i]); + if (i != versions.length - 1){ + sb.append(", "); + } + } + el.setAttribute("savable_versions", sb.toString()); + + writtenSavables.put(object, el); + object.write(exporter); + } + if(className != null){ + el.setAttribute("class", className); + } + + currentElement = old; + } + + public void write(Savable[] objects, String name, Savable[] defVal) throws IOException { + if (objects == null) { + return; + } + if (Arrays.equals(objects, defVal)) { + return; + } + + Element old = currentElement; + Element el = appendElement(name); + el.setAttribute("size", String.valueOf(objects.length)); + for (int i = 0; i < objects.length; i++) { + Savable o = objects[i]; + if(o == null){ + //renderStateList has special loading code, so we can leave out the null values + if(!name.equals("renderStateList")){ + Element before = currentElement; + appendElement("null"); + currentElement = before; + } + }else{ + write(o, o.getClass().getName(), null); + } + } + currentElement = old; + } + + public void write(Savable[][] value, String name, Savable[][] defVal) throws IOException { + if (value == null) return; + if(Arrays.deepEquals(value, defVal)) return; + + Element el = appendElement(name); + el.setAttribute("size_outer", String.valueOf(value.length)); + el.setAttribute("size_inner", String.valueOf(value[0].length)); + for (Savable[] bs : value) { + for(Savable b : bs){ + write(b, b.getClass().getSimpleName(), null); + } + } + currentElement = (Element) currentElement.getParentNode(); + } + + public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { + if (array == null) { + return; + } + if (array.equals(defVal)) { + return; + } + Element old = currentElement; + Element el = appendElement(name); + currentElement = el; + el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size())); + for (Object o : array) { + if(o == null) { + continue; + } + else if (o instanceof Savable) { + Savable s = (Savable) o; + write(s, s.getClass().getName(), null); + } else { + throw new ClassCastException("Not a Savable instance: " + o); + } + } + currentElement = old; + } + + public void writeSavableArrayListArray(ArrayList[] objects, String name, ArrayList[] defVal) throws IOException { + if (objects == null) {return;} + if (Arrays.equals(objects, defVal)) {return;} + + Element old = currentElement; + Element el = appendElement(name); + el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(objects.length)); + for (int i = 0; i < objects.length; i++) { + ArrayList o = objects[i]; + if(o == null){ + Element before = currentElement; + appendElement("null"); + currentElement = before; + }else{ + StringBuilder buf = new StringBuilder("SavableArrayList_"); + buf.append(i); + writeSavableArrayList(o, buf.toString(), null); + } + } + currentElement = old; + } + + public void writeSavableArrayListArray2D(ArrayList[][] value, String name, ArrayList[][] defVal) throws IOException { + if (value == null) return; + if(Arrays.deepEquals(value, defVal)) return; + + Element el = appendElement(name); + int size = value.length; + el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(size)); + + for (int i=0; i< size; i++) { + ArrayList[] vi = value[i]; + writeSavableArrayListArray(vi, "SavableArrayListArray_"+i, null); + } + currentElement = (Element) el.getParentNode(); + } + + public void writeFloatBufferArrayList(ArrayList 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/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java new file mode 100644 index 000000000..f889a8eb6 --- /dev/null +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.export.xml; + +import java.io.*; +import java.nio.charset.Charset; +import org.w3c.dom.*; + +/** + * The DOMSerializer was based primarily off the DOMSerializer.java class from the + * "Java and XML" 3rd Edition book by Brett McLaughlin, and Justin Edelson. Some + * modifications were made to support formatting of elements and attributes. + * + * @author Brett McLaughlin, Justin Edelson - Original creation for "Java and XML" book. + * @author Doug Daniels (dougnukem) - adjustments for XML formatting + * @version $Revision: 4207 $, $Date: 2009-03-29 11:19:16 -0400 (Sun, 29 Mar 2009) $ + */ +public class DOMSerializer { + + /** The encoding to use for output (default is UTF-8) */ + private Charset encoding = Charset.forName("utf-8"); + + /** The amount of indentation to use (default is 4 spaces). */ + private int indent = 4; + + /** The line separator to use (default is the based on the current system settings). */ + private String lineSeparator = System.getProperty("line.separator", "\n"); + + private void escape(Writer writer, String s) throws IOException { + if (s == null) { return; } + for (int i = 0, len = s.length(); i < len; i++) { + char c = s.charAt(i); + switch (c) { + case '<': + writer.write("<"); + 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/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java new file mode 100644 index 000000000..32138270d --- /dev/null +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.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/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java new file mode 100644 index 000000000..3e947e2b1 --- /dev/null +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.export.xml; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeImporter; +import com.jme3.export.Savable; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.xml.sax.SAXException; + +/** + * Part of the jME XML IO system as introduced in the google code jmexml project. + * @author Kai Rabien (hevee) - original author of the code.google.com jmexml project + * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5 + */ +public class XMLImporter implements JmeImporter { + + private AssetManager assetManager; + private DOMInputCapsule domIn; + int formatVersion = 0; + + public XMLImporter() { + } + + public int getFormatVersion() { + return formatVersion; + } + + public AssetManager getAssetManager(){ + return assetManager; + } + + public void setAssetManager(AssetManager assetManager){ + this.assetManager = assetManager; + } + + public Object load(AssetInfo info) throws IOException{ + assetManager = info.getManager(); + InputStream in = info.openStream(); + Savable obj = load(in); + in.close(); + return obj; + } + + public Savable load(File f) throws IOException { + FileInputStream fis = null; + try { + fis = new FileInputStream(f); + Savable sav = load(fis); + return sav; + } finally { + if (fis != null) fis.close(); + } + } + + public Savable load(InputStream f) throws IOException { + try { + domIn = new DOMInputCapsule(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(f), this); + return domIn.readSavable(null, null); + } catch (SAXException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } catch (ParserConfigurationException e) { + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public InputCapsule getCapsule(Savable id) { + return domIn; + } + + public static XMLImporter getInstance() { + return new XMLImporter(); + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/GeoMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/GeoMap.java new file mode 100644 index 000000000..75a538136 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/GeoMap.java @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain; + +import com.jme3.export.*; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +/** + * Constructs heightfields to be used in Terrain. + */ +public class GeoMap implements Savable { + + protected float[] hdata; + protected int width, height, maxval; + + public GeoMap() {} + + @Deprecated + public GeoMap(FloatBuffer heightData, int width, int height, int maxval){ + hdata = new float[heightData.limit()]; + heightData.get(hdata); + this.width = width; + this.height = height; + this.maxval = maxval; + } + + public GeoMap(float[] heightData, int width, int height, int maxval){ + this.hdata = heightData; + this.width = width; + this.height = height; + this.maxval = maxval; + } + + @Deprecated + public FloatBuffer getHeightData(){ + if (!isLoaded()) + return null; + return BufferUtils.createFloatBuffer(hdata); + } + + public float[] getHeightArray(){ + if (!isLoaded()) + return null; + return hdata; + } + + /** + * @return The maximum possible value that getValue() can + * return. Mostly depends on the source data format (byte, short, int, etc). + */ + public int getMaximumValue(){ + return maxval; + } + + /** + * Returns the height value for a given point. + * + * MUST return the same value as getHeight(y*getWidth()+x) + * + * @param x the X coordinate + * @param y the Y coordinate + * @returns an arbitrary height looked up from the heightmap + * + * @throws NullPointerException If isLoaded() is false + */ + public float getValue(int x, int y) { + return hdata[y*width+x]; + } + + /** + * Returns the height value at the given index. + * + * zero index is top left of map, + * getWidth()*getHeight() index is lower right + * + * @param i The index + * @returns an arbitrary height looked up from the heightmap + * + * @throws NullPointerException If isLoaded() is false + */ + public float getValue(int i) { + return hdata[i]; + } + + + /** + * Returns the width of this Geomap + * + * @returns the width of this Geomap + */ + public int getWidth() { + return width; + } + + /** + * Returns the height of this Geomap + * + * @returns the height of this Geomap + */ + public int getHeight() { + return height; + } + + /** + * Returns true if the Geomap data is loaded in memory + * If false, then the data is unavailable- must be loaded with load() + * before the methods getHeight/getNormal can be used + * + * @returns wether the geomap data is loaded in system memory + */ + public boolean isLoaded() { + return true; + } + + /** + * Creates a normal array from the normal data in this Geomap + * + * @param store A preallocated FloatBuffer where to store the data (optional), size must be >= getWidth()*getHeight()*3 + * @returns store, or a new FloatBuffer if store is null + * + * @throws NullPointerException If isLoaded() or hasNormalmap() is false + */ + public FloatBuffer writeNormalArray(FloatBuffer store, Vector3f scale) { + + if (store!=null){ + if (store.remaining() < getWidth()*getHeight()*3) + throw new BufferUnderflowException(); + }else{ + store = BufferUtils.createFloatBuffer(getWidth()*getHeight()*3); + } + store.rewind(); + + Vector3f oppositePoint = new Vector3f(); + Vector3f adjacentPoint = new Vector3f(); + Vector3f rootPoint = new Vector3f(); + Vector3f tempNorm = new Vector3f(); + int normalIndex = 0; + + for (int y = 0; y < getHeight(); y++) { + for (int x = 0; x < getWidth(); x++) { + rootPoint.set(x, getValue(x,y), y); + if (y == getHeight() - 1) { + if (x == getWidth() - 1) { // case #4 : last row, last col + // left cross up +// adj = normalIndex - getWidth(); +// opp = normalIndex - 1; + adjacentPoint.set(x, getValue(x,y-1), y-1); + oppositePoint.set(x-1, getValue(x-1, y), y); + } else { // case #3 : last row, except for last col + // right cross up +// adj = normalIndex + 1; +// opp = normalIndex - getWidth(); + adjacentPoint.set(x+1, getValue(x+1,y), y); + oppositePoint.set(x, getValue(x,y-1), y-1); + } + } else { + if (x == getWidth() - 1) { // case #2 : last column except for last row + // left cross down + adjacentPoint.set(x-1, getValue(x-1,y), y); + oppositePoint.set(x, getValue(x,y+1), y+1); +// adj = normalIndex - 1; +// opp = normalIndex + getWidth(); + } else { // case #1 : most cases + // right cross down + adjacentPoint.set(x, getValue(x,y+1), y+1); + oppositePoint.set(x+1, getValue(x+1,y), y); +// adj = normalIndex + getWidth(); +// opp = normalIndex + 1; + } + } + + + + tempNorm.set(adjacentPoint).subtractLocal(rootPoint) + .crossLocal(oppositePoint.subtractLocal(rootPoint)); + tempNorm.multLocal(scale).normalizeLocal(); +// store.put(tempNorm.x).put(tempNorm.y).put(tempNorm.z); + BufferUtils.setInBuffer(tempNorm, store, + normalIndex); + normalIndex++; + } + } + + return store; + } + + /** + * Creates a vertex array from the height data in this Geomap + * + * The scale argument specifies the scale to use for the vertex buffer. + * For example, if scale is 10,1,10, then the greatest X value is getWidth()*10 + * + * @param store A preallocated FloatBuffer where to store the data (optional), size must be >= getWidth()*getHeight()*3 + * @param scale Created vertexes are scaled by this vector + * + * @returns store, or a new FloatBuffer if store is null + * + * @throws NullPointerException If isLoaded() is false + */ + public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean center) { + + if (store!=null){ + if (store.remaining() < width*height*3) + throw new BufferUnderflowException(); + }else{ + store = BufferUtils.createFloatBuffer(width*height*3); + } + + assert hdata.length == height*width; + + Vector3f offset = new Vector3f(-getWidth() * scale.x * 0.5f, + 0, + -getWidth() * scale.z * 0.5f); + if (!center) + offset.zero(); + + int i = 0; + for (int z = 0; z < height; z++){ + for (int x = 0; x < width; x++){ + store.put( (float)x*scale.x + offset.x ); + store.put( (float)hdata[i++]*scale.y ); + store.put( (float)z*scale.z + offset.z ); + } + } + + return store; + } + + public Vector2f getUV(int x, int y, Vector2f store){ + store.set( (float)x / (float)getWidth(), + (float)y / (float)getHeight() ); + return store; + } + + public Vector2f getUV(int i, Vector2f store){ + return store; + } + + public FloatBuffer writeTexCoordArray(FloatBuffer store, Vector2f offset, Vector2f scale){ + if (store!=null){ + if (store.remaining() < getWidth()*getHeight()*2) + throw new BufferUnderflowException(); + }else{ + store = BufferUtils.createFloatBuffer(getWidth()*getHeight()*2); + } + + if (offset == null) + offset = new Vector2f(); + + Vector2f tcStore = new Vector2f(); + for (int y = 0; y < getHeight(); y++){ + for (int x = 0; x < getWidth(); x++){ + getUV(x,y,tcStore); + store.put( offset.x + tcStore.x * scale.x ); + store.put( offset.y + tcStore.y * scale.y ); + } + + } + + return store; + } + + public IntBuffer writeIndexArray(IntBuffer store){ + int faceN = (getWidth()-1)*(getHeight()-1)*2; + + if (store!=null){ + if (store.remaining() < faceN*3) + throw new BufferUnderflowException(); + }else{ + store = BufferUtils.createIntBuffer(faceN*3); + } + + int i = 0; + for (int z = 0; z < getHeight()-1; z++){ + for (int x = 0; x < getWidth()-1; x++){ + store.put(i).put(i+getWidth()).put(i+getWidth()+1); + store.put(i+getWidth()+1).put(i+1).put(i); + i++; + + // TODO: There's probably a better way to do this.. + if (x==getWidth()-2) i++; + } + } + store.flip(); + + return store; + } + + public Mesh createMesh(Vector3f scale, Vector2f tcScale, boolean center){ + FloatBuffer pb = writeVertexArray(null, scale, center); + FloatBuffer tb = writeTexCoordArray(null, Vector2f.ZERO, tcScale); + FloatBuffer nb = writeNormalArray(null, scale); + IntBuffer ib = writeIndexArray(null); + Mesh m = new Mesh(); + m.setBuffer(Type.Position, 3, pb); + m.setBuffer(Type.Normal, 3, nb); + m.setBuffer(Type.TexCoord, 2, tb); + m.setBuffer(Type.Index, 3, ib); + m.setStatic(); + m.updateBound(); + return m; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(hdata, "hdataarray", null); + oc.write(width, "width", 0); + oc.write(height, "height", 0); + oc.write(maxval, "maxval", 0); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + hdata = ic.readFloatArray("hdataarray", null); + if (hdata == null) { + FloatBuffer buf = ic.readFloatBuffer("hdata", null); + if (buf != null) { + hdata = new float[buf.limit()]; + buf.get(hdata); + } + } + width = ic.readInt("width", 0); + height = ic.readInt("height", 0); + maxval = ic.readInt("maxval", 0); + } + + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/ProgressMonitor.java b/jme3-terrain/src/main/java/com/jme3/terrain/ProgressMonitor.java new file mode 100644 index 000000000..2d42337bf --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/ProgressMonitor.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain; + +/** + * 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/jme3-terrain/src/main/java/com/jme3/terrain/Terrain.java b/jme3-terrain/src/main/java/com/jme3/terrain/Terrain.java new file mode 100644 index 000000000..d96bb1340 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/Terrain.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain; + +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import java.util.List; + +/** + * Terrain can be one or many meshes comprising of a, probably large, piece of land. + * Terrain is Y-up in the grid axis, meaning gravity acts in the -Y direction. + * Level of Detail (LOD) is supported and expected as terrains can get very large. LOD can + * also be disabled if you so desire, however some terrain implementations can choose to ignore + * useLOD(boolean). + * Terrain implementations should extend Node, or at least Spatial. + * + * @author bowens + */ +public interface Terrain { + + /** + * Get the real-world height of the terrain at the specified X-Z coorindate. + * @param xz the X-Z world coordinate + * @return the height at the given point + */ + public float getHeight(Vector2f xz); + + /** + * Get the normal vector for the surface of the terrain at the specified + * X-Z coordinate. This normal vector can be a close approximation. It does not + * take into account any normal maps on the material. + * @param xz the X-Z world coordinate + * @return the normal vector at the given point + */ + public Vector3f getNormal(Vector2f xz); + + /** + * Get the heightmap height at the specified X-Z coordinate. This does not + * count scaling and snaps the XZ coordinate to the nearest (rounded) heightmap grid point. + * @param xz world coordinate + * @return the height, unscaled and uninterpolated + */ + public float getHeightmapHeight(Vector2f xz); + + /** + * Set the height at the specified X-Z coordinate. + * To set the height of the terrain and see it, you will have + * to unlock the terrain meshes by calling terrain.setLocked(false) before + * you call setHeight(). + * @param xzCoordinate coordinate to set the height + * @param height that will be set at the coordinate + */ + public void setHeight(Vector2f xzCoordinate, float height); + + /** + * Set the height at many points. The two lists must be the same size. + * Each xz coordinate entry matches to a height entry, 1 for 1. So the + * first coordinate matches to the first height value, the last to the + * last etc. + * @param xz a list of coordinates where the hight will be set + * @param height the heights that match the xz coordinates + */ + public void setHeight(List xz, List height); + + /** + * Raise/lower the height in one call (instead of getHeight then setHeight). + * @param xzCoordinate world coordinate to adjust the terrain height + * @param delta +- value to adjust the height by + */ + public void adjustHeight(Vector2f xzCoordinate, float delta); + + /** + * Raise/lower the height at many points. The two lists must be the same size. + * Each xz coordinate entry matches to a height entry, 1 for 1. So the + * first coordinate matches to the first height value, the last to the + * last etc. + * @param xz a list of coordinates where the hight will be adjusted + * @param height +- value to adjust the height by, that matches the xz coordinates + */ + public void adjustHeight(List xz, List height); + + /** + * Get the heightmap of the entire terrain. + * This can return null if that terrain object does not store the height data. + * Infinite or "paged" terrains will not be able to support this, so use with caution. + */ + public float[] getHeightMap(); + + /** + * This is calculated by the specific LOD algorithm. + * A value of one means that the terrain is showing full detail. + * The higher the value, the more the terrain has been generalized + * and the less detailed it will be. + */ + public int getMaxLod(); + + /** + * Lock or unlock the meshes of this terrain. + * Locked meshes are un-editable but have better performance. + * This should call the underlying getMesh().setStatic()/setDynamic() methods. + * @param locked or unlocked + */ + public void setLocked(boolean locked); + + /** + * Pre-calculate entropy values. + * Some terrain systems support entropy calculations to determine LOD + * changes. Often these entropy calculations are expensive and can be + * cached ahead of time. Use this method to do that. + */ + public void generateEntropy(ProgressMonitor monitor); + + /** + * Returns the material that this terrain uses. + * If it uses many materials, just return the one you think is best. + * For TerrainQuads this is sufficient. For TerrainGrid you want to call + * getMaterial(Vector3f) instead. + */ + public Material getMaterial(); + + /** + * Returns the material that this terrain uses. + * Terrain can have different materials in different locations. + * In general, the TerrainQuad will only have one material. But + * TerrainGrid will have a different material per tile. + * + * It could be possible to pass in null for the location, some Terrain + * implementations might just have the one material and not care where + * you are looking. So implementations must handle null being supplied. + * + * @param worldLocation the location, in world coordinates, of where + * we are interested in the underlying texture. + */ + public Material getMaterial(Vector3f worldLocation); + + /** + * Used for painting to get the number of vertices along the edge of the + * terrain. + * This is an un-scaled size, and should represent the vertex count (ie. the + * texture coord count) along an edge of a square terrain. + * + * In the standard TerrainQuad default implementation, this will return + * the "totalSize" of the terrain (512 or so). + */ + public int getTerrainSize(); + + /** + * Get the scale of the texture coordinates. Normally if the texture is + * laid on the terrain and not scaled so that the texture does not repeat, + * then each texture coordinate (on a vertex) will be 1/(terrain size). + * That is: the coverage between each consecutive texture coordinate will + * be a percentage of the total terrain size. + * So if the terrain is 512 vertexes wide, then each texture coord will cover + * 1/512 (or 0.00195) percent of the texture. + * This is used for converting between tri-planar texture scales and regular + * texture scales. + * + * not needed + */ + //public float getTextureCoordinateScale(); + + /** + * + * + */ + public int getNumMajorSubdivisions(); +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/LODGeomap.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/LODGeomap.java new file mode 100644 index 000000000..fec90d811 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/LODGeomap.java @@ -0,0 +1,1206 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.FastMath; +import com.jme3.math.Triangle; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.terrain.GeoMap; +import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.nio.Buffer; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +/** + * Produces the mesh for the TerrainPatch. + * This LOD algorithm generates a single triangle strip by first building the center of the + * mesh, minus one outer edge around it. Then it builds the edges in counter-clockwise order, + * starting at the bottom right and working up, then left across the top, then down across the + * left, then right across the bottom. + * It needs to know what its neighbour's LOD's are so it can stitch the edges. + * It creates degenerate polygons in order to keep the winding order of the polygons and to move + * the strip to a new position while still maintaining the continuity of the overall mesh. These + * degenerates are removed quickly by the video card. + * + * @author Brent Owens + */ +public class LODGeomap extends GeoMap { + + public LODGeomap() { + } + + @Deprecated + public LODGeomap(int size, FloatBuffer heightMap) { + super(heightMap, size, size, 1); + } + + public LODGeomap(int size, float[] heightMap) { + super(heightMap, size, size, 1); + } + + public Mesh createMesh(Vector3f scale, Vector2f tcScale, Vector2f tcOffset, float offsetAmount, int totalSize, boolean center) { + return this.createMesh(scale, tcScale, tcOffset, offsetAmount, totalSize, center, 1, false, false, false, false); + } + + public Mesh createMesh(Vector3f scale, Vector2f tcScale, Vector2f tcOffset, float offsetAmount, int totalSize, boolean center, int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod) { + FloatBuffer pb = writeVertexArray(null, scale, center); + FloatBuffer texb = writeTexCoordArray(null, tcOffset, tcScale, offsetAmount, totalSize); + FloatBuffer nb = writeNormalArray(null, scale); + Buffer ib; + IndexBuffer idxB = writeIndexArrayLodDiff(lod, rightLod, topLod, leftLod, bottomLod, totalSize); + if (idxB.getBuffer() instanceof IntBuffer) + ib = (IntBuffer)idxB.getBuffer(); + else + ib = (ShortBuffer)idxB.getBuffer(); + FloatBuffer bb = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3); + FloatBuffer tanb = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3); + writeTangentArray(nb, tanb, bb, texb, scale); + Mesh m = new Mesh(); + m.setMode(Mode.TriangleStrip); + m.setBuffer(Type.Position, 3, pb); + m.setBuffer(Type.Normal, 3, nb); + m.setBuffer(Type.Tangent, 3, tanb); + m.setBuffer(Type.Binormal, 3, bb); + m.setBuffer(Type.TexCoord, 2, texb); + if (ib instanceof IntBuffer) + m.setBuffer(Type.Index, 3, (IntBuffer)ib); + else if (ib instanceof ShortBuffer) + m.setBuffer(Type.Index, 3, (ShortBuffer)ib); + m.setStatic(); + m.updateBound(); + return m; + } + + public FloatBuffer writeTexCoordArray(FloatBuffer store, Vector2f offset, Vector2f scale, float offsetAmount, int totalSize) { + if (store != null) { + if (store.remaining() < getWidth() * getHeight() * 2) { + throw new BufferUnderflowException(); + } + } else { + store = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 2); + } + + if (offset == null) { + offset = new Vector2f(); + } + + Vector2f tcStore = new Vector2f(); + + // work from bottom of heightmap up, so we don't flip the coords + for (int y = getHeight() - 1; y >= 0; y--) { + for (int x = 0; x < getWidth(); x++) { + getUV(x, y, tcStore, offset, offsetAmount, totalSize); + float tx = tcStore.x * scale.x; + float ty = tcStore.y * scale.y; + store.put(tx); + store.put(ty); + } + } + + return store; + } + + public Vector2f getUV(int x, int y, Vector2f store, Vector2f offset, float offsetAmount, int totalSize) { + float offsetX = offset.x + (offsetAmount * 1.0f); + float offsetY = -offset.y + (offsetAmount * 1.0f);//note the -, we flip the tex coords + + store.set((((float) x) + offsetX) / (float) (totalSize - 1), // calculates percentage of texture here + (((float) y) + offsetY) / (float) (totalSize - 1)); + return store; + } + + /** + * Create the LOD index array that will seam its edges with its neighbour's LOD. + * This is a scary method!!! It will break your mind. + * + * @param store to store the index buffer + * @param lod level of detail of the mesh + * @param rightLod LOD of the right neighbour + * @param topLod LOD of the top neighbour + * @param leftLod LOD of the left neighbour + * @param bottomLod LOD of the bottom neighbour + * @return the LOD-ified index buffer + */ + public IndexBuffer writeIndexArrayLodDiff(int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod, int totalSize) { + + + int numIndexes = calculateNumIndexesLodDiff(lod); + + IndexBuffer ib = IndexBuffer.createIndexBuffer(numIndexes, numIndexes); + VerboseBuffer buffer = new VerboseBuffer(ib); + + + // generate center squares minus the edges + //System.out.println("for (x="+lod+"; x<"+(getWidth()-(2*lod))+"; x+="+lod+")"); + //System.out.println(" for (z="+lod+"; z<"+(getWidth()-(1*lod))+"; z+="+lod+")"); + for (int r = lod; r < getWidth() - (2 * lod); r += lod) { // row + int rowIdx = r * getWidth(); + int nextRowIdx = (r + 1 * lod) * getWidth(); + for (int c = lod; c < getWidth() - (1 * lod); c += lod) { // column + int idx = rowIdx + c; + buffer.put(idx); + idx = nextRowIdx + c; + buffer.put(idx); + } + + // add degenerate triangles + if (r < getWidth() - (3 * lod)) { + int idx = nextRowIdx + getWidth() - (1 * lod) - 1; + buffer.put(idx); + idx = nextRowIdx + (1 * lod); // inset by 1 + buffer.put(idx); + //System.out.println(""); + } + } + //System.out.println("\nright:"); + + //int runningBufferCount = buffer.getCount(); + //System.out.println("buffer start: "+runningBufferCount); + + + // right + int br = getWidth() * (getWidth() - lod) - 1 - lod; + buffer.put(br); // bottom right -1 + int corner = getWidth() * getWidth() - 1; + buffer.put(corner); // bottom right corner + if (rightLod) { // if lower LOD + for (int row = getWidth() - lod; row >= 1 + lod; row -= 2 * lod) { + int idx = (row) * getWidth() - 1 - lod; + buffer.put(idx); + idx = (row - lod) * getWidth() - 1; + buffer.put(idx); + if (row > lod + 1) { //if not the last one + idx = (row - lod) * getWidth() - 1 - lod; + buffer.put(idx); + idx = (row - lod) * getWidth() - 1; + buffer.put(idx); + } else { + } + } + } else { + buffer.put(corner);//br+1);//degenerate to flip winding order + for (int row = getWidth() - lod; row > lod; row -= lod) { + int idx = row * getWidth() - 1; // mult to get row + buffer.put(idx); + buffer.put(idx - lod); + } + + } + + buffer.put(getWidth() - 1); + + + //System.out.println("\nbuffer right: "+(buffer.getCount()-runningBufferCount)); + //runningBufferCount = buffer.getCount(); + + + //System.out.println("\ntop:"); + + // top (the order gets reversed here so the diagonals line up) + if (topLod) { // if lower LOD + if (rightLod) { + buffer.put(getWidth() - 1); + } + for (int col = getWidth() - 1; col >= lod; col -= 2 * lod) { + int idx = (lod * getWidth()) + col - lod; // next row + buffer.put(idx); + idx = col - 2 * lod; + buffer.put(idx); + if (col > lod * 2) { //if not the last one + idx = (lod * getWidth()) + col - 2 * lod; + buffer.put(idx); + idx = col - 2 * lod; + buffer.put(idx); + } else { + } + } + } else { + if (rightLod) { + buffer.put(getWidth() - 1); + } + for (int col = getWidth() - 1 - lod; col > 0; col -= lod) { + int idx = col + (lod * getWidth()); + buffer.put(idx); + idx = col; + buffer.put(idx); + } + buffer.put(0); + } + buffer.put(0); + + //System.out.println("\nbuffer top: "+(buffer.getCount()-runningBufferCount)); + //runningBufferCount = buffer.getCount(); + + //System.out.println("\nleft:"); + + // left + if (leftLod) { // if lower LOD + if (topLod) { + buffer.put(0); + } + for (int row = 0; row < getWidth() - lod; row += 2 * lod) { + int idx = (row + lod) * getWidth() + lod; + buffer.put(idx); + idx = (row + 2 * lod) * getWidth(); + buffer.put(idx); + if (row < getWidth() - 1 - 2 * lod) { //if not the last one + idx = (row + 2 * lod) * getWidth() + lod; + buffer.put(idx); + idx = (row + 2 * lod) * getWidth(); + buffer.put(idx); + } else { + } + } + } else { + if (!topLod) { + buffer.put(0); + } + //buffer.put(getWidth()+1); // degenerate + //buffer.put(0); // degenerate winding-flip + for (int row = lod; row < getWidth() - lod; row += lod) { + int idx = row * getWidth(); + buffer.put(idx); + idx = row * getWidth() + lod; + buffer.put(idx); + } + + } + buffer.put(getWidth() * (getWidth() - 1)); + + + //System.out.println("\nbuffer left: "+(buffer.getCount()-runningBufferCount)); + //runningBufferCount = buffer.getCount(); + + //if (true) return buffer.delegate; + //System.out.println("\nbottom"); + + // bottom + if (bottomLod) { // if lower LOD + if (leftLod) { + buffer.put(getWidth() * (getWidth() - 1)); + } + // there was a slight bug here when really high LOD near maxLod + // far right has extra index one row up and all the way to the right, need to skip last index entered + // seemed to be fixed by making "getWidth()-1-2-lod" this: "getWidth()-1-2*lod", which seems more correct + for (int col = 0; col < getWidth() - lod; col += 2 * lod) { + int idx = getWidth() * (getWidth() - 1 - lod) + col + lod; + buffer.put(idx); + idx = getWidth() * (getWidth() - 1) + col + 2 * lod; + buffer.put(idx); + if (col < getWidth() - 1 - 2 * lod) { //if not the last one + idx = getWidth() * (getWidth() - 1 - lod) + col + 2 * lod; + buffer.put(idx); + idx = getWidth() * (getWidth() - 1) + col + 2 * lod; + buffer.put(idx); + } else { + } + } + } else { + if (leftLod) { + buffer.put(getWidth() * (getWidth() - 1)); + } + for (int col = lod; col < getWidth() - lod; col += lod) { + int idx = getWidth() * (getWidth() - 1 - lod) + col; // up + buffer.put(idx); + idx = getWidth() * (getWidth() - 1) + col; // down + buffer.put(idx); + } + //buffer.put(getWidth()*getWidth()-1-lod); // <-- THIS caused holes at the end! + } + + buffer.put(getWidth() * getWidth() - 1); + + //System.out.println("\nbuffer bottom: "+(buffer.getCount()-runningBufferCount)); + //runningBufferCount = buffer.getCount(); + + //System.out.println("\nBuffer size: "+buffer.getCount()); + + // fill in the rest of the buffer with degenerates, there should only be a couple + for (int i = buffer.getCount(); i < numIndexes; i++) { + buffer.put(getWidth() * getWidth() - 1); + } + + return buffer.delegate; + } + + public IndexBuffer writeIndexArrayLodVariable(int lod, int rightLod, int topLod, int leftLod, int bottomLod, int totalSize) { + + int numIndexes = calculateNumIndexesLodDiff(lod); + + IndexBuffer ib = IndexBuffer.createIndexBuffer(numIndexes, numIndexes); + VerboseBuffer buffer = new VerboseBuffer(ib); + + + // generate center squares minus the edges + //System.out.println("for (x="+lod+"; x<"+(getWidth()-(2*lod))+"; x+="+lod+")"); + //System.out.println(" for (z="+lod+"; z<"+(getWidth()-(1*lod))+"; z+="+lod+")"); + for (int r = lod; r < getWidth() - (2 * lod); r += lod) { // row + int rowIdx = r * getWidth(); + int nextRowIdx = (r + 1 * lod) * getWidth(); + for (int c = lod; c < getWidth() - (1 * lod); c += lod) { // column + int idx = rowIdx + c; + buffer.put(idx); + idx = nextRowIdx + c; + buffer.put(idx); + } + + // add degenerate triangles + if (r < getWidth() - (3 * lod)) { + int idx = nextRowIdx + getWidth() - (1 * lod) - 1; + buffer.put(idx); + idx = nextRowIdx + (1 * lod); // inset by 1 + buffer.put(idx); + //System.out.println(""); + } + } + //System.out.println("\nright:"); + + //int runningBufferCount = buffer.getCount(); + //System.out.println("buffer start: "+runningBufferCount); + + + // right + int br = getWidth() * (getWidth() - lod) - 1 - lod; + buffer.put(br); // bottom right -1 + int corner = getWidth() * getWidth() - 1; + buffer.put(corner); // bottom right corner + if (rightLod > lod) { // if lower LOD + int idx = corner; + int it = (getWidth() - 1) / rightLod; // iterations + int lodDiff = rightLod / lod; + for (int i = it; i > 0; i--) { // for each lod level of the neighbour + idx = getWidth() * (i * rightLod + 1) - 1; + for (int j = 1; j <= lodDiff; j++) { // for each section in that lod level + int idxB = idx - (getWidth() * (j * lod)) - lod; + + if (j == lodDiff && i == 1) {// the last one + buffer.put(getWidth() - 1); + } else if (j == lodDiff) { + buffer.put(idxB); + buffer.put(idxB + lod); + } else { + buffer.put(idxB); + buffer.put(idx); + } + } + } + // reset winding order + buffer.put(getWidth() * (lod + 1) - lod - 1); // top-right +1row + buffer.put(getWidth() - 1);// top-right + + } else { + buffer.put(corner);//br+1);//degenerate to flip winding order + for (int row = getWidth() - lod; row > lod; row -= lod) { + int idx = row * getWidth() - 1; // mult to get row + buffer.put(idx); + buffer.put(idx - lod); + } + buffer.put(getWidth() - 1); + } + + + //System.out.println("\nbuffer right: "+(buffer.getCount()-runningBufferCount)); + //runningBufferCount = buffer.getCount(); + + + //System.out.println("\ntop:"); + + // top (the order gets reversed here so the diagonals line up) + if (topLod > lod) { // if lower LOD + if (rightLod > lod) { + // need to flip winding order + buffer.put(getWidth() - 1); + buffer.put(getWidth() * lod - 1); + buffer.put(getWidth() - 1); + } + int idx = getWidth() - 1; + int it = (getWidth() - 1) / topLod; // iterations + int lodDiff = topLod / lod; + for (int i = it; i > 0; i--) { // for each lod level of the neighbour + idx = (i * topLod); + for (int j = 1; j <= lodDiff; j++) { // for each section in that lod level + int idxB = lod * getWidth() + (i * topLod) - (j * lod); + + if (j == lodDiff && i == 1) {// the last one + buffer.put(0); + } else if (j == lodDiff) { + buffer.put(idxB); + buffer.put(idx - topLod); + } else { + buffer.put(idxB); + buffer.put(idx); + } + } + } + } else { + if (rightLod > lod) { + buffer.put(getWidth() - 1); + } + for (int col = getWidth() - 1 - lod; col > 0; col -= lod) { + int idx = col + (lod * getWidth()); + buffer.put(idx); + idx = col; + buffer.put(idx); + } + buffer.put(0); + } + buffer.put(0); + + //System.out.println("\nbuffer top: "+(buffer.getCount()-runningBufferCount)); + //runningBufferCount = buffer.getCount(); + + //System.out.println("\nleft:"); + + // left + if (leftLod > lod) { // if lower LOD + + int idx = 0; + int it = (getWidth() - 1) / leftLod; // iterations + int lodDiff = leftLod / lod; + for (int i = 0; i < it; i++) { // for each lod level of the neighbour + idx = getWidth() * (i * leftLod); + for (int j = 1; j <= lodDiff; j++) { // for each section in that lod level + int idxB = idx + (getWidth() * (j * lod)) + lod; + + if (j == lodDiff && i == it - 1) {// the last one + buffer.put(getWidth() * getWidth() - getWidth()); + } else if (j == lodDiff) { + buffer.put(idxB); + buffer.put(idxB - lod); + } else { + buffer.put(idxB); + buffer.put(idx); + } + } + } + + } else { + buffer.put(0); + buffer.put(getWidth() * lod + lod); + buffer.put(0); + for (int row = lod; row < getWidth() - lod; row += lod) { + int idx = row * getWidth(); + buffer.put(idx); + idx = row * getWidth() + lod; + buffer.put(idx); + } + buffer.put(getWidth() * (getWidth() - 1)); + } + //buffer.put(getWidth()*(getWidth()-1)); + + + //System.out.println("\nbuffer left: "+(buffer.getCount()-runningBufferCount)); + //runningBufferCount = buffer.getCount(); + + //if (true) return buffer.delegate; + //System.out.println("\nbottom"); + + // bottom + if (bottomLod > lod) { // if lower LOD + if (leftLod > lod) { + buffer.put(getWidth() * (getWidth() - 1)); + buffer.put(getWidth() * (getWidth() - lod)); + buffer.put(getWidth() * (getWidth() - 1)); + } + + int idx = getWidth() * getWidth() - getWidth(); + int it = (getWidth() - 1) / bottomLod; // iterations + int lodDiff = bottomLod / lod; + for (int i = 0; i < it; i++) { // for each lod level of the neighbour + idx = getWidth() * getWidth() - getWidth() + (i * bottomLod); + for (int j = 1; j <= lodDiff; j++) { // for each section in that lod level + int idxB = idx - (getWidth() * lod) + j * lod; + + if (j == lodDiff && i == it - 1) {// the last one + buffer.put(getWidth() * getWidth() - 1); + } else if (j == lodDiff) { + buffer.put(idxB); + buffer.put(idx + bottomLod); + } else { + buffer.put(idxB); + buffer.put(idx); + } + } + } + } else { + if (leftLod > lod) { + buffer.put(getWidth() * (getWidth() - 1)); + buffer.put(getWidth() * getWidth() - (getWidth() * lod) + lod); + buffer.put(getWidth() * (getWidth() - 1)); + } + for (int col = lod; col < getWidth() - lod; col += lod) { + int idx = getWidth() * (getWidth() - 1 - lod) + col; // up + buffer.put(idx); + idx = getWidth() * (getWidth() - 1) + col; // down + buffer.put(idx); + } + //buffer.put(getWidth()*getWidth()-1-lod); // <-- THIS caused holes at the end! + } + + buffer.put(getWidth() * getWidth() - 1); + + //System.out.println("\nbuffer bottom: "+(buffer.getCount()-runningBufferCount)); + //runningBufferCount = buffer.getCount(); + + //System.out.println("\nBuffer size: "+buffer.getCount()); + + // fill in the rest of the buffer with degenerates, there should only be a couple + for (int i = buffer.getCount(); i < numIndexes; i++) { + buffer.put(getWidth() * getWidth() - 1); + } + + return buffer.delegate; + } + + + /*private int calculateNumIndexesNormal(int lod) { + int length = getWidth()-1; + int num = ((length/lod)+1)*((length/lod)+1)*2; + System.out.println("num: "+num); + num -= 2*((length/lod)+1); + System.out.println("num2: "+num); + // now get the degenerate indexes that exist between strip rows + num += 2*(((length/lod)+1)-2); // every row except the first and last + System.out.println("Index buffer size: "+num); + return num; + }*/ + /** + * calculate how many indexes there will be. + * This isn't that precise and there might be a couple extra. + */ + private int calculateNumIndexesLodDiff(int lod) { + if (lod == 0) { + lod = 1; + } + int length = getWidth() - 1; // make it even for lod calc + int side = (length / lod) + 1 - (2); + //System.out.println("side: "+side); + int num = side * side * 2; + //System.out.println("num: "+num); + num -= 2 * side; // remove one first row and one last row (they are only hit once each) + //System.out.println("num2: "+num); + // now get the degenerate indexes that exist between strip rows + int degenerates = 2 * (side - (2)); // every row except the first and last + num += degenerates; + //System.out.println("degenerates: "+degenerates); + + //System.out.println("center, before edges: "+num); + + num += (getWidth() / lod) * 2 * 4; + num++; + + num += 10;// TODO remove me: extra + //System.out.println("Index buffer size: "+num); + return num; + } + + public FloatBuffer[] writeTangentArray(FloatBuffer normalBuffer, FloatBuffer tangentStore, FloatBuffer binormalStore, FloatBuffer textureBuffer, Vector3f scale) { + if (!isLoaded()) { + throw new NullPointerException(); + } + + if (tangentStore != null) { + if (tangentStore.remaining() < getWidth() * getHeight() * 3) { + throw new BufferUnderflowException(); + } + } else { + tangentStore = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3); + } + tangentStore.rewind(); + + if (binormalStore != null) { + if (binormalStore.remaining() < getWidth() * getHeight() * 3) { + throw new BufferUnderflowException(); + } + } else { + binormalStore = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3); + } + binormalStore.rewind(); + + Vector3f normal = new Vector3f(); + Vector3f tangent = new Vector3f(); + Vector3f binormal = new Vector3f(); + /*Vector3f v1 = new Vector3f(); + Vector3f v2 = new Vector3f(); + Vector3f v3 = new Vector3f(); + Vector2f t1 = new Vector2f(); + Vector2f t2 = new Vector2f(); + Vector2f t3 = new Vector2f();*/ + + for (int r = 0; r < getHeight(); r++) { + for (int c = 0; c < getWidth(); c++) { + + int idx = (r * getWidth() + c) * 3; + normal.set(normalBuffer.get(idx), normalBuffer.get(idx+1), normalBuffer.get(idx+2)); + tangent.set(normal.cross(new Vector3f(0,0,1))); + binormal.set(new Vector3f(1,0,0).cross(normal)); + + BufferUtils.setInBuffer(tangent.normalizeLocal(), tangentStore, (r * getWidth() + c)); // save the tangent + BufferUtils.setInBuffer(binormal.normalizeLocal(), binormalStore, (r * getWidth() + c)); // save the binormal + } + } + +/* for (int r = 0; r < getHeight(); r++) { + for (int c = 0; c < getWidth(); c++) { + + int texIdx = ((getHeight() - 1 - r) * getWidth() + c) * 2; // pull from the end + int texIdxAbove = ((getHeight() - 1 - (r - 1)) * getWidth() + c) * 2; // pull from the end + int texIdxNext = ((getHeight() - 1 - (r + 1)) * getWidth() + c) * 2; // pull from the end + + v1.set(c, getValue(c, r), r); + t1.set(textureBuffer.get(texIdx), textureBuffer.get(texIdx + 1)); + + // below + if (r == getHeight()-1) { // last row + v3.set(c, getValue(c, r), r + 1); + float u = textureBuffer.get(texIdx) - textureBuffer.get(texIdxAbove); + u += textureBuffer.get(texIdx); + float v = textureBuffer.get(texIdx + 1) - textureBuffer.get(texIdxAbove + 1); + v += textureBuffer.get(texIdx + 1); + t3.set(u, v); + } else { + v3.set(c, getValue(c, r + 1), r + 1); + t3.set(textureBuffer.get(texIdxNext), textureBuffer.get(texIdxNext + 1)); + } + + //right + if (c == getWidth()-1) { // last column + v2.set(c + 1, getValue(c, r), r); + float u = textureBuffer.get(texIdx) - textureBuffer.get(texIdx - 2); + u += textureBuffer.get(texIdx); + float v = textureBuffer.get(texIdx + 1) - textureBuffer.get(texIdx - 1); + v += textureBuffer.get(texIdx - 1); + t2.set(u, v); + } else { + v2.set(c + 1, getValue(c + 1, r), r); // one to the right + t2.set(textureBuffer.get(texIdx + 2), textureBuffer.get(texIdx + 3)); + } + + calculateTangent(new Vector3f[]{v1.mult(scale), v2.mult(scale), v3.mult(scale)}, new Vector2f[]{t1, t2, t3}, tangent, binormal); + BufferUtils.setInBuffer(tangent, tangentStore, (r * getWidth() + c)); // save the tangent + BufferUtils.setInBuffer(binormal, binormalStore, (r * getWidth() + c)); // save the binormal + } + } + */ + return new FloatBuffer[]{tangentStore, binormalStore}; + } + + /** + * + * @param v Takes 3 vertices: root, right, bottom + * @param t Takes 3 tex coords: root, right, bottom + * @param tangent that will store the result + * @return the tangent store + */ + public static Vector3f calculateTangent(Vector3f[] v, Vector2f[] t, Vector3f tangent, Vector3f binormal) { + Vector3f edge1 = new Vector3f(); // y=0 + Vector3f edge2 = new Vector3f(); // x=0 + Vector2f edge1uv = new Vector2f(); // y=0 + Vector2f edge2uv = new Vector2f(); // x=0 + + t[2].subtract(t[0], edge2uv); + t[1].subtract(t[0], edge1uv); + + float det = edge1uv.x * edge2uv.y;// - edge1uv.y*edge2uv.x; = 0 + + boolean normalize = true; + if (Math.abs(det) < 0.0000001f) { + det = 1; + normalize = true; + } + + v[1].subtract(v[0], edge1); + v[2].subtract(v[0], edge2); + + tangent.set(edge1); + tangent.normalizeLocal(); + binormal.set(edge2); + binormal.normalizeLocal(); + + float factor = 1 / det; + tangent.x = (edge2uv.y * edge1.x) * factor; + tangent.y = 0; + tangent.z = (edge2uv.y * edge1.z) * factor; + if (normalize) { + tangent.normalizeLocal(); + } + + binormal.x = 0; + binormal.y = (edge1uv.x * edge2.y) * factor; + binormal.z = (edge1uv.x * edge2.z) * factor; + if (normalize) { + binormal.normalizeLocal(); + } + + return tangent; + } + + @Override + public FloatBuffer writeNormalArray(FloatBuffer store, Vector3f scale) { + if (!isLoaded()) { + throw new NullPointerException(); + } + + if (store != null) { + if (store.remaining() < getWidth() * getHeight() * 3) { + throw new BufferUnderflowException(); + } + } else { + store = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3); + } + store.rewind(); + + TempVars vars = TempVars.get(); + + Vector3f rootPoint = vars.vect1; + Vector3f rightPoint = vars.vect2; + Vector3f leftPoint = vars.vect3; + Vector3f topPoint = vars.vect4; + Vector3f bottomPoint = vars.vect5; + + Vector3f tmp1 = vars.vect6; + + // calculate normals for each polygon + for (int r = 0; r < getHeight(); r++) { + for (int c = 0; c < getWidth(); c++) { + + rootPoint.set(0, getValue(c, r), 0); + Vector3f normal = vars.vect8; + + if (r == 0) { // first row + if (c == 0) { // first column + rightPoint.set(1, getValue(c + 1, r), 0); + bottomPoint.set(0, getValue(c, r + 1), 1); + getNormal(bottomPoint, rootPoint, rightPoint, scale, normal); + } else if (c == getWidth() - 1) { // last column + leftPoint.set(-1, getValue(c - 1, r), 0); + bottomPoint.set(0, getValue(c, r + 1), 1); + getNormal(leftPoint, rootPoint, bottomPoint, scale, normal); + } else { // all middle columns + leftPoint.set(-1, getValue(c - 1, r), 0); + rightPoint.set(1, getValue(c + 1, r), 0); + bottomPoint.set(0, getValue(c, r + 1), 1); + + normal.set( getNormal(leftPoint, rootPoint, bottomPoint, scale, tmp1) ); + normal.addLocal( getNormal(bottomPoint, rootPoint, rightPoint, scale, tmp1) ); + } + } else if (r == getHeight() - 1) { // last row + if (c == 0) { // first column + topPoint.set(0, getValue(c, r - 1), -1); + rightPoint.set(1, getValue(c + 1, r), 0); + getNormal(rightPoint, rootPoint, topPoint, scale, normal); + } else if (c == getWidth() - 1) { // last column + topPoint.set(0, getValue(c, r - 1), -1); + leftPoint.set(-1, getValue(c - 1, r), 0); + getNormal(topPoint, rootPoint, leftPoint, scale, normal); + } else { // all middle columns + topPoint.set(0, getValue(c, r - 1), -1); + leftPoint.set(-1, getValue(c - 1, r), 0); + rightPoint.set(1, getValue(c + 1, r), 0); + + normal.set( getNormal(topPoint, rootPoint, leftPoint, scale, tmp1) ); + normal.addLocal( getNormal(rightPoint, rootPoint, topPoint, scale, tmp1) ); + } + } else { // all middle rows + if (c == 0) { // first column + topPoint.set(0, getValue(c, r - 1), -1); + rightPoint.set(1, getValue(c + 1, r), 0); + bottomPoint.set(0, getValue(c, r + 1), 1); + + normal.set( getNormal(rightPoint, rootPoint, topPoint, scale, tmp1) ); + normal.addLocal( getNormal(bottomPoint, rootPoint, rightPoint, scale, tmp1) ); + } else if (c == getWidth() - 1) { // last column + topPoint.set(0, getValue(c, r - 1), -1); + leftPoint.set(-1, getValue(c - 1, r), 0); + bottomPoint.set(0, getValue(c, r + 1), 1); + + normal.set( getNormal(topPoint, rootPoint, leftPoint, scale, tmp1) ); + normal.addLocal( getNormal(leftPoint, rootPoint, bottomPoint, scale, tmp1) ); + } else { // all middle columns + topPoint.set(0, getValue(c, r - 1), -1); + leftPoint.set(-1, getValue(c - 1, r), 0); + rightPoint.set(1, getValue(c + 1, r), 0); + bottomPoint.set(0, getValue(c, r + 1), 1); + + normal.set( getNormal(topPoint, rootPoint, leftPoint, scale, tmp1 ) ); + normal.addLocal( getNormal(leftPoint, rootPoint, bottomPoint, scale, tmp1) ); + normal.addLocal( getNormal(bottomPoint, rootPoint, rightPoint, scale, tmp1) ); + normal.addLocal( getNormal(rightPoint, rootPoint, topPoint, scale, tmp1) ); + } + } + normal.normalizeLocal(); + BufferUtils.setInBuffer(normal, store, (r * getWidth() + c)); // save the normal + } + } + vars.release(); + + return store; + } + + private Vector3f getNormal(Vector3f firstPoint, Vector3f rootPoint, Vector3f secondPoint, Vector3f scale, Vector3f store) { + float x1 = firstPoint.x - rootPoint.x; + float y1 = firstPoint.y - rootPoint.y; + float z1 = firstPoint.z - rootPoint.z; + x1 *= scale.x; + y1 *= scale.y; + z1 *= scale.z; + float x2 = secondPoint.x - rootPoint.x; + float y2 = secondPoint.y - rootPoint.y; + float z2 = secondPoint.z - rootPoint.z; + x2 *= scale.x; + y2 *= scale.y; + z2 *= scale.z; + float x3 = (y1 * z2) - (z1 * y2); + float y3 = (z1 * x2) - (x1 * z2); + float z3 = (x1 * y2) - (y1 * x2); + + float inv = 1.0f / FastMath.sqrt(x3 * x3 + y3 * y3 + z3 * z3); + store.x = x3 * inv; + store.y = y3 * inv; + store.z = z3 * inv; + + + /*firstPoint.multLocal(scale); + rootPoint.multLocal(scale); + secondPoint.multLocal(scale); + firstPoint.subtractLocal(rootPoint); + secondPoint.subtractLocal(rootPoint); + firstPoint.cross(secondPoint, store);*/ + return store; + } + + /** + * Keeps a count of the number of indexes, good for debugging + */ + public class VerboseBuffer { + + private IndexBuffer delegate; + //private IntBuffer delegateInt; + //private ShortBuffer delegateShort; + int count = 0; + //private boolean intb = true; + + public VerboseBuffer(IndexBuffer d) { + this.delegate = d; + } + + /*public VerboseBuffer(Buffer d) { + if (d instanceof IntBuffer) + delegateInt = (IntBuffer)d; + else if (d instanceof ShortBuffer) { + delegateShort = (ShortBuffer)d; + intb = false; + } + }*/ + + public void put(int value) { + delegate.put(count, value); + count++; + } + /*public void put(int value) { + try { + count++; + int limit = intb? delegateInt.limit() : delegateShort.limit(); + if (count > limit) + throw new BufferOverflowException(); + if (intb) + delegateInt.put(value); + else { + System.out.println(Integer.toString(value)+" "+Short.toString((short)value)); + delegateShort.put((short)value); + } + } catch (BufferOverflowException e) { + Logger.getLogger(this.getClass().getName()).log(Logger.Level.ERROR, "err buffer size: "+delegateInt.capacity()); + } + }*/ + + public int getCount() { + return count; + } + } + + /** + * Get the two triangles that make up the grid section at the specified point. + * + * For every grid space there are two triangles oriented like this: + * *----* + * |a / | + * | / b| + * *----* + * The corners of the mesh have differently oriented triangles. The two + * corners that we have to special-case are the top left and bottom right + * corners. They are oriented inversely: + * *----* + * | \ b| + * |a \ | + * *----* + */ + protected float getHeight(int x, int z, float xm, float zm) { + + int index = findClosestHeightIndex(x, z); + if (index < 0) { + return Float.NaN; + } + + float h1 = hdata[index]; // top left + float h2 = hdata[index + 1]; // top right + float h3 = hdata[index + width]; // bottom left + float h4 = hdata[index + width + 1]; // bottom right + + //float dix = (x % 1f) ; + //float diz = (z % 1f) ; + + if ((x == 0 && z == 0) || (x == width - 2 && z == width - 2)) { + // top left or bottom right grid point + /* 1----2 + * | \ b| + * |a \ | + * 3----4 */ + if (xm= 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/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/LRUCache.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/LRUCache.java new file mode 100644 index 000000000..5d2fde396 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/LRUCache.java @@ -0,0 +1,122 @@ +package com.jme3.terrain.geomipmap; + +// Copyright 2007 Christian d'Heureuse, Inventec Informatik AG, Zurich, +// Switzerland +// www.source-code.biz, www.inventec.ch/chdh +// +// This module is multi-licensed and may be used under the terms +// of any of the following licenses: +// +// EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal +// LGPL, GNU Lesser General Public License, V2 or later, +// http://www.gnu.org/licenses/lgpl.html +// GPL, GNU General Public License, V2 or later, +// http://www.gnu.org/licenses/gpl.html +// AL, Apache License, V2.0 or later, http://www.apache.org/licenses +// BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php +// +// Please contact the author if you need another license. +// This module is provided "as is", without warranties of any kind. +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * An LRU cache, based on LinkedHashMap. + * + *

      + * This cache has a fixed maximum number of elements (cacheSize). + * If the cache is full and another entry is added, the LRU (least recently + * used) entry is dropped. + * + *

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

      + * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
      + * Multi-licensed: EPL / LGPL / GPL / AL / BSD. + */ +public class LRUCache { + + private static final float hashTableLoadFactor = 0.75f; + private LinkedHashMap map; + private int cacheSize; + + /** + * Creates a new LRU cache. + * + * @param cacheSize + * the maximum number of entries that will be kept in this cache. + */ + public LRUCache(int cacheSize) { + this.cacheSize = cacheSize; + int hashTableCapacity = (int) Math.ceil(cacheSize / LRUCache.hashTableLoadFactor) + 1; + this.map = new LinkedHashMap(hashTableCapacity, LRUCache.hashTableLoadFactor, true) { + // (an anonymous inner class) + + private static final long serialVersionUID = 1; + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return this.size() > LRUCache.this.cacheSize; + } + }; + } + + /** + * Retrieves an entry from the cache.
      + * The retrieved entry becomes the MRU (most recently used) entry. + * + * @param key + * the key whose associated value is to be returned. + * @return the value associated to this key, or null if no value with this + * key exists in the cache. + */ + public synchronized V get(K key) { + return this.map.get(key); + } + + /** + * Adds an entry to this cache. + * The new entry becomes the MRU (most recently used) entry. + * If an entry with the specified key already exists in the cache, it is + * replaced by the new entry. + * If the cache is full, the LRU (least recently used) entry is removed from + * the cache. + * + * @param key + * the key with which the specified value is to be associated. + * @param value + * a value to be associated with the specified key. + */ + public synchronized void put(K key, V value) { + this.map.put(key, value); + } + + /** + * Clears the cache. + */ + public synchronized void clear() { + this.map.clear(); + } + + /** + * Returns the number of used entries in the cache. + * + * @return the number of entries currently in the cache. + */ + public synchronized int usedEntries() { + return this.map.size(); + } + + /** + * Returns a Collection that contains a copy of all cache + * entries. + * + * @return a Collection with a copy of the cache content. + */ + public synchronized Collection> getAll() { + return new ArrayList>(this.map.entrySet()); + } +} // end class LRUCache diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java new file mode 100644 index 000000000..995bc8f13 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/MultiTerrainLodControl.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap; + +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * An extension of the TerrainLodControl that handles + * multiple terrains at once. This is to be used if you + * have your own tiling/paging terrain system, such as + * TerrainGrid. + * + * @author Brent Owens + */ +public class MultiTerrainLodControl extends TerrainLodControl { + + List terrains = new ArrayList(); + private List addedTerrains = new ArrayList(); + private List removedTerrains = new ArrayList(); + + public MultiTerrainLodControl(List cameras) { + this.cameras = cameras; + lodCalculator = new DistanceLodCalculator(65, 2.7f); + } + + public MultiTerrainLodControl(Camera camera) { + List cams = new ArrayList(); + cams.add(camera); + this.cameras = cams; + lodCalculator = new DistanceLodCalculator(65, 2.7f); + } + + /** + * Add a terrain that will have its LOD handled by this control. + * It will be added next update run. You should only call this from + * the render thread. + */ + public void addTerrain(TerrainQuad tq) { + addedTerrains.add(tq); + } + + /** + * Add a terrain that will no longer have its LOD handled by this control. + * It will be removed next update run. You should only call this from + * the render thread. + */ + public void removeTerrain(TerrainQuad tq) { + removedTerrains.add(tq); + } + + @Override + protected UpdateLOD getLodThread(List locations, LodCalculator lodCalculator) { + return new UpdateMultiLOD(locations, lodCalculator); + } + + @Override + protected void prepareTerrain() { + if (!addedTerrains.isEmpty()) { + for (TerrainQuad t : addedTerrains) { + if (!terrains.contains(t)) + terrains.add(t); + } + addedTerrains.clear(); + } + + if (!removedTerrains.isEmpty()) { + terrains.removeAll(removedTerrains); + removedTerrains.clear(); + } + + for (TerrainQuad terrain : terrains) + terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely + } + + /** + * Overrides the parent UpdateLOD runnable to process + * multiple terrains. + */ + protected class UpdateMultiLOD extends UpdateLOD { + + + protected UpdateMultiLOD(List camLocations, LodCalculator lodCalculator) { + super(camLocations, lodCalculator); + } + + @Override + public HashMap call() throws Exception { + + setLodCalcRunning(true); + + HashMap updated = new HashMap(); + + for (TerrainQuad terrainQuad : terrains) { + // go through each patch and calculate its LOD based on camera distance + terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here + } + + for (TerrainQuad terrainQuad : terrains) { + // then calculate the neighbour LOD values for seaming + terrainQuad.findNeighboursLod(updated); + } + + for (TerrainQuad terrainQuad : terrains) { + // check neighbour quads that need their edges seamed + terrainQuad.fixEdges(updated); + } + + for (TerrainQuad terrainQuad : terrains) { + // perform the edge seaming, if it requires it + terrainQuad.reIndexPages(updated, lodCalculator.usesVariableLod()); + } + + //setUpdateQuadLODs(updated); // set back to main ogl thread + setLodCalcRunning(false); + + return updated; + } + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NeighbourFinder.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NeighbourFinder.java new file mode 100644 index 000000000..ccaf8d1d5 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NeighbourFinder.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap; + +/** + * Used for TerrainQuad to find neighbours that are not part of the + * same quad tree. Normally TerrainQuads function in a quad tree and + * use the neighbour methods getRightQuad, getLeftQuad etc. to update + * LOD values of the terrain (and for some other routines). + * + * With this you can have a parent, control or spatial, that manages a group of + * TerrainQuads by linking them together through these four methods. + * + * The general orientation of TerrainQuads and their sub-quads is as such: + * + * + * +-- x+ ----> + * | + * | 1 | 3 (quadrants) + * z+ --+-- + * | 2 | 4 + * | + * \/ + * + * Your implementation will still have to manage getHeight, getNormal, and + * most other Terrain.java interface methods; often by offsetting the XZ + * coordinate parameters. + * + * @author sploreg + */ +public interface NeighbourFinder { + + /** + * Get the TerrainQuad to the right of the supplied 'center' quad. + */ + public TerrainQuad getRightQuad(TerrainQuad center); + + /** + * Get the TerrainQuad to the left of the supplied 'center' quad. + */ + public TerrainQuad getLeftQuad(TerrainQuad center); + + /** + * Get the TerrainQuad above the supplied 'center' quad. + */ + public TerrainQuad getTopQuad(TerrainQuad center); + + /** + * Get the TerrainQuad below the supplied 'center' quad. + */ + public TerrainQuad getDownQuad(TerrainQuad center); +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java new file mode 100644 index 000000000..4c41e739e --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap; + +import com.jme3.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/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGrid.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGrid.java new file mode 100644 index 000000000..50e1f9f73 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGrid.java @@ -0,0 +1,565 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap; + +import com.jme3.bounding.BoundingBox; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.UpdateControl; +import com.jme3.terrain.Terrain; +import com.jme3.terrain.heightmap.HeightMap; +import com.jme3.terrain.heightmap.HeightMapGrid; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + *

      + * TerrainGrid itself is an actual TerrainQuad. Its four children are the visible four tiles.

      + *

      + * The grid is indexed by cells. Each cell has an integer XZ coordinate originating at 0,0. + * TerrainGrid will piggyback on the TerrainLodControl so it can use the camera for its + * updates as well. It does this in the overwritten update() method. + *

      + * It uses an LRU (Least Recently Used) cache of 16 terrain tiles (full TerrainQuadTrees). The + * center 4 are the ones that are visible. As the camera moves, it checks what camera cell it is in + * and will attach the now visible tiles. + *

      + * The 'quadIndex' variable is a 4x4 array that represents the tiles. The center + * four (index numbers: 5, 6, 9, 10) are what is visible. Each quadIndex value is an + * offset vector. The vector contains whole numbers and represents how many tiles in offset + * this location is from the center of the map. So for example the index 11 [Vector3f(2, 0, 1)] + * is located 2*terrainSize in X axis and 1*terrainSize in Z axis. + *

      + * As the camera moves, it tests what cameraCell it is in. Each camera cell covers four quad tiles + * and is half way inside each one. + *

      + * +-------+-------+
      + * | 1     |     3 |    Four terrainQuads that make up the grid
      + * |    *..|..*    |    with the cameraCell in the middle, covering
      + * |----|--|--|----|    all four quads.
      + * |    *..|..*    |
      + * | 2     |     4 |
      + * +-------+-------+
      + * 

      + * This results in the effect of when the camera gets half way across one of the sides of a quad to + * an empty (non-loaded) area, it will trigger the system to load in the next tiles. + *

      + * The tile loading is done on a background thread, and once the tile is loaded, then it is + * attached to the qrid quad tree, back on the OGL thread. It will grab the terrain quad from + * the LRU cache if it exists. If it does not exist, it will load in the new TerrainQuad tile. + *

      + * The loading of new tiles triggers events for any TerrainGridListeners. The events are: + *

        + *
      • tile Attached + *
      • tile Detached + *
      • grid moved. + *
      + *

      + * These allow physics to update, and other operation (often needed for loading the terrain) to occur + * at the right time. + *

      + * @author Anthyon + */ +public class TerrainGrid extends TerrainQuad { + protected static final Logger log = Logger.getLogger(TerrainGrid.class.getCanonicalName()); + protected Vector3f currentCamCell = Vector3f.ZERO; + protected int quarterSize; // half of quadSize + protected int quadSize; + protected HeightMapGrid heightMapGrid; + private TerrainGridTileLoader gridTileLoader; + protected Vector3f[] quadIndex; + protected Set listeners = new HashSet(); + protected Material material; + //cache needs to be 1 row (4 cells) larger than what we care is cached + protected LRUCache cache = new LRUCache(20); + protected int cellsLoaded = 0; + protected int[] gridOffset; + protected boolean runOnce = false; + protected ExecutorService cacheExecutor; + + protected class UpdateQuadCache implements Runnable { + + protected final Vector3f location; + + public UpdateQuadCache(Vector3f location) { + this.location = location; + } + + /** + * This is executed if the camera has moved into a new CameraCell and will load in + * the new TerrainQuad tiles to be children of this TerrainGrid parent. + * It will first check the LRU cache to see if the terrain tile is already there, + * if it is not there, it will load it in and then cache that tile. + * The terrain tiles get added to the quad tree back on the OGL thread using the + * attachQuadAt() method. It also resets any cached values in TerrainQuad (such as + * neighbours). + */ + public void run() { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + int quadIdx = i * 4 + j; + final Vector3f quadCell = location.add(quadIndex[quadIdx]); + TerrainQuad q = cache.get(quadCell); + if (q == null) { + if (heightMapGrid != null) { + // create the new Quad since it doesn't exist + HeightMap heightMapAt = heightMapGrid.getHeightMapAt(quadCell); + q = new TerrainQuad(getName() + "Quad" + quadCell, patchSize, quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap()); + q.setMaterial(material.clone()); + log.log(Level.FINE, "Loaded TerrainQuad {0} from HeightMapGrid", q.getName()); + } else if (gridTileLoader != null) { + q = gridTileLoader.getTerrainQuadAt(quadCell); + // only clone the material to the quad if it doesn't have a material of its own + if(q.getMaterial()==null) q.setMaterial(material.clone()); + log.log(Level.FINE, "Loaded TerrainQuad {0} from TerrainQuadGrid", q.getName()); + } + } + cache.put(quadCell, q); + + + final int quadrant = getQuadrant(quadIdx); + final TerrainQuad newQuad = q; + + if (isCenter(quadIdx)) { + // if it should be attached as a child right now, attach it + getControl(UpdateControl.class).enqueue(new Callable() { + // back on the OpenGL thread: + public Object call() throws Exception { + if (newQuad.getParent() != null) { + attachQuadAt(newQuad, quadrant, quadCell, true); + } + else { + attachQuadAt(newQuad, quadrant, quadCell, false); + } + return null; + } + }); + } else { + getControl(UpdateControl.class).enqueue(new Callable() { + public Object call() throws Exception { + removeQuad(newQuad); + return null; + } + }); + } + } + } + + getControl(UpdateControl.class).enqueue(new Callable() { + // back on the OpenGL thread: + public Object call() throws Exception { + for (Spatial s : getChildren()) { + if (s instanceof TerrainQuad) { + TerrainQuad tq = (TerrainQuad)s; + tq.resetCachedNeighbours(); + } + } + System.out.println("fixed normals "+location.clone().mult(size)); + setNeedToRecalculateNormals(); + return null; + } + }); + } + } + + protected boolean isCenter(int quadIndex) { + return quadIndex == 9 || quadIndex == 5 || quadIndex == 10 || quadIndex == 6; + } + + protected int getQuadrant(int quadIndex) { + if (quadIndex == 5) { + return 1; + } else if (quadIndex == 9) { + return 2; + } else if (quadIndex == 6) { + return 3; + } else if (quadIndex == 10) { + return 4; + } + return 0; // error + } + + public TerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, TerrainGridTileLoader terrainQuadGrid, + Vector2f offset, float offsetAmount) { + this.name = name; + this.patchSize = patchSize; + this.size = maxVisibleSize; + this.stepScale = scale; + this.offset = offset; + this.offsetAmount = offsetAmount; + initData(); + this.gridTileLoader = terrainQuadGrid; + terrainQuadGrid.setPatchSize(this.patchSize); + terrainQuadGrid.setQuadSize(this.quadSize); + addControl(new UpdateControl()); + + fixNormalEdges(new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2)); + addControl(new NormalRecalcControl(this)); + } + + public TerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, TerrainGridTileLoader terrainQuadGrid) { + this(name, patchSize, maxVisibleSize, scale, terrainQuadGrid, new Vector2f(), 0); + } + + public TerrainGrid(String name, int patchSize, int maxVisibleSize, TerrainGridTileLoader terrainQuadGrid) { + this(name, patchSize, maxVisibleSize, Vector3f.UNIT_XYZ, terrainQuadGrid); + } + + public TerrainGrid() { + } + + private void initData() { + int maxVisibleSize = size; + this.quarterSize = maxVisibleSize >> 2; + this.quadSize = (maxVisibleSize + 1) >> 1; + this.totalSize = maxVisibleSize; + this.gridOffset = new int[]{0, 0}; + + /* + * -z + * | + * 1|3 + * -x ----+---- x + * 2|4 + * | + * z + */ + this.quadIndex = new Vector3f[]{ + new Vector3f(-1, 0, -1), new Vector3f(0, 0, -1), new Vector3f(1, 0, -1), new Vector3f(2, 0, -1), + new Vector3f(-1, 0, 0), new Vector3f(0, 0, 0), new Vector3f(1, 0, 0), new Vector3f(2, 0, 0), + new Vector3f(-1, 0, 1), new Vector3f(0, 0, 1), new Vector3f(1, 0, 1), new Vector3f(2, 0, 1), + new Vector3f(-1, 0, 2), new Vector3f(0, 0, 2), new Vector3f(1, 0, 2), new Vector3f(2, 0, 2)}; + + } + + /** + * Get the location in cell-coordinates of the specified location. + * Cell coordinates are integer corrdinates, usually with y=0, each + * representing a cell in the world. + * For example, moving right in the +X direction: + * (0,0,0) (1,0,0) (2,0,0), (3,0,0) + * and then down the -Z direction: + * (3,0,-1) (3,0,-2) (3,0,-3) + */ + public Vector3f getCamCell(Vector3f location) { + Vector3f tile = getTileCell(location); + Vector3f offsetHalf = new Vector3f(-0.5f, 0, -0.5f); + Vector3f shifted = tile.subtract(offsetHalf); + return new Vector3f(FastMath.floor(shifted.x), 0, FastMath.floor(shifted.z)); + } + + /** + * Centered at 0,0. + * Get the tile index location in integer form: + * @param location world coordinate + */ + public Vector3f getTileCell(Vector3f location) { + Vector3f tileLoc = location.divide(this.getWorldScale().mult(this.quadSize)); + return tileLoc; + } + + public TerrainGridTileLoader getGridTileLoader() { + return gridTileLoader; + } + + /** + * Get the terrain tile at the specified world location, in XZ coordinates. + */ + public Terrain getTerrainAt(Vector3f worldLocation) { + if (worldLocation == null) + return null; + Vector3f tileCell = getTileCell(worldLocation.setY(0)); + tileCell = new Vector3f(Math.round(tileCell.x), tileCell.y, Math.round(tileCell.z)); + return cache.get(tileCell); + } + + /** + * Get the terrain tile at the specified XZ cell coordinate (not world coordinate). + * @param cellCoordinate integer cell coordinates + * @return the terrain tile at that location + */ + public Terrain getTerrainAtCell(Vector3f cellCoordinate) { + return cache.get(cellCoordinate); + } + + /** + * Convert the world location into a cell location (integer coordinates) + */ + public Vector3f toCellSpace(Vector3f worldLocation) { + Vector3f tileCell = getTileCell(worldLocation); + tileCell = new Vector3f(Math.round(tileCell.x), tileCell.y, Math.round(tileCell.z)); + return tileCell; + } + + /** + * Convert the cell coordinate (integer coordinates) into world coordinates. + */ + public Vector3f toWorldSpace(Vector3f cellLocation) { + return cellLocation.mult(getLocalScale()).multLocal(quadSize - 1); + } + + protected void removeQuad(TerrainQuad q) { + if (q != null && ( (q.getQuadrant() > 0 && q.getQuadrant()<5) || q.getParent() != null) ) { + for (TerrainGridListener l : listeners) { + l.tileDetached(getTileCell(q.getWorldTranslation()), q); + } + q.setQuadrant((short)0); + this.detachChild(q); + cellsLoaded++; // For gridoffset calc., maybe the run() method is a better location for this. + } + } + + /** + * Runs on the rendering thread + * @param shifted quads are still attached to the parent and don't need to re-load + */ + protected void attachQuadAt(TerrainQuad q, int quadrant, Vector3f quadCell, boolean shifted) { + + q.setQuadrant((short) quadrant); + if (!shifted) + this.attachChild(q); + + Vector3f loc = quadCell.mult(this.quadSize - 1).subtract(quarterSize, 0, quarterSize);// quadrant location handled TerrainQuad automatically now + q.setLocalTranslation(loc); + + if (!shifted) { + for (TerrainGridListener l : listeners) { + l.tileAttached(quadCell, q); + } + } + updateModelBound(); + + } + + + /** + * Called when the camera has moved into a new cell. We need to + * update what quads are in the scene now. + * + * Step 1: touch cache + * LRU cache is used, so elements that need to remain + * should be touched. + * + * Step 2: load new quads in background thread + * if the camera has moved into a new cell, we load in new quads + * @param camCell the cell the camera is in + */ + protected void updateChildren(Vector3f camCell) { + + int dx = 0; + int dy = 0; + if (currentCamCell != null) { + dx = (int) (camCell.x - currentCamCell.x); + dy = (int) (camCell.z - currentCamCell.z); + } + + int xMin = 0; + int xMax = 4; + int yMin = 0; + int yMax = 4; + if (dx == -1) { // camera moved to -X direction + xMax = 3; + } else if (dx == 1) { // camera moved to +X direction + xMin = 1; + } + + if (dy == -1) { // camera moved to -Y direction + yMax = 3; + } else if (dy == 1) { // camera moved to +Y direction + yMin = 1; + } + + // Touch the items in the cache that we are and will be interested in. + // We activate cells in the direction we are moving. If we didn't move + // either way in one of the axes (say X or Y axis) then they are all touched. + for (int i = yMin; i < yMax; i++) { + for (int j = xMin; j < xMax; j++) { + cache.get(camCell.add(quadIndex[i * 4 + j])); + } + } + + // --------------------------------------------------- + // --------------------------------------------------- + + if (cacheExecutor == null) { + // use the same executor as the LODControl + cacheExecutor = createExecutorService(); + } + + cacheExecutor.submit(new UpdateQuadCache(camCell)); + + this.currentCamCell = camCell; + } + + public void addListener(TerrainGridListener listener) { + this.listeners.add(listener); + } + + public Vector3f getCurrentCell() { + return this.currentCamCell; + } + + public void removeListener(TerrainGridListener listener) { + this.listeners.remove(listener); + } + + @Override + public void setMaterial(Material mat) { + this.material = mat; + super.setMaterial(mat); + } + + public void setQuadSize(int quadSize) { + this.quadSize = quadSize; + } + + @Override + public void adjustHeight(List xz, List height) { + Vector3f currentGridLocation = getCurrentCell().mult(getLocalScale()).multLocal(quadSize - 1); + for (Vector2f vect : xz) { + vect.x -= currentGridLocation.x; + vect.y -= currentGridLocation.z; + } + super.adjustHeight(xz, height); + } + + @Override + protected float getHeightmapHeight(int x, int z) { + return super.getHeightmapHeight(x - gridOffset[0], z - gridOffset[1]); + } + + @Override + public int getNumMajorSubdivisions() { + return 2; + } + + @Override + public Material getMaterial(Vector3f worldLocation) { + if (worldLocation == null) + return null; + Vector3f tileCell = getTileCell(worldLocation); + Terrain terrain = cache.get(tileCell); + if (terrain == null) + return null; // terrain not loaded for that cell yet! + return terrain.getMaterial(worldLocation); + } + + /** + * This will print out any exceptions from the thread + */ + protected ExecutorService createExecutorService() { + final ThreadFactory threadFactory = new ThreadFactory() { + public Thread newThread(Runnable r) { + Thread th = new Thread(r); + th.setName("jME TerrainGrid Thread"); + th.setDaemon(true); + return th; + } + }; + ThreadPoolExecutor ex = new ThreadPoolExecutor(1, 1, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(), + threadFactory) { + protected void afterExecute(Runnable r, Throwable t) { + super.afterExecute(r, t); + if (t == null && r instanceof Future) { + try { + Future future = (Future) r; + if (future.isDone()) + future.get(); + } catch (CancellationException ce) { + t = ce; + } catch (ExecutionException ee) { + t = ee.getCause(); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); // ignore/reset + } + } + if (t != null) + t.printStackTrace(); + } + }; + return ex; + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule c = im.getCapsule(this); + name = c.readString("name", null); + size = c.readInt("size", 0); + patchSize = c.readInt("patchSize", 0); + stepScale = (Vector3f) c.readSavable("stepScale", null); + offset = (Vector2f) c.readSavable("offset", null); + offsetAmount = c.readFloat("offsetAmount", 0); + gridTileLoader = (TerrainGridTileLoader) c.readSavable("terrainQuadGrid", null); + material = (Material) c.readSavable("material", null); + initData(); + if (gridTileLoader != null) { + gridTileLoader.setPatchSize(this.patchSize); + gridTileLoader.setQuadSize(this.quadSize); + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule c = ex.getCapsule(this); + c.write(gridTileLoader, "terrainQuadGrid", null); + c.write(size, "size", 0); + c.write(patchSize, "patchSize", 0); + c.write(stepScale, "stepScale", null); + c.write(offset, "offset", null); + c.write(offsetAmount, "offsetAmount", 0); + c.write(material, "material", null); + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridListener.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridListener.java new file mode 100644 index 000000000..cb25cae21 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridListener.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap; + +import com.jme3.math.Vector3f; + +/** + * Notifies the user of grid change events, such as moving to new grid cells. + * @author Anthyon + */ +public interface TerrainGridListener { + + /** + * Called whenever the camera has moved full grid cells. This triggers new tiles to load. + * @param newCenter + */ + public void gridMoved(Vector3f newCenter); + + /** + * Called when a TerrainQuad is attached to the scene and is visible (attached to the root TerrainGrid) + * @param cell the cell that is moved into + * @param quad the quad that was just attached + */ + public void tileAttached( Vector3f cell, TerrainQuad quad ); + + /** + * Called when a TerrainQuad is detached from its TerrainGrid parent: it is no longer on the scene graph. + * @param cell the cell that is moved into + * @param quad the quad that was just detached + */ + public void tileDetached( Vector3f cell, TerrainQuad quad ); +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridLodControl.java new file mode 100644 index 000000000..65a209be3 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridLodControl.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap; + +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.terrain.Terrain; +import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; +import java.util.List; + +/** + * Updates grid offsets and cell positions. + * As well as updating LOD. + * + * @author sploreg + */ +public class TerrainGridLodControl extends TerrainLodControl { + + public TerrainGridLodControl(Terrain terrain, Camera camera) { + super(terrain, camera); + } + + @Override + protected void updateLOD(List locations, LodCalculator lodCalculator) { + TerrainGrid terrainGrid = (TerrainGrid)getSpatial(); + + // for now, only the first camera is handled. + // to accept more, there are two ways: + // 1: every camera has an associated grid, then the location is not enough to identify which camera location has changed + // 2: grids are associated with locations, and no incremental update is done, we load new grids for new locations, and unload those that are not needed anymore + Vector3f cam = locations.isEmpty() ? Vector3f.ZERO.clone() : locations.get(0); + Vector3f camCell = terrainGrid.getCamCell(cam); // get the grid index value of where the camera is (ie. 2,1) + if (terrainGrid.cellsLoaded > 1) { // Check if cells are updated before updating gridoffset. + terrainGrid.gridOffset[0] = Math.round(camCell.x * (terrainGrid.size / 2)); + terrainGrid.gridOffset[1] = Math.round(camCell.z * (terrainGrid.size / 2)); + terrainGrid.cellsLoaded = 0; + } + if (camCell.x != terrainGrid.currentCamCell.x || camCell.z != terrainGrid.currentCamCell.z || !terrainGrid.runOnce) { + // if the camera has moved into a new cell, load new terrain into the visible 4 center quads + terrainGrid.updateChildren(camCell); + for (TerrainGridListener l : terrainGrid.listeners) { + l.gridMoved(camCell); + } + } + terrainGrid.runOnce = true; + super.updateLOD(locations, lodCalculator); + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridTileLoader.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridTileLoader.java new file mode 100644 index 000000000..7ff1ce043 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridTileLoader.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap; + +import com.jme3.export.Savable; +import com.jme3.math.Vector3f; + +/** + * + * @author normenhansen + */ +public interface TerrainGridTileLoader extends Savable { + + public TerrainQuad getTerrainQuadAt(Vector3f location); + + public void setPatchSize(int patchSize); + + public void setQuadSize(int quadSize); +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java new file mode 100644 index 000000000..96bedc21f --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.control.Control; +import com.jme3.terrain.Terrain; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Tells the terrain to update its Level of Detail. + * It needs the cameras to do this, and there could possibly + * be several cameras in the scene, so it accepts a list + * of cameras. + * NOTE: right now it just uses the first camera passed in, + * in the future it will use all of them to determine what + * LOD to set. + * + * This control serializes, but it does not save the Camera reference. + * This camera reference has to be manually added in when you load the + * terrain to the scene! + * + * When the control or the terrain are removed from the scene, you should call + * TerrainLodControl.detachAndCleanUpControl() to remove any threads it created + * to handle the LOD processing. If you supply your own executor service, then + * you have to handle its thread termination yourself. + * + * @author Brent Owens + */ +public class TerrainLodControl extends AbstractControl { + + private Terrain terrain; + protected List cameras; + private List cameraLocations = new ArrayList(); + protected LodCalculator lodCalculator; + private boolean hasResetLod = false; // used when enabled is set to false + + private HashMap updatedPatches; + private final Object updatePatchesLock = new Object(); + + protected List lastCameraLocations; // used for LOD calc + private AtomicBoolean lodCalcRunning = new AtomicBoolean(false); + private int lodOffCount = 0; + + protected ExecutorService executor; + protected Future> indexer; + private boolean forceUpdate = true; + + public TerrainLodControl() { + } + + public TerrainLodControl(Terrain terrain, Camera camera) { + List cams = new ArrayList(); + cams.add(camera); + this.terrain = terrain; + this.cameras = cams; + lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator + } + + /** + * Only uses the first camera right now. + * @param terrain to act upon (must be a Spatial) + * @param cameras one or more cameras to reference for LOD calc + */ + public TerrainLodControl(Terrain terrain, List cameras) { + this.terrain = terrain; + this.cameras = cameras; + lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } + + /** + * Set your own custom executor to be used. The control will use + * this instead of creating its own. + */ + public void setExecutor(ExecutorService executor) { + this.executor = executor; + } + + protected ExecutorService createExecutorService() { + return Executors.newSingleThreadExecutor(new ThreadFactory() { + public Thread newThread(Runnable r) { + Thread th = new Thread(r); + th.setName("jME Terrain Thread"); + th.setDaemon(true); + return th; + } + }); + } + + @Override + protected void controlUpdate(float tpf) { + //list of cameras for when terrain supports multiple cameras (ie split screen) + + if (lodCalculator == null) + return; + + if (!enabled) { + if (!hasResetLod) { + // this will get run once + hasResetLod = true; + lodCalculator.turnOffLod(); + } + } + + if (cameras != null) { + if (cameraLocations.isEmpty() && !cameras.isEmpty()) { + for (Camera c : cameras) // populate them + { + cameraLocations.add(c.getLocation()); + } + } + updateLOD(cameraLocations, lodCalculator); + } + } + + /** + * Call this when you remove the terrain or this control from the scene. + * It will clear up any threads it had. + */ + public void detachAndCleanUpControl() { + if (executor != null) + executor.shutdownNow(); + getSpatial().removeControl(this); + } + + // do all of the LOD calculations + protected void updateLOD(List locations, LodCalculator lodCalculator) { + if(getSpatial() == null){ + return; + } + + // update any existing ones that need updating + updateQuadLODs(); + + if (lodCalculator.isLodOff()) { + // we want to calculate the base lod at least once + if (lodOffCount == 1) + return; + else + lodOffCount++; + } else + lodOffCount = 0; + + if (lastCameraLocations != null) { + if (!forceUpdate && lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff()) + return; // don't update if in same spot + else + lastCameraLocations = cloneVectorList(locations); + forceUpdate = false; + } + else { + lastCameraLocations = cloneVectorList(locations); + return; + } + + if (isLodCalcRunning()) { + return; + } + setLodCalcRunning(true); + + if (executor == null) + executor = createExecutorService(); + + prepareTerrain(); + + UpdateLOD updateLodThread = getLodThread(locations, lodCalculator); + indexer = executor.submit(updateLodThread); + } + + /** + * Force the LOD to update instantly, does not wait for the camera to move. + * It will reset once it has updated. + */ + public void forceUpdate() { + this.forceUpdate = true; + } + + protected void prepareTerrain() { + TerrainQuad terrain = (TerrainQuad)getSpatial(); + terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely + } + + protected UpdateLOD getLodThread(List locations, LodCalculator lodCalculator) { + return new UpdateLOD(locations, lodCalculator); + } + + /** + * Back on the ogl thread: update the terrain patch geometries + */ + private void updateQuadLODs() { + if (indexer != null) { + if (indexer.isDone()) { + try { + + HashMap updated = indexer.get(); + if (updated != null) { + // do the actual geometry update here + for (UpdatedTerrainPatch utp : updated.values()) { + utp.updateAll(); + } + } + + } catch (InterruptedException ex) { + Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex); + } catch (ExecutionException ex) { + Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex); + } finally { + indexer = null; + } + } + } + } + + 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; + } + + protected synchronized boolean isLodCalcRunning() { + return lodCalcRunning.get(); + } + + protected synchronized void setLodCalcRunning(boolean running) { + lodCalcRunning.set(running); + } + + private List cloneVectorList(List locations) { + List cloned = new ArrayList(); + for(Vector3f l : locations) + cloned.add(l.clone()); + return cloned; + } + + + + + + + + public Control cloneForSpatial(Spatial spatial) { + if (spatial instanceof Terrain) { + List cameraClone = new ArrayList(); + if (cameras != null) { + for (Camera c : cameras) { + cameraClone.add(c); + } + } + TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, cameraClone); + cloned.setLodCalculator(lodCalculator.clone()); + return cloned; + } + return null; + } + + public void setCamera(Camera camera) { + List cams = new ArrayList(); + cams.add(camera); + setCameras(cams); + } + + 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 Terrain) { + this.terrain = (Terrain) spatial; + } + } + + public void setTerrain(Terrain terrain) { + this.terrain = terrain; + } + + public LodCalculator getLodCalculator() { + return lodCalculator; + } + + public void setLodCalculator(LodCalculator lodCalculator) { + this.lodCalculator = lodCalculator; + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if (!enabled) { + // reset the lod levels to max detail for the terrain + hasResetLod = false; + } else { + hasResetLod = true; + lodCalculator.turnOnLod(); + } + } + + + /** + * Calculates the LOD of all child terrain patches. + */ + protected class UpdateLOD implements Callable> { + protected List camLocations; + protected LodCalculator lodCalculator; + + protected UpdateLOD(List camLocations, LodCalculator lodCalculator) { + this.camLocations = camLocations; + this.lodCalculator = lodCalculator; + } + + public HashMap call() throws Exception { + //long start = System.currentTimeMillis(); + //if (isLodCalcRunning()) { + // return null; + //} + setLodCalcRunning(true); + + TerrainQuad terrainQuad = (TerrainQuad)getSpatial(); + + // go through each patch and calculate its LOD based on camera distance + HashMap updated = new HashMap(); + boolean lodChanged = terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here + + if (!lodChanged) { + // not worth updating anything else since no one's LOD changed + setLodCalcRunning(false); + return null; + } + + + // then calculate its neighbour LOD values for seaming in the shader + terrainQuad.findNeighboursLod(updated); + + terrainQuad.fixEdges(updated); // 'updated' can get added to here + + terrainQuad.reIndexPages(updated, lodCalculator.usesVariableLod()); + + //setUpdateQuadLODs(updated); // set back to main ogl thread + + setLodCalcRunning(false); + + return updated; + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write((Node)terrain, "terrain", null); + oc.write(lodCalculator, "lodCalculator", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + terrain = (Terrain) ic.readSavable("terrain", null); + lodCalculator = (LodCalculator) ic.readSavable("lodCalculator", new DistanceLodCalculator()); + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java new file mode 100644 index 000000000..4ac811e9f --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java @@ -0,0 +1,1008 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingSphere; +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; +import com.jme3.collision.UnsupportedCollisionException; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.*; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.terrain.geomipmap.TerrainQuad.LocationHeight; +import com.jme3.terrain.geomipmap.lodcalc.util.EntropyComputeUtil; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.HashMap; +import java.util.List; + + +/** + * A terrain patch is a leaf in the terrain quad tree. It has a mesh that can change levels of detail (LOD) + * whenever the view point, or camera, changes. The actual terrain mesh is created by the LODGeomap class. + * That uses a geo-mipmapping algorithm to change the index buffer of the mesh. + * The mesh is a triangle strip. In wireframe mode you might notice some strange lines, these are degenerate + * triangles generated by the geoMipMap algorithm and can be ignored. The video card removes them at almost no cost. + * + * Each patch needs to know its neighbour's LOD so it can seam its edges with them, in case the neighbour has a different + * LOD. If this doesn't happen, you will see gaps. + * + * The LOD value is most detailed at zero. It gets less detailed the higher the LOD value until you reach maxLod, which + * is a mathematical limit on the number of times the 'size' of the patch can be divided by two. However there is a -1 to that + * for now until I add in a custom index buffer calculation for that max level, the current algorithm does not go that far. + * + * You can supply a LodThresholdCalculator for use in determining when the LOD should change. It's API will no doubt change + * in the near future. Right now it defaults to just changing LOD every two patch sizes. So if a patch has a size of 65, + * then the LOD changes every 130 units away. + * + * @author Brent Owens + */ +public class TerrainPatch extends Geometry { + + protected LODGeomap geomap; + protected int lod = 0; // this terrain patch's LOD + private int maxLod = -1; + protected int previousLod = -1; + protected int lodLeft, lodTop, lodRight, lodBottom; // it's neighbour's LODs + + protected int size; + + protected int totalSize; + + protected short quadrant = 1; + + // x/z step + protected Vector3f stepScale; + + // center of the patch in relation to (0,0,0) + protected Vector2f offset; + + // amount the patch has been shifted. + protected float offsetAmount; + + //protected LodCalculator lodCalculator; + //protected LodCalculatorFactory lodCalculatorFactory; + + protected TerrainPatch leftNeighbour, topNeighbour, rightNeighbour, bottomNeighbour; + protected boolean searchedForNeighboursAlready = false; + + // these two vectors are calculated on the GL thread, but used in the outside LOD thread + protected Vector3f worldTranslationCached; + protected Vector3f worldScaleCached; + + protected float[] lodEntropy; + + public TerrainPatch() { + super("TerrainPatch"); + setBatchHint(BatchHint.Never); + } + + public TerrainPatch(String name) { + super(name); + setBatchHint(BatchHint.Never); + } + + 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 patch. + * @param size + * the size of the heightmap. + * @param stepScale + * the scale for the axes. + * @param heightMap + * the height data. + * @param origin + * the origin offset of the patch. + */ + public TerrainPatch(String name, int size, Vector3f stepScale, + float[] heightMap, Vector3f origin) { + this(name, size, stepScale, heightMap, origin, size, new Vector2f(), 0); + } + + /** + * Constructor instantiates a new TerrainPatch object. The + * parameters and heightmap data are then processed to generate a + * TriMesh object for renderering. + * + * @param name + * the name of the terrain patch. + * @param size + * the size of the patch. + * @param stepScale + * the scale for the axes. + * @param heightMap + * the height data. + * @param origin + * the origin offset of the patch. + * @param totalSize + * the total size of the terrain. (Higher if the patch is part of + * a TerrainQuad 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); + setBatchHint(BatchHint.Never); + this.size = size; + this.stepScale = stepScale; + this.totalSize = totalSize; + this.offsetAmount = offsetAmount; + this.offset = offset; + + setLocalTranslation(origin); + + geomap = new LODGeomap(size, heightMap); + Mesh m = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false); + setMesh(m); + + } + + /** + * This calculation is slow, so don't use it often. + */ + public void generateLodEntropies() { + float[] entropies = new float[getMaxLod()+1]; + for (int i = 0; i <= getMaxLod(); i++){ + int curLod = (int) Math.pow(2, i); + IndexBuffer idxB = geomap.writeIndexArrayLodDiff(curLod, false, false, false, false, totalSize); + Buffer ib; + if (idxB.getBuffer() instanceof IntBuffer) + ib = (IntBuffer)idxB.getBuffer(); + else + ib = (ShortBuffer)idxB.getBuffer(); + entropies[i] = EntropyComputeUtil.computeLodEntropy(mesh, ib); + } + + lodEntropy = entropies; + } + + public float[] getLodEntropies(){ + if (lodEntropy == null){ + generateLodEntropies(); + } + return lodEntropy; + } + + @Deprecated + public FloatBuffer getHeightmap() { + return BufferUtils.createFloatBuffer(geomap.getHeightArray()); + } + + public float[] getHeightMap() { + return geomap.getHeightArray(); + } + + /** + * The maximum lod supported by this terrain patch. + * If the patch size is 32 then the returned value would be log2(32)-2 = 3 + * You can then use that value, 3, to see how many times you can divide 32 by 2 + * before the terrain gets too un-detailed (can't stitch it any further). + * @return the maximum LOD + */ + public int getMaxLod() { + if (maxLod < 0) + maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide + + return maxLod; + } + + protected void reIndexGeometry(HashMap updated, boolean useVariableLod) { + + UpdatedTerrainPatch utp = updated.get(getName()); + + if (utp != null && utp.isReIndexNeeded() ) { + 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(); + + IndexBuffer idxB; + if (useVariableLod) + idxB = geomap.writeIndexArrayLodVariable(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()), totalSize); + else + idxB = geomap.writeIndexArrayLodDiff(pow, right, top, left, bottom, totalSize); + + Buffer b; + if (idxB.getBuffer() instanceof IntBuffer) + b = (IntBuffer)idxB.getBuffer(); + else + b = (ShortBuffer)idxB.getBuffer(); + utp.setNewIndexBuffer(b); + } + + } + + + public Vector2f getTex(float x, float z, Vector2f store) { + if (x < 0 || z < 0 || x >= size || z >= size) { + store.set(Vector2f.ZERO); + return store; + } + int idx = (int) (z * size + x); + return store.set(getMesh().getFloatBuffer(Type.TexCoord).get(idx*2), + getMesh().getFloatBuffer(Type.TexCoord).get(idx*2+1) ); + } + + public float getHeightmapHeight(float x, float z) { + if (x < 0 || z < 0 || x >= size || z >= size) + return 0; + int idx = (int) (z * size + x); + return getMesh().getFloatBuffer(Type.Position).get(idx*3+1); // 3 floats per entry (x,y,z), the +1 is to get the Y + } + + /** + * Get the triangle of this geometry at the specified local coordinate. + * @param x local to the terrain patch + * @param z local to the terrain patch + * @return the triangle in world coordinates, or null if the point does intersect this patch on the XZ axis + */ + public Triangle getTriangle(float x, float z) { + return geomap.getTriangleAtPoint(x, z, getWorldScale() , getWorldTranslation()); + } + + /** + * Get the triangles at the specified grid point. Probably only 2 triangles + * @param x local to the terrain patch + * @param z local to the terrain patch + * @return the triangles in world coordinates, or null if the point does intersect this patch on the XZ axis + */ + public Triangle[] getGridTriangles(float x, float z) { + return geomap.getGridTrianglesAtPoint(x, z, getWorldScale() , getWorldTranslation()); + } + + protected void setHeight(List locationHeights, boolean overrideHeight) { + + for (LocationHeight lh : locationHeights) { + if (lh.x < 0 || lh.z < 0 || lh.x >= size || lh.z >= size) + continue; + int idx = lh.z * size + lh.x; + if (overrideHeight) { + geomap.getHeightArray()[idx] = lh.h; + } else { + float h = getMesh().getFloatBuffer(Type.Position).get(idx*3+1); + geomap.getHeightArray()[idx] = h+lh.h; + } + + } + + FloatBuffer newVertexBuffer = geomap.writeVertexArray(null, stepScale, false); + getMesh().clearBuffer(Type.Position); + getMesh().setBuffer(Type.Position, 3, newVertexBuffer); + } + + /** + * recalculate all of the normal vectors in this terrain patch + */ + protected void updateNormals() { + FloatBuffer newNormalBuffer = geomap.writeNormalArray(null, getWorldScale()); + getMesh().getBuffer(Type.Normal).updateData(newNormalBuffer); + FloatBuffer newTangentBuffer = null; + FloatBuffer newBinormalBuffer = null; + FloatBuffer[] tb = geomap.writeTangentArray(newNormalBuffer, newTangentBuffer, newBinormalBuffer, (FloatBuffer)getMesh().getBuffer(Type.TexCoord).getData(), getWorldScale()); + newTangentBuffer = tb[0]; + newBinormalBuffer = tb[1]; + getMesh().getBuffer(Type.Tangent).updateData(newTangentBuffer); + getMesh().getBuffer(Type.Binormal).updateData(newBinormalBuffer); + } + + private void setInBuffer(Mesh mesh, int index, Vector3f normal, Vector3f tangent, Vector3f binormal) { + VertexBuffer NB = mesh.getBuffer(Type.Normal); + VertexBuffer TB = mesh.getBuffer(Type.Tangent); + VertexBuffer BB = mesh.getBuffer(Type.Binormal); + BufferUtils.setInBuffer(normal, (FloatBuffer)NB.getData(), index); + BufferUtils.setInBuffer(tangent, (FloatBuffer)TB.getData(), index); + BufferUtils.setInBuffer(binormal, (FloatBuffer)BB.getData(), index); + NB.setUpdateNeeded(); + TB.setUpdateNeeded(); + BB.setUpdateNeeded(); + } + + /** + * Matches the normals along the edge of the patch with the neighbours. + * Computes the normals for the right, bottom, left, and top edges of the + * patch, and saves those normals in the neighbour's edges too. + * + * Takes 4 points (if has neighbour on that side) for each + * point on the edge of the patch: + * * + * | + * *---x---* + * | + * * + * It works across the right side of the patch, from the top down to + * the bottom. Then it works on the bottom side of the patch, from the + * left to the right. + */ + protected void fixNormalEdges(TerrainPatch right, + TerrainPatch bottom, + TerrainPatch top, + TerrainPatch left, + TerrainPatch bottomRight, + TerrainPatch bottomLeft, + TerrainPatch topRight, + TerrainPatch topLeft) + { + Vector3f rootPoint = new Vector3f(); + Vector3f rightPoint = new Vector3f(); + Vector3f leftPoint = new Vector3f(); + Vector3f topPoint = new Vector3f(); + + Vector3f bottomPoint = new Vector3f(); + + Vector3f tangent = new Vector3f(); + Vector3f binormal = new Vector3f(); + Vector3f normal = new Vector3f(); + + + int s = this.getSize()-1; + + if (right != null) { // right side, works its way down + for (int i=0; i= size || z >= size) + return null; // out of range + + int index = (z*size+x)*3; + FloatBuffer nb = (FloatBuffer)this.getMesh().getBuffer(Type.Normal).getData(); + Vector3f normal = new Vector3f(); + normal.x = nb.get(index); + normal.y = nb.get(index+1); + normal.z = nb.get(index+2); + return normal; + } + + protected float getHeight(int x, int z, float xm, float zm) { + return geomap.getHeight(x,z,xm,zm); + } + + /** + * Locks the mesh (sets it static) to improve performance. + * But it it not editable then. Set unlock to make it editable. + */ + public void lockMesh() { + getMesh().setStatic(); + } + + /** + * Unlocks the mesh (sets it dynamic) to make it editable. + * It will be editable but performance will be reduced. + * Call lockMesh to improve performance. + */ + public void unlockMesh() { + getMesh().setDynamic(); + } + + /** + * Returns the offset amount this terrain patch uses for textures. + * + * @return The current offset amount. + */ + public float getOffsetAmount() { + return offsetAmount; + } + + /** + * Returns the step scale that stretches the height map. + * + * @return The current step scale. + */ + public Vector3f getStepScale() { + return stepScale; + } + + /** + * Returns the total size of the terrain. + * + * @return The terrain's total size. + */ + public int getTotalSize() { + return totalSize; + } + + /** + * Returns the size of this terrain patch. + * + * @return The current patch size. + */ + public int getSize() { + return size; + } + + /** + * Returns the current offset amount. This is used when building texture + * coordinates. + * + * @return The current offset amount. + */ + public Vector2f getOffset() { + return offset; + } + + /** + * Sets the value for the current offset amount to use when building texture + * coordinates. Note that this does NOT rebuild the terrain at all. + * This is mostly used for outside constructors of terrain patches. + * + * @param offset + * The new texture offset. + */ + public void setOffset(Vector2f offset) { + this.offset = offset; + } + + /** + * Sets the size of this terrain patch. Note that this does NOT + * rebuild the terrain at all. This is mostly used for outside constructors + * of terrain patches. + * + * @param size + * The new size. + */ + public void setSize(int size) { + this.size = size; + + maxLod = -1; // reset it + } + + /** + * Sets the total size of the terrain . Note that this does NOT + * rebuild the terrain at all. This is mostly used for outside constructors + * of terrain patches. + * + * @param totalSize + * The new total size. + */ + public void setTotalSize(int totalSize) { + this.totalSize = totalSize; + } + + /** + * Sets the step scale of this terrain patch's height map. Note that this + * does NOT rebuild the terrain at all. This is mostly used for + * outside constructors of terrain patches. + * + * @param stepScale + * The new step scale. + */ + public void setStepScale(Vector3f stepScale) { + this.stepScale = stepScale; + } + + /** + * Sets the offset of this terrain texture map. Note that this does NOT + * rebuild the terrain at all. This is mostly used for outside + * constructors of terrain patches. + * + * @param offsetAmount + * The new texture offset. + */ + public void setOffsetAmount(float offsetAmount) { + this.offsetAmount = offsetAmount; + } + + /** + * @return Returns the quadrant. + */ + public short getQuadrant() { + return quadrant; + } + + /** + * @param quadrant + * The quadrant to set. + */ + public void setQuadrant(short quadrant) { + this.quadrant = quadrant; + } + + public int getLod() { + return lod; + } + + public void setLod(int lod) { + this.lod = lod; + } + + public int getPreviousLod() { + return previousLod; + } + + public void setPreviousLod(int previousLod) { + this.previousLod = previousLod; + } + + protected int getLodLeft() { + return lodLeft; + } + + protected void setLodLeft(int lodLeft) { + this.lodLeft = lodLeft; + } + + protected int getLodTop() { + return lodTop; + } + + protected void setLodTop(int lodTop) { + this.lodTop = lodTop; + } + + protected int getLodRight() { + return lodRight; + } + + protected void setLodRight(int lodRight) { + this.lodRight = lodRight; + } + + protected int getLodBottom() { + return lodBottom; + } + + protected void setLodBottom(int lodBottom) { + this.lodBottom = lodBottom; + } + + /*public void setLodCalculator(LodCalculatorFactory lodCalculatorFactory) { + this.lodCalculatorFactory = lodCalculatorFactory; + setLodCalculator(lodCalculatorFactory.createCalculator(this)); + }*/ + + @Override + public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException { + if (refreshFlags != 0) + throw new IllegalStateException("Scene graph must be updated" + + " before checking collision"); + + if (other instanceof BoundingVolume) + if (!getWorldBound().intersects((BoundingVolume)other)) + return 0; + + if(other instanceof Ray) + return collideWithRay((Ray)other, results); + else if (other instanceof BoundingVolume) + return collideWithBoundingVolume((BoundingVolume)other, results); + else { + throw new UnsupportedCollisionException("TerrainPatch cannnot collide with "+other.getClass().getName()); + } + } + + + private int collideWithRay(Ray ray, CollisionResults results) { + // This should be handled in the root terrain quad + return 0; + } + + private int collideWithBoundingVolume(BoundingVolume boundingVolume, CollisionResults results) { + if (boundingVolume instanceof BoundingBox) + return collideWithBoundingBox((BoundingBox)boundingVolume, results); + else if(boundingVolume instanceof BoundingSphere) { + BoundingSphere sphere = (BoundingSphere) boundingVolume; + BoundingBox bbox = new BoundingBox(boundingVolume.getCenter().clone(), sphere.getRadius(), + sphere.getRadius(), + sphere.getRadius()); + return collideWithBoundingBox(bbox, results); + } + return 0; + } + + protected Vector3f worldCoordinateToLocal(Vector3f loc) { + Vector3f translated = new Vector3f(); + translated.x = loc.x/getWorldScale().x - getWorldTranslation().x; + translated.y = loc.y/getWorldScale().y - getWorldTranslation().y; + translated.z = loc.z/getWorldScale().z - getWorldTranslation().z; + return translated; + } + + /** + * This most definitely is not optimized. + */ + private int collideWithBoundingBox(BoundingBox bbox, CollisionResults results) { + + // test the four corners, for cases where the bbox dimensions are less than the terrain grid size, which is probably most of the time + Vector3f topLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent())); + Vector3f topRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent())); + Vector3f bottomLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z+bbox.getZExtent())); + Vector3f bottomRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z+bbox.getZExtent())); + + Triangle t = getTriangle(topLeft.x, topLeft.z); + if (t != null && bbox.collideWith(t, results) > 0) + return 1; + t = getTriangle(topRight.x, topRight.z); + if (t != null && bbox.collideWith(t, results) > 0) + return 1; + t = getTriangle(bottomLeft.x, bottomLeft.z); + if (t != null && bbox.collideWith(t, results) > 0) + return 1; + t = getTriangle(bottomRight.x, bottomRight.z); + if (t != null && bbox.collideWith(t, results) > 0) + return 1; + + // box is larger than the points on the terrain, so test against the points + for (float z=topLeft.z; z= size || z >= size) + continue; + t = getTriangle(x,z); + if (t != null && bbox.collideWith(t, results) > 0) + return 1; + } + } + + return 0; + } + + + @Override + public void write(JmeExporter ex) throws IOException { + // the mesh is removed, and reloaded when read() is called + // this reduces the save size to 10% by not saving the mesh + Mesh temp = getMesh(); + mesh = null; + + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(size, "size", 16); + oc.write(totalSize, "totalSize", 16); + oc.write(quadrant, "quadrant", (short)0); + oc.write(stepScale, "stepScale", Vector3f.UNIT_XYZ); + oc.write(offset, "offset", Vector3f.UNIT_XYZ); + oc.write(offsetAmount, "offsetAmount", 0); + //oc.write(lodCalculator, "lodCalculator", null); + //oc.write(lodCalculatorFactory, "lodCalculatorFactory", null); + oc.write(lodEntropy, "lodEntropy", null); + oc.write(geomap, "geomap", null); + + setMesh(temp); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + size = ic.readInt("size", 16); + totalSize = ic.readInt("totalSize", 16); + quadrant = ic.readShort("quadrant", (short)0); + stepScale = (Vector3f) ic.readSavable("stepScale", Vector3f.UNIT_XYZ); + offset = (Vector2f) ic.readSavable("offset", Vector3f.UNIT_XYZ); + offsetAmount = ic.readFloat("offsetAmount", 0); + //lodCalculator = (LodCalculator) ic.readSavable("lodCalculator", new DistanceLodCalculator()); + //lodCalculator.setTerrainPatch(this); + //lodCalculatorFactory = (LodCalculatorFactory) ic.readSavable("lodCalculatorFactory", null); + lodEntropy = ic.readFloatArray("lodEntropy", null); + geomap = (LODGeomap) ic.readSavable("geomap", null); + + Mesh regen = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false); + setMesh(regen); + //TangentBinormalGenerator.generate(this); // note that this will be removed + ensurePositiveVolumeBBox(); + } + + @Override + public TerrainPatch clone() { + TerrainPatch clone = new TerrainPatch(); + clone.name = name.toString(); + clone.size = size; + clone.totalSize = totalSize; + clone.quadrant = quadrant; + clone.stepScale = stepScale.clone(); + clone.offset = offset.clone(); + clone.offsetAmount = offsetAmount; + //clone.lodCalculator = lodCalculator.clone(); + //clone.lodCalculator.setTerrainPatch(clone); + //clone.setLodCalculator(lodCalculatorFactory.clone()); + clone.geomap = new LODGeomap(size, geomap.getHeightArray()); + clone.setLocalTranslation(getLocalTranslation().clone()); + Mesh m = clone.geomap.createMesh(clone.stepScale, Vector2f.UNIT_XY, clone.offset, clone.offsetAmount, clone.totalSize, false); + clone.setMesh(m); + clone.setMaterial(material.clone()); + return clone; + } + + protected void ensurePositiveVolumeBBox() { + if (getModelBound() instanceof BoundingBox) { + if (((BoundingBox)getModelBound()).getYExtent() < 0.001f) { + // a correction so the box always has a volume + ((BoundingBox)getModelBound()).setYExtent(0.001f); + updateWorldBound(); + } + } + } + + /** + * Caches the transforms (except rotation) so the LOD calculator, + * which runs on a separate thread, can access them safely. + */ + protected void cacheTerrainTransforms() { + this.worldScaleCached = getWorldScale().clone(); + this.worldTranslationCached = getWorldTranslation().clone(); + } + + public Vector3f getWorldScaleCached() { + return worldScaleCached; + } + + public Vector3f getWorldTranslationCached() { + return worldTranslationCached; + } + + /** + * Removes any references when the terrain is being removed. + */ + protected void clearCaches() { + if (leftNeighbour != null) { + leftNeighbour.rightNeighbour = null; + leftNeighbour = null; + } + if (rightNeighbour != null) { + rightNeighbour.leftNeighbour = null; + rightNeighbour = null; + } + if (topNeighbour != null) { + topNeighbour.bottomNeighbour = null; + topNeighbour = null; + } + if (bottomNeighbour != null) { + bottomNeighbour.topNeighbour = null; + bottomNeighbour = null; + } + } + + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java new file mode 100644 index 000000000..8c9e0bc14 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java @@ -0,0 +1,1913 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Ray; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.WireBox; +import com.jme3.terrain.ProgressMonitor; +import com.jme3.terrain.Terrain; +import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; +import com.jme3.terrain.geomipmap.picking.BresenhamTerrainPicker; +import com.jme3.terrain.geomipmap.picking.TerrainPickData; +import com.jme3.terrain.geomipmap.picking.TerrainPicker; +import com.jme3.util.TangentBinormalGenerator; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + *

      + * TerrainQuad is a heightfield-based terrain system. Heightfield terrain is fast and can + * render large areas, and allows for easy Level of Detail control. However it does not + * permit caves easily. + * TerrainQuad is a quad tree, meaning that the root quad has four children, and each of + * those children have four children. All the way down until you reach the bottom, the actual + * geometry, the TerrainPatches. + * If you look at a TerrainQuad in wireframe mode with the TerrainLODControl attached, you will + * see blocks that change their LOD level together; these are the TerrainPatches. The TerrainQuad + * is just an organizational structure for the TerrainPatches so patches that are not in the + * view frustum get culled quickly. + * TerrainQuads size are a power of 2, plus 1. So 513x513, or 1025x1025 etc. + * Each point in the terrain is one unit apart from its neighbour. So a 513x513 terrain + * will be 513 units wide and 513 units long. + * Patch size can be specified on the terrain. This sets how large each geometry (TerrainPatch) + * is. It also must be a power of 2 plus 1 so the terrain can be subdivided equally. + *

      + *

      + * The height of the terrain can be modified at runtime using setHeight() + *

      + *

      + * A terrain quad is a node in the quad tree of the terrain system. + * The root terrain quad will be the only one that receives the update() call every frame + * and it will determine if there has been any LOD change. + *

      + * The leaves of the terrain quad tree are Terrain Patches. These have the real geometry mesh. + *

      + * Heightmap coordinates start from the bottom left of the world and work towards the + * top right. + *

      + *  +x
      + *  ^
      + *  | ......N = length of heightmap
      + *  | :     :
      + *  | :     :
      + *  | 0.....:
      + *  +---------> +z
      + * (world coordinates)
      + * 
      + * @author Brent Owens + */ +public class TerrainQuad extends Node implements Terrain { + protected Vector2f offset; + + protected int totalSize; // the size of this entire terrain tree (on one side) + + protected int size; // size of this quad, can be between totalSize and patchSize + + protected int patchSize; // size of the individual patches + + protected Vector3f stepScale; + + protected float offsetAmount; + + protected int quadrant = 0; // 1=upper left, 2=lower left, 3=upper right, 4=lower right + private int maxLod = -1; + private BoundingBox affectedAreaBBox; // only set in the root quad + + private TerrainPicker picker; + private Vector3f lastScale = Vector3f.UNIT_XYZ; + + protected NeighbourFinder neighbourFinder; + + public TerrainQuad() { + super("Terrain"); + } + + /** + * Creates a terrain with: + *
        + *
      • the total, real-world, size of the terrain
      • + *
      • the patchSize, or the size of each geometry tile of the terrain
      • + *
      • the heightmap that defines the height of the terrain
      • + *
      + *

      + * A TerrainQuad of totalSize 513x513 will be 513 units wide and 513 units long. + * PatchSize is just used to subdivide the terrain into tiles that can be culled. + *

      + * @param name the name of the scene element. This is required for + * identification and comparison purposes. + * @param patchSize size of the individual patches (geometry). Power of 2 plus 1, + * must be smaller than totalSize. (eg. 33, 65...) + * @param totalSize the size of this entire terrain (on one side). Power of 2 plus 1 + * (eg. 513, 1025, 2049...) + * @param heightMap The height map to generate the terrain from (a flat + * height map will be generated if this is null). The size of one side of the heightmap + * must match the totalSize. So a 513x513 heightmap is needed for a terrain with totalSize of 513. + */ + public TerrainQuad(String name, int patchSize, int totalSize, float[] heightMap) { + this(name, patchSize, totalSize, Vector3f.UNIT_XYZ, heightMap); + + affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2); + fixNormalEdges(affectedAreaBBox); + addControl(new NormalRecalcControl(this)); + } + + /** + * + * @param name the name of the scene element. This is required for + * identification and comparison purposes. + * @param patchSize size of the individual patches + * @param quadSize + * @param totalSize the size of this entire terrain tree (on one side) + * @param heightMap The height map to generate the terrain from (a flat + * height map will be generated if this is null) + */ + @Deprecated + public TerrainQuad(String name, int patchSize, int quadSize, int totalSize, float[] heightMap) { + this(name, patchSize, totalSize, quadSize, Vector3f.UNIT_XYZ, heightMap); + } + + /** + * + * @param name the name of the scene element. This is required for + * identification and comparison purposes. + * @param patchSize size of the individual patches + * @param size size of this quad, can be between totalSize and patchSize + * @param scale + * @param heightMap The height map to generate the terrain from (a flat + * height map will be generated if this is null) + */ + @Deprecated + public TerrainQuad(String name, int patchSize, int size, Vector3f scale, float[] heightMap) { + this(name, patchSize, size, scale, heightMap, size, new Vector2f(), 0); + //affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2); + //fixNormalEdges(affectedAreaBBox); + //addControl(new NormalRecalcControl(this)); + } + + /** + * + * @param name the name of the scene element. This is required for + * identification and comparison purposes. + * @param patchSize size of the individual patches + * @param totalSize the size of this entire terrain tree (on one side) + * @param quadSize + * @param scale + * @param heightMap The height map to generate the terrain from (a flat + * height map will be generated if this is null) + */ + @Deprecated + public TerrainQuad(String name, int patchSize, int totalSize, int quadSize, Vector3f scale, float[] heightMap) { + this(name, patchSize, quadSize, scale, heightMap, totalSize, new Vector2f(), 0); + //affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2); + //fixNormalEdges(affectedAreaBBox); + //addControl(new NormalRecalcControl(this)); + } + + protected TerrainQuad(String name, int patchSize, int quadSize, + Vector3f scale, float[] heightMap, int totalSize, + Vector2f offset, float offsetAmount) + { + super(name); + + if (heightMap == null) + heightMap = generateDefaultHeightMap(quadSize); + + if (!FastMath.isPowerOfTwo(quadSize - 1)) { + throw new RuntimeException("size given: " + quadSize + " Terrain quad sizes may only be (2^N + 1)"); + } + if (FastMath.sqrt(heightMap.length) > quadSize) { + Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Heightmap size is larger than the terrain size. Make sure your heightmap image is the same size as the terrain!"); + } + + this.offset = offset; + this.offsetAmount = offsetAmount; + this.totalSize = totalSize; + this.size = quadSize; + this.patchSize = patchSize; + this.stepScale = scale; + split(patchSize, heightMap); + } + + public void setNeighbourFinder(NeighbourFinder neighbourFinder) { + this.neighbourFinder = neighbourFinder; + resetCachedNeighbours(); + } + + /** + * Forces the recalculation of all normals on the terrain. + */ + public void recalculateAllNormals() { + affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2); + } + + /** + * Create just a flat heightmap + */ + private float[] generateDefaultHeightMap(int size) { + float[] heightMap = new float[size*size]; + return heightMap; + } + + /** + * 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 + } + } + + /** + * Caches the transforms (except rotation) so the LOD calculator, + * which runs on a separate thread, can access them safely. + */ + protected void cacheTerrainTransforms() { + for (int i = children.size(); --i >= 0;) { + Spatial child = children.get(i); + if (child instanceof TerrainQuad) { + ((TerrainQuad) child).cacheTerrainTransforms(); + } else if (child instanceof TerrainPatch) { + ((TerrainPatch) child).cacheTerrainTransforms(); + } + } + } + + private int collideWithRay(Ray ray, CollisionResults results) { + if (picker == null) + picker = new BresenhamTerrainPicker(this); + + Vector3f intersection = picker.getTerrainIntersection(ray, results); + if (intersection != null) { + if (ray.getLimit() < Float.POSITIVE_INFINITY) { + if (results.getClosestCollision().getDistance() <= ray.getLimit()) + return 1; // in range + else + return 0; // out of range + } else + return 1; + } else + return 0; + } + + /** + * Generate the entropy values for the terrain for the "perspective" LOD + * calculator. This routine can take a long time to run! + * @param progressMonitor optional + */ + public void generateEntropy(ProgressMonitor progressMonitor) { + // only check this on the root quad + if (isRootQuad()) + if (progressMonitor != null) { + int numCalc = (totalSize-1)/(patchSize-1); // make it an even number + progressMonitor.setMonitorMax(numCalc*numCalc); + } + + if (children != null) { + for (int i = children.size(); --i >= 0;) { + Spatial child = children.get(i); + if (child instanceof TerrainQuad) { + ((TerrainQuad) child).generateEntropy(progressMonitor); + } else if (child instanceof TerrainPatch) { + ((TerrainPatch) child).generateLodEntropies(); + if (progressMonitor != null) + progressMonitor.incrementProgress(1); + } + } + } + + // only do this on the root quad + if (isRootQuad()) + if (progressMonitor != null) + progressMonitor.progressComplete(); + } + + protected boolean isRootQuad() { + return (getParent() != null && !(getParent() instanceof TerrainQuad) ); + } + + public Material getMaterial() { + return getMaterial(null); + } + + public Material getMaterial(Vector3f worldLocation) { + // get the material from one of the children. They all share the same material + if (children != null) { + for (int i = children.size(); --i >= 0;) { + Spatial child = children.get(i); + if (child instanceof TerrainQuad) { + return ((TerrainQuad)child).getMaterial(worldLocation); + } else if (child instanceof TerrainPatch) { + return ((TerrainPatch)child).getMaterial(); + } + } + } + return null; + } + + public int getNumMajorSubdivisions() { + return 1; + } + + + protected boolean calculateLod(List location, HashMap updates, LodCalculator lodCalculator) { + + boolean lodChanged = false; + + if (children != null) { + for (int i = children.size(); --i >= 0;) { + Spatial child = children.get(i); + if (child instanceof TerrainQuad) { + boolean b = ((TerrainQuad) child).calculateLod(location, updates, lodCalculator); + if (b) + lodChanged = true; + } else if (child instanceof TerrainPatch) { + boolean b = lodCalculator.calculateLod((TerrainPatch) child, location, updates); + if (b) + lodChanged = true; + } + } + } + + return lodChanged; + } + + protected synchronized void findNeighboursLod(HashMap 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; + TerrainPatch left = patch.leftNeighbour; + TerrainPatch top = patch.topNeighbour; + + 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); + updated.put(utpR.getName(), utpR); + utpR.setNewLod(right.lod); + } + utp.setRightLod(utpR.getNewLod()); + utpR.setLeftLod(utp.getNewLod()); + } + if (down != null) { + UpdatedTerrainPatch utpD = updated.get(down.getName()); + if (utpD == null) { + utpD = new UpdatedTerrainPatch(down); + updated.put(utpD.getName(), utpD); + utpD.setNewLod(down.lod); + } + utp.setBottomLod(utpD.getNewLod()); + utpD.setTopLod(utp.getNewLod()); + } + + if (left != null) { + UpdatedTerrainPatch utpL = updated.get(left.getName()); + if (utpL == null) { + utpL = new UpdatedTerrainPatch(left); + updated.put(utpL.getName(), utpL); + utpL.setNewLod(left.lod); + } + utp.setLeftLod(utpL.getNewLod()); + utpL.setRightLod(utp.getNewLod()); + } + if (top != null) { + UpdatedTerrainPatch utpT = updated.get(top.getName()); + if (utpT == null) { + utpT = new UpdatedTerrainPatch(top); + updated.put(utpT.getName(), utpT); + utpT.setNewLod(top.lod); + } + utp.setTopLod(utpT.getNewLod()); + utpT.setBottomLod(utp.getNewLod()); + } + } + } + } + } + + /** + * Reset the cached references of neighbours. + * TerrainQuad caches neighbours for faster LOD checks. + * Sometimes you might want to reset this cache (for instance in TerrainGrid) + */ + public void resetCachedNeighbours() { + if (children != null) { + for (int x = children.size(); --x >= 0;) { + Spatial child = children.get(x); + if (child instanceof TerrainQuad) { + ((TerrainQuad) child).resetCachedNeighbours(); + } else if (child instanceof TerrainPatch) { + TerrainPatch patch = (TerrainPatch) child; + patch.searchedForNeighboursAlready = false; + } + } + } + } + + /** + * Find any neighbours that should have their edges seamed because another neighbour + * changed its LOD to a greater value (less detailed) + */ + protected synchronized void fixEdges(HashMap updated) { + if (children != null) { + for (int x = children.size(); --x >= 0;) { + Spatial child = children.get(x); + if (child instanceof TerrainQuad) { + ((TerrainQuad) child).fixEdges(updated); + } else if (child instanceof TerrainPatch) { + TerrainPatch patch = (TerrainPatch) child; + UpdatedTerrainPatch utp = updated.get(patch.getName()); + + if(utp != null && utp.lodChanged()) { + if (!patch.searchedForNeighboursAlready) { + // set the references to the neighbours + patch.rightNeighbour = findRightPatch(patch); + patch.bottomNeighbour = findDownPatch(patch); + patch.leftNeighbour = findLeftPatch(patch); + patch.topNeighbour = findTopPatch(patch); + patch.searchedForNeighboursAlready = true; + } + TerrainPatch right = patch.rightNeighbour; + TerrainPatch down = patch.bottomNeighbour; + TerrainPatch top = patch.topNeighbour; + TerrainPatch left = patch.leftNeighbour; + if (right != null) { + UpdatedTerrainPatch utpR = updated.get(right.getName()); + if (utpR == null) { + utpR = new UpdatedTerrainPatch(right); + updated.put(utpR.getName(), utpR); + utpR.setNewLod(right.lod); + } + utpR.setLeftLod(utp.getNewLod()); + utpR.setFixEdges(true); + } + if (down != null) { + UpdatedTerrainPatch utpD = updated.get(down.getName()); + if (utpD == null) { + utpD = new UpdatedTerrainPatch(down); + updated.put(utpD.getName(), utpD); + utpD.setNewLod(down.lod); + } + utpD.setTopLod(utp.getNewLod()); + utpD.setFixEdges(true); + } + if (top != null){ + UpdatedTerrainPatch utpT = updated.get(top.getName()); + if (utpT == null) { + utpT = new UpdatedTerrainPatch(top); + updated.put(utpT.getName(), utpT); + utpT.setNewLod(top.lod); + } + utpT.setBottomLod(utp.getNewLod()); + utpT.setFixEdges(true); + } + if (left != null){ + UpdatedTerrainPatch utpL = updated.get(left.getName()); + if (utpL == null) { + utpL = new UpdatedTerrainPatch(left); + updated.put(utpL.getName(), utpL); + utpL.setNewLod(left.lod); + } + utpL.setRightLod(utp.getNewLod()); + utpL.setFixEdges(true); + } + } + } + } + } + } + + protected synchronized void reIndexPages(HashMap updated, boolean usesVariableLod) { + if (children != null) { + for (int i = children.size(); --i >= 0;) { + Spatial child = children.get(i); + if (child instanceof TerrainQuad) { + ((TerrainQuad) child).reIndexPages(updated, usesVariableLod); + } else if (child instanceof TerrainPatch) { + ((TerrainPatch) child).reIndexGeometry(updated, usesVariableLod); + } + } + } + } + + /** + * split divides the heightmap data for four children. The + * children are either quads or patches. This is dependent on the size of the + * children. If the child's size is less than or equal to the set block + * size, then patches are created, otherwise, quads are created. + * + * @param blockSize + * the blocks size to test against. + * @param heightMap + * the height data. + */ + protected void split(int blockSize, float[] heightMap) { + if ((size >> 1) + 1 <= blockSize) { + createQuadPatch(heightMap); + } else { + createQuad(blockSize, heightMap); + } + + } + + /** + * Quadrants, world coordinates, and heightmap coordinates (Y-up): + * + * -z + * -u | + * -v 1|3 + * -x ----+---- x + * 2|4 u + * | v + * z + * createQuad generates four new quads from this quad. + * The heightmap's top left (0,0) coordinate is at the bottom, -x,-z + * coordinate of the terrain, so it grows in the positive x.z direction. + */ + protected void createQuad(int blockSize, float[] heightMap) { + // create 4 terrain quads + int quarterSize = size >> 2; + + int split = (size + 1) >> 1; + + Vector2f tempOffset = new Vector2f(); + offsetAmount += quarterSize; + + //if (lodCalculator == null) + // lodCalculator = createDefaultLodCalculator(); // set a default one + + // 1 upper left of heightmap, upper left quad + float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split); + + Vector3f origin1 = new Vector3f(-quarterSize * stepScale.x, 0, + -quarterSize * stepScale.z); + + tempOffset.x = offset.x; + tempOffset.y = offset.y; + tempOffset.x += origin1.x; + tempOffset.y += origin1.z; + + TerrainQuad quad1 = new TerrainQuad(getName() + "Quad1", blockSize, + split, stepScale, heightBlock1, totalSize, tempOffset, + offsetAmount); + quad1.setLocalTranslation(origin1); + quad1.quadrant = 1; + this.attachChild(quad1); + + // 2 lower left of heightmap, lower left quad + float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1, + split); + + Vector3f origin2 = new Vector3f(-quarterSize * stepScale.x, 0, + quarterSize * stepScale.z); + + tempOffset = new Vector2f(); + tempOffset.x = offset.x; + tempOffset.y = offset.y; + tempOffset.x += origin2.x; + tempOffset.y += origin2.z; + + TerrainQuad quad2 = new TerrainQuad(getName() + "Quad2", blockSize, + split, stepScale, heightBlock2, totalSize, tempOffset, + offsetAmount); + quad2.setLocalTranslation(origin2); + quad2.quadrant = 2; + this.attachChild(quad2); + + // 3 upper right of heightmap, upper right quad + float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0, + split); + + Vector3f origin3 = new Vector3f(quarterSize * stepScale.x, 0, + -quarterSize * stepScale.z); + + tempOffset = new Vector2f(); + tempOffset.x = offset.x; + tempOffset.y = offset.y; + tempOffset.x += origin3.x; + tempOffset.y += origin3.z; + + TerrainQuad quad3 = new TerrainQuad(getName() + "Quad3", blockSize, + split, stepScale, heightBlock3, totalSize, tempOffset, + offsetAmount); + quad3.setLocalTranslation(origin3); + quad3.quadrant = 3; + this.attachChild(quad3); + + // 4 lower right of heightmap, lower right quad + float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1, + split - 1, split); + + Vector3f origin4 = new Vector3f(quarterSize * stepScale.x, 0, + quarterSize * stepScale.z); + + tempOffset = new Vector2f(); + tempOffset.x = offset.x; + tempOffset.y = offset.y; + tempOffset.x += origin4.x; + tempOffset.y += origin4.z; + + TerrainQuad quad4 = new TerrainQuad(getName() + "Quad4", blockSize, + split, stepScale, heightBlock4, totalSize, tempOffset, + offsetAmount); + quad4.setLocalTranslation(origin4); + quad4.quadrant = 4; + this.attachChild(quad4); + + } + + public void generateDebugTangents(Material mat) { + for (int x = children.size(); --x >= 0;) { + Spatial child = children.get(x); + if (child instanceof TerrainQuad) { + ((TerrainQuad)child).generateDebugTangents(mat); + } else if (child instanceof TerrainPatch) { + Geometry debug = new Geometry( "Debug " + name, + TangentBinormalGenerator.genTbnLines( ((TerrainPatch)child).getMesh(), 0.8f)); + attachChild(debug); + debug.setLocalTranslation(child.getLocalTranslation()); + debug.setCullHint(CullHint.Never); + debug.setMaterial(mat); + } + } + } + + /** + * createQuadPatch creates four child patches from this quad. + */ + protected void createQuadPatch(float[] heightMap) { + // create 4 terrain patches + int quarterSize = size >> 2; + int halfSize = size >> 1; + int split = (size + 1) >> 1; + + //if (lodCalculator == null) + // lodCalculator = createDefaultLodCalculator(); // set a default one + + offsetAmount += quarterSize; + + // 1 lower left + float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split); + + Vector3f origin1 = new Vector3f(-halfSize * stepScale.x, 0, -halfSize + * stepScale.z); + + Vector2f tempOffset1 = new Vector2f(); + tempOffset1.x = offset.x; + tempOffset1.y = offset.y; + tempOffset1.x += origin1.x / 2; + tempOffset1.y += origin1.z / 2; + + TerrainPatch patch1 = new TerrainPatch(getName() + "Patch1", split, + stepScale, heightBlock1, origin1, totalSize, tempOffset1, + offsetAmount); + patch1.setQuadrant((short) 1); + this.attachChild(patch1); + patch1.setModelBound(new BoundingBox()); + patch1.updateModelBound(); + //patch1.setLodCalculator(lodCalculator); + //TangentBinormalGenerator.generate(patch1); + + // 2 upper left + float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1, + split); + + Vector3f origin2 = new Vector3f(-halfSize * stepScale.x, 0, 0); + + Vector2f tempOffset2 = new Vector2f(); + tempOffset2.x = offset.x; + tempOffset2.y = offset.y; + tempOffset2.x += origin1.x / 2; + tempOffset2.y += quarterSize * stepScale.z; + + TerrainPatch patch2 = new TerrainPatch(getName() + "Patch2", split, + stepScale, heightBlock2, origin2, totalSize, tempOffset2, + offsetAmount); + patch2.setQuadrant((short) 2); + this.attachChild(patch2); + patch2.setModelBound(new BoundingBox()); + patch2.updateModelBound(); + //patch2.setLodCalculator(lodCalculator); + //TangentBinormalGenerator.generate(patch2); + + // 3 lower right + float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0, + split); + + Vector3f origin3 = new Vector3f(0, 0, -halfSize * stepScale.z); + + Vector2f tempOffset3 = new Vector2f(); + tempOffset3.x = offset.x; + tempOffset3.y = offset.y; + tempOffset3.x += quarterSize * stepScale.x; + tempOffset3.y += origin3.z / 2; + + TerrainPatch patch3 = new TerrainPatch(getName() + "Patch3", split, + stepScale, heightBlock3, origin3, totalSize, tempOffset3, + offsetAmount); + patch3.setQuadrant((short) 3); + this.attachChild(patch3); + patch3.setModelBound(new BoundingBox()); + patch3.updateModelBound(); + //patch3.setLodCalculator(lodCalculator); + //TangentBinormalGenerator.generate(patch3); + + // 4 upper right + float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1, + split - 1, split); + + Vector3f origin4 = new Vector3f(0, 0, 0); + + Vector2f tempOffset4 = new Vector2f(); + tempOffset4.x = offset.x; + tempOffset4.y = offset.y; + tempOffset4.x += quarterSize * stepScale.x; + tempOffset4.y += quarterSize * stepScale.z; + + TerrainPatch patch4 = new TerrainPatch(getName() + "Patch4", split, + stepScale, heightBlock4, origin4, totalSize, tempOffset4, + offsetAmount); + patch4.setQuadrant((short) 4); + this.attachChild(patch4); + patch4.setModelBound(new BoundingBox()); + patch4.updateModelBound(); + //patch4.setLodCalculator(lodCalculator); + //TangentBinormalGenerator.generate(patch4); + } + + public float[] createHeightSubBlock(float[] heightMap, int x, + int y, int side) { + float[] rVal = new float[side * side]; + int bsize = (int) FastMath.sqrt(heightMap.length); + int count = 0; + for (int i = y; i < side + y; i++) { + for (int j = x; j < side + x; j++) { + if (j < bsize && i < bsize) + rVal[count] = heightMap[j + (i * bsize)]; + count++; + } + } + return rVal; + } + + /** + * A handy method that will attach all bounding boxes of this terrain + * to the node you supply. + * Useful to visualize the bounding boxes when debugging. + * + * @param parent that will get the bounding box shapes of the terrain attached to + */ + public void attachBoundChildren(Node parent) { + for (int i = 0; i < this.getQuantity(); i++) { + if (this.getChild(i) instanceof TerrainQuad) { + ((TerrainQuad) getChild(i)).attachBoundChildren(parent); + } else if (this.getChild(i) instanceof TerrainPatch) { + BoundingVolume bv = getChild(i).getWorldBound(); + if (bv instanceof BoundingBox) { + attachBoundingBox((BoundingBox)bv, parent); + } + } + } + BoundingVolume bv = getWorldBound(); + if (bv instanceof BoundingBox) { + attachBoundingBox((BoundingBox)bv, parent); + } + } + + /** + * used by attachBoundChildren() + */ + private void attachBoundingBox(BoundingBox bb, Node parent) { + WireBox wb = new WireBox(bb.getXExtent(), bb.getYExtent(), bb.getZExtent()); + Geometry g = new Geometry(); + g.setMesh(wb); + g.setLocalTranslation(bb.getCenter()); + parent.attachChild(g); + } + + /** + * Signal if the normal vectors for the terrain need to be recalculated. + * Does this by looking at the affectedAreaBBox bounding box. If the bbox + * exists already, then it will grow the box to fit the new changedPoint. + * If the affectedAreaBBox is null, then it will create one of unit size. + * + * @param needToRecalculateNormals if null, will cause needToRecalculateNormals() to return false + */ + protected void setNormalRecalcNeeded(Vector2f changedPoint) { + if (changedPoint == null) { // set needToRecalculateNormals() to false + affectedAreaBBox = null; + return; + } + + if (affectedAreaBBox == null) { + affectedAreaBBox = new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f); // unit length + } else { + // adjust size of box to be larger + affectedAreaBBox.mergeLocal(new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f)); + } + } + + protected boolean needToRecalculateNormals() { + if (affectedAreaBBox != null) + return true; + if (!lastScale.equals(getWorldScale())) { + affectedAreaBBox = new BoundingBox(getWorldTranslation(), Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE); + lastScale = getWorldScale(); + return true; + } + return false; + } + + /** + * This will cause all normals for this terrain quad to be recalculated + */ + protected void setNeedToRecalculateNormals() { + affectedAreaBBox = new BoundingBox(getWorldTranslation(), Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE); + } + + public float getHeightmapHeight(Vector2f xz) { + // offset + int halfSize = totalSize / 2; + int x = Math.round((xz.x / getWorldScale().x) + halfSize); + int z = Math.round((xz.y / getWorldScale().z) + halfSize); + + if (!isInside(x, z)) + return Float.NaN; + return getHeightmapHeight(x, z); + } + + /** + * This will just get the heightmap value at the supplied point, + * not an interpolated (actual) height value. + */ + protected float getHeightmapHeight(int x, int z) { + int quad = findQuadrant(x, z); + int split = (size + 1) >> 1; + if (children != null) { + for (int i = children.size(); --i >= 0;) { + Spatial spat = children.get(i); + int col = x; + int row = z; + boolean match = false; + + // get the childs quadrant + int childQuadrant = 0; + if (spat instanceof TerrainQuad) { + childQuadrant = ((TerrainQuad) spat).getQuadrant(); + } else if (spat instanceof TerrainPatch) { + childQuadrant = ((TerrainPatch) spat).getQuadrant(); + } + + if (childQuadrant == 1 && (quad & 1) != 0) { + match = true; + } else if (childQuadrant == 2 && (quad & 2) != 0) { + row = z - split + 1; + match = true; + } else if (childQuadrant == 3 && (quad & 4) != 0) { + col = x - split + 1; + match = true; + } else if (childQuadrant == 4 && (quad & 8) != 0) { + col = x - split + 1; + row = z - split + 1; + match = true; + } + + if (match) { + if (spat instanceof TerrainQuad) { + return ((TerrainQuad) spat).getHeightmapHeight(col, row); + } else if (spat instanceof TerrainPatch) { + return ((TerrainPatch) spat).getHeightmapHeight(col, row); + } + } + + } + } + return Float.NaN; + } + + protected Vector3f getMeshNormal(int x, int z) { + int quad = findQuadrant(x, z); + int split = (size + 1) >> 1; + if (children != null) { + for (int i = children.size(); --i >= 0;) { + Spatial spat = children.get(i); + int col = x; + int row = z; + boolean match = false; + + // get the childs quadrant + int childQuadrant = 0; + if (spat instanceof TerrainQuad) { + childQuadrant = ((TerrainQuad) spat).getQuadrant(); + } else if (spat instanceof TerrainPatch) { + childQuadrant = ((TerrainPatch) spat).getQuadrant(); + } + + if (childQuadrant == 1 && (quad & 1) != 0) { + match = true; + } else if (childQuadrant == 2 && (quad & 2) != 0) { + row = z - split + 1; + match = true; + } else if (childQuadrant == 3 && (quad & 4) != 0) { + col = x - split + 1; + match = true; + } else if (childQuadrant == 4 && (quad & 8) != 0) { + col = x - split + 1; + row = z - split + 1; + match = true; + } + + if (match) { + if (spat instanceof TerrainQuad) { + return ((TerrainQuad) spat).getMeshNormal(col, row); + } else if (spat instanceof TerrainPatch) { + return ((TerrainPatch) spat).getMeshNormal(col, row); + } + } + + } + } + return null; + } + + /** + * is the 2d point inside the terrain? + * @param x local coordinate + * @param z local coordinate + */ + private boolean isInside(int x, int z) { + if (x < 0 || z < 0 || x > totalSize || z > totalSize) + return false; + return true; + } + + /** + * Used for searching for a child and keeping + * track of its quadrant + */ + private class QuadrantChild { + int col; + int row; + Spatial child; + + QuadrantChild(int col, int row, Spatial child) { + this.col = col; + this.row = row; + this.child = child; + } + } + + private QuadrantChild findMatchingChild(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) + return new QuadrantChild(col, row, spat); + } + } + return null; + } + + /** + * Get the interpolated height of the terrain at the specified point. + * @param xz the location to get the height for + * @return Float.NAN if the value does not exist, or the coordinates are outside of the terrain + */ + public float getHeight(Vector2f xz) { + // offset + float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)(totalSize-1) / 2f); + float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)(totalSize-1) / 2f); + if (!isInside((int)x, (int)z)) + return Float.NaN; + float height = getHeight((int)x, (int)z, (x%1f), (z%1f)); + height *= getWorldScale().y; + return height; + } + + /* + * gets an interpolated value at the specified point + */ + protected float getHeight(int x, int z, float xm, float zm) { + + QuadrantChild match = findMatchingChild(x,z); + if (match != null) { + if (match.child instanceof TerrainQuad) { + return ((TerrainQuad) match.child).getHeight(match.col, match.row, xm, zm); + } else if (match.child instanceof TerrainPatch) { + return ((TerrainPatch) match.child).getHeight(match.col, match.row, xm, zm); + } + } + return Float.NaN; + } + + public Vector3f getNormal(Vector2f xz) { + // offset + float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)(totalSize-1) / 2f); + float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)(totalSize-1) / 2f); + Vector3f normal = getNormal(x, z, xz); + + return normal; + } + + protected Vector3f getNormal(float x, float z, Vector2f xz) { + x-=0.5f; + z-=0.5f; + float col = FastMath.floor(x); + float row = FastMath.floor(z); + boolean onX = false; + if(1 - (x - col)-(z - row) < 0) // what triangle to interpolate on + onX = true; + // v1--v2 ^ + // | / | | + // | / | | + // v3--v4 | Z + // | + // <-------Y + // X + Vector3f n1 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.ceil(z)); + Vector3f n2 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.ceil(z)); + Vector3f n3 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.floor(z)); + Vector3f n4 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.floor(z)); + + return n1.add(n2).add(n3).add(n4).normalize(); + } + + public void setHeight(Vector2f xz, float height) { + List coord = new ArrayList(); + coord.add(xz); + List h = new ArrayList(); + h.add(height); + + setHeight(coord, h); + } + + public void adjustHeight(Vector2f xz, float delta) { + List coord = new ArrayList(); + coord.add(xz); + List h = new ArrayList(); + h.add(delta); + + adjustHeight(coord, h); + } + + public void setHeight(List xz, List height) { + setHeight(xz, height, true); + } + + public void adjustHeight(List xz, List height) { + setHeight(xz, height, false); + } + + protected void setHeight(List xz, List height, boolean overrideHeight) { + if (xz.size() != height.size()) + throw new IllegalArgumentException("Both lists must be the same length!"); + + int halfSize = totalSize / 2; + + List locations = new ArrayList(); + + // offset + for (int i=0; i locations, boolean overrideHeight) { + if (children == null) + return; + + List quadLH1 = new ArrayList(); + List quadLH2 = new ArrayList(); + List quadLH3 = new ArrayList(); + List quadLH4 = new ArrayList(); + Spatial quad1 = null; + Spatial quad2 = null; + Spatial quad3 = null; + Spatial quad4 = null; + + // get the child quadrants + for (int i = children.size(); --i >= 0;) { + Spatial spat = children.get(i); + int childQuadrant = 0; + if (spat instanceof TerrainQuad) { + childQuadrant = ((TerrainQuad) spat).getQuadrant(); + } else if (spat instanceof TerrainPatch) { + childQuadrant = ((TerrainPatch) spat).getQuadrant(); + } + + if (childQuadrant == 1) + quad1 = spat; + else if (childQuadrant == 2) + quad2 = spat; + else if (childQuadrant == 3) + quad3 = spat; + else if (childQuadrant == 4) + quad4 = spat; + } + + int split = (size + 1) >> 1; + + // distribute each locationHeight into the quadrant it intersects + for (LocationHeight lh : locations) { + int quad = findQuadrant(lh.x, lh.z); + int col = lh.x; + int row = lh.z; + + if ((quad & 1) != 0) { + quadLH1.add(lh); + } + if ((quad & 2) != 0) { + row = lh.z - split + 1; + quadLH2.add(new LocationHeight(lh.x, row, lh.h)); + } + if ((quad & 4) != 0) { + col = lh.x - split + 1; + quadLH3.add(new LocationHeight(col, lh.z, lh.h)); + } + if ((quad & 8) != 0) { + col = lh.x - split + 1; + row = lh.z - split + 1; + quadLH4.add(new LocationHeight(col, row, lh.h)); + } + } + + // send the locations to the children + if (!quadLH1.isEmpty()) { + if (quad1 instanceof TerrainQuad) + ((TerrainQuad)quad1).setHeight(quadLH1, overrideHeight); + else if(quad1 instanceof TerrainPatch) + ((TerrainPatch)quad1).setHeight(quadLH1, overrideHeight); + } + + if (!quadLH2.isEmpty()) { + if (quad2 instanceof TerrainQuad) + ((TerrainQuad)quad2).setHeight(quadLH2, overrideHeight); + else if(quad2 instanceof TerrainPatch) + ((TerrainPatch)quad2).setHeight(quadLH2, overrideHeight); + } + + if (!quadLH3.isEmpty()) { + if (quad3 instanceof TerrainQuad) + ((TerrainQuad)quad3).setHeight(quadLH3, overrideHeight); + else if(quad3 instanceof TerrainPatch) + ((TerrainPatch)quad3).setHeight(quadLH3, overrideHeight); + } + + if (!quadLH4.isEmpty()) { + if (quad4 instanceof TerrainQuad) + ((TerrainQuad)quad4).setHeight(quadLH4, overrideHeight); + else if(quad4 instanceof TerrainPatch) + ((TerrainPatch)quad4).setHeight(quadLH4, overrideHeight); + } + } + + protected boolean isPointOnTerrain(int x, int z) { + return (x >= 0 && x <= totalSize && z >= 0 && z <= totalSize); + } + + + public int getTerrainSize() { + return totalSize; + } + + + // a position can be in multiple quadrants, so use a bit anded value. + private int findQuadrant(int x, int y) { + int split = (size + 1) >> 1; + int quads = 0; + if (x < split && y < split) + quads |= 1; + if (x < split && y >= split - 1) + quads |= 2; + if (x >= split - 1 && y < split) + quads |= 4; + if (x >= split - 1 && y >= split - 1) + quads |= 8; + return quads; + } + + /** + * lock or unlock the meshes of this terrain. + * Locked meshes are uneditable but have better performance. + * @param locked or unlocked + */ + public void setLocked(boolean locked) { + for (int i = 0; i < this.getQuantity(); i++) { + if (this.getChild(i) instanceof TerrainQuad) { + ((TerrainQuad) getChild(i)).setLocked(locked); + } else if (this.getChild(i) instanceof TerrainPatch) { + if (locked) + ((TerrainPatch) getChild(i)).lockMesh(); + else + ((TerrainPatch) getChild(i)).unlockMesh(); + } + } + } + + + public int getQuadrant() { + return quadrant; + } + + public void setQuadrant(short quadrant) { + this.quadrant = quadrant; + } + + + protected TerrainPatch getPatch(int quad) { + if (children != null) + for (int x = children.size(); --x >= 0;) { + Spatial child = children.get(x); + if (child instanceof TerrainPatch) { + TerrainPatch tb = (TerrainPatch) child; + if (tb.getQuadrant() == quad) + return tb; + } + } + return null; + } + + protected TerrainQuad getQuad(int quad) { + if (quad == 0) + return this; + if (children != null) + for (int x = children.size(); --x >= 0;) { + Spatial child = children.get(x); + if (child instanceof TerrainQuad) { + TerrainQuad tq = (TerrainQuad) child; + if (tq.getQuadrant() == quad) + return tq; + } + } + return null; + } + + protected TerrainPatch findRightPatch(TerrainPatch tp) { + if (tp.getQuadrant() == 1) + return getPatch(3); + else if (tp.getQuadrant() == 2) + return getPatch(4); + else if (tp.getQuadrant() == 3) { + // find the patch to the right and ask it for child 1. + TerrainQuad quad = findRightQuad(); + if (quad != null) + return quad.getPatch(1); + } else if (tp.getQuadrant() == 4) { + // find the patch to the right and ask it for child 2. + TerrainQuad quad = findRightQuad(); + if (quad != null) + return quad.getPatch(2); + } + + return null; + } + + protected TerrainPatch findDownPatch(TerrainPatch tp) { + if (tp.getQuadrant() == 1) + return getPatch(2); + else if (tp.getQuadrant() == 3) + return getPatch(4); + else if (tp.getQuadrant() == 2) { + // find the patch below and ask it for child 1. + TerrainQuad quad = findDownQuad(); + if (quad != null) + return quad.getPatch(1); + } else if (tp.getQuadrant() == 4) { + TerrainQuad quad = findDownQuad(); + if (quad != null) + return quad.getPatch(3); + } + + return null; + } + + + protected TerrainPatch findTopPatch(TerrainPatch tp) { + if (tp.getQuadrant() == 2) + return getPatch(1); + else if (tp.getQuadrant() == 4) + return getPatch(3); + else if (tp.getQuadrant() == 1) { + // find the patch above and ask it for child 2. + TerrainQuad quad = findTopQuad(); + if (quad != null) + return quad.getPatch(2); + } else if (tp.getQuadrant() == 3) { + TerrainQuad quad = findTopQuad(); + if (quad != null) + return quad.getPatch(4); + } + + return null; + } + + protected TerrainPatch findLeftPatch(TerrainPatch tp) { + if (tp.getQuadrant() == 3) + return getPatch(1); + else if (tp.getQuadrant() == 4) + return getPatch(2); + else if (tp.getQuadrant() == 1) { + // find the patch above and ask it for child 3. + 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() { + boolean useFinder = false; + if (getParent() == null || !(getParent() instanceof TerrainQuad)) { + if (neighbourFinder == null) + return null; + else + useFinder = true; + } + + TerrainQuad pQuad = null; + if (!useFinder) + 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); + } else if (quadrant == 0) { + // at the top quad + if (useFinder) { + TerrainQuad quad = neighbourFinder.getRightQuad(this); + return quad; + } + } + + return null; + } + + protected TerrainQuad findDownQuad() { + boolean useFinder = false; + if (getParent() == null || !(getParent() instanceof TerrainQuad)) { + if (neighbourFinder == null) + return null; + else + useFinder = true; + } + + TerrainQuad pQuad = null; + if (!useFinder) + 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); + } else if (quadrant == 0) { + // at the top quad + if (useFinder) { + TerrainQuad quad = neighbourFinder.getDownQuad(this); + return quad; + } + } + + return null; + } + + protected TerrainQuad findTopQuad() { + boolean useFinder = false; + if (getParent() == null || !(getParent() instanceof TerrainQuad)) { + if (neighbourFinder == null) + return null; + else + useFinder = true; + } + + TerrainQuad pQuad = null; + if (!useFinder) + 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); + } else if (quadrant == 0) { + // at the top quad + if (useFinder) { + TerrainQuad quad = neighbourFinder.getTopQuad(this); + return quad; + } + } + + return null; + } + + protected TerrainQuad findLeftQuad() { + boolean useFinder = false; + if (getParent() == null || !(getParent() instanceof TerrainQuad)) { + if (neighbourFinder == null) + return null; + else + useFinder = true; + } + + TerrainQuad pQuad = null; + if (!useFinder) + 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); + } else if (quadrant == 0) { + // at the top quad + if (useFinder) { + TerrainQuad quad = neighbourFinder.getLeftQuad(this); + return quad; + } + } + + 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); + tp.ensurePositiveVolumeBBox(); + if (tp.getWorldBound().intersects(toTest)) { + CollisionResults cr = new CollisionResults(); + toTest.collideWith(tp.getWorldBound(), cr); + if (cr != null && cr.getClosestCollision() != null) { + cr.getClosestCollision().getDistance(); + results.add(new TerrainPickData(tp, cr.getClosestCollision())); + } + } + } + else if (children.get(i) instanceof TerrainQuad) { + ((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); + //lodCalculator = (LodCalculator) c.readSavable("lodCalculator", createDefaultLodCalculator()); + //lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null); + + if ( !(getParent() instanceof TerrainQuad) ) { + BoundingBox all = new BoundingBox(getWorldTranslation(), totalSize, totalSize, totalSize); + affectedAreaBBox = all; + updateNormals(); + } + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule c = e.getCapsule(this); + c.write(size, "size", 0); + c.write(totalSize, "totalSize", 0); + c.write(stepScale, "stepScale", null); + c.write(offset, "offset", new Vector2f(0,0)); + c.write(offsetAmount, "offsetAmount", 0); + c.write(quadrant, "quadrant", 0); + //c.write(lodCalculatorFactory, "lodCalculatorFactory", null); + //c.write(lodCalculator, "lodCalculator", null); + } + + @Override + public TerrainQuad clone() { + return this.clone(true); + } + + @Override + public TerrainQuad clone(boolean cloneMaterials) { + TerrainQuad quadClone = (TerrainQuad) super.clone(cloneMaterials); + quadClone.name = name.toString(); + quadClone.size = size; + quadClone.totalSize = totalSize; + if (stepScale != null) { + quadClone.stepScale = stepScale.clone(); + } + if (offset != null) { + quadClone.offset = offset.clone(); + } + quadClone.offsetAmount = offsetAmount; + quadClone.quadrant = quadrant; + //quadClone.lodCalculatorFactory = lodCalculatorFactory.clone(); + //quadClone.lodCalculator = lodCalculator.clone(); + + TerrainLodControl lodControlCloned = this.getControl(TerrainLodControl.class); + TerrainLodControl lodControl = quadClone.getControl(TerrainLodControl.class); + + if (lodControlCloned != null && !(getParent() instanceof TerrainQuad)) { + //lodControlCloned.setLodCalculator(lodControl.getLodCalculator().clone()); + } + NormalRecalcControl normalControl = getControl(NormalRecalcControl.class); + if (normalControl != null) + normalControl.setTerrain(this); + + return quadClone; + } + + @Override + protected void setParent(Node parent) { + super.setParent(parent); + if (parent == null) { + // if the terrain is being detached + clearCaches(); + } + } + + /** + * Removes any cached references this terrain is holding, in particular + * the TerrainPatch's neighbour references. + * This is called automatically when the root terrainQuad is detached from + * its parent or if setParent(null) is called. + */ + public void clearCaches() { + if (children != null) { + for (int i = children.size(); --i >= 0;) { + Spatial child = children.get(i); + if (child instanceof TerrainQuad) { + ((TerrainQuad) child).clearCaches(); + } else if (child instanceof TerrainPatch) { + ((TerrainPatch) child).clearCaches(); + } + } + } + } + + public int getMaxLod() { + if (maxLod < 0) + maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide + + return maxLod; + } + + public int getPatchSize() { + return patchSize; + } + + public int getTotalSize() { + return totalSize; + } + + public float[] getHeightMap() { + + float[] hm = null; + int length = ((size-1)/2)+1; + int area = size*size; + hm = new float[area]; + + if (getChildren() != null && !getChildren().isEmpty()) { + float[] ul=null, ur=null, bl=null, br=null; + // get the child heightmaps + if (getChild(0) instanceof TerrainPatch) { + for (Spatial s : getChildren()) { + if ( ((TerrainPatch)s).getQuadrant() == 1) + ul = ((TerrainPatch)s).getHeightMap(); + else if(((TerrainPatch) s).getQuadrant() == 2) + bl = ((TerrainPatch)s).getHeightMap(); + else if(((TerrainPatch) s).getQuadrant() == 3) + ur = ((TerrainPatch)s).getHeightMap(); + else if(((TerrainPatch) s).getQuadrant() == 4) + br = ((TerrainPatch)s).getHeightMap(); + } + } + else { + ul = getQuad(1).getHeightMap(); + bl = getQuad(2).getHeightMap(); + ur = getQuad(3).getHeightMap(); + br = getQuad(4).getHeightMap(); + } + + // combine them into a single heightmap + + + // first upper blocks + for (int y=0; y locations, HashMap updates) { + if (locations == null || locations.isEmpty()) + return false;// no camera yet + float distance = getCenterLocation(terrainPatch).distance(locations.get(0)); + + if (turnOffLod) { + // set to full detail + int prevLOD = terrainPatch.getLod(); + UpdatedTerrainPatch utp = updates.get(terrainPatch.getName()); + if (utp == null) { + utp = new UpdatedTerrainPatch(terrainPatch); + updates.put(utp.getName(), utp); + } + utp.setNewLod(0); + utp.setPreviousLod(prevLOD); + //utp.setReIndexNeeded(true); + return true; + } + + // go through each lod level to find the one we are in + for (int i = 0; i <= terrainPatch.getMaxLod(); i++) { + if (distance < getLodDistanceThreshold() * (i + 1)*terrainPatch.getWorldScaleCached().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(); + + UpdatedTerrainPatch utp = updates.get(terrainPatch.getName()); + if (utp == null) { + utp = new UpdatedTerrainPatch(terrainPatch);//save in here, do not update actual variables + updates.put(utp.getName(), utp); + } + utp.setNewLod(i); + utp.setPreviousLod(prevLOD); + //utp.setReIndexNeeded(reIndexNeeded); + + return reIndexNeeded; + } + } + + return false; + } + + protected Vector3f getCenterLocation(TerrainPatch terrainPatch) { + Vector3f loc = terrainPatch.getWorldTranslationCached(); + loc.x += terrainPatch.getSize()*terrainPatch.getWorldScaleCached().x / 2; + loc.z += terrainPatch.getSize()*terrainPatch.getWorldScaleCached().z / 2; + return loc; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(size, "patchSize", 32); + oc.write(lodMultiplier, "lodMultiplier", 32); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + size = ic.readInt("patchSize", 32); + lodMultiplier = ic.readFloat("lodMultiplier", 2.7f); + } + + @Override + public LodCalculator clone() { + DistanceLodCalculator clone = new DistanceLodCalculator(size, lodMultiplier); + return clone; + } + + @Override + public String toString() { + return "DistanceLodCalculator "+size+"*"+lodMultiplier; + } + + /** + * Gets the camera distance where the LOD level will change + */ + protected float getLodDistanceThreshold() { + return size*lodMultiplier; + } + + /** + * Does this calculator require the terrain to have the difference of + * LOD levels of neighbours to be more than 1. + */ + public boolean usesVariableLod() { + return false; + } + + public float getLodMultiplier() { + return lodMultiplier; + } + + public void setLodMultiplier(float lodMultiplier) { + this.lodMultiplier = lodMultiplier; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public void turnOffLod() { + turnOffLod = true; + } + + public boolean isLodOff() { + return turnOffLod; + } + + public void turnOnLod() { + turnOffLod = false; + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodCalculator.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodCalculator.java new file mode 100644 index 000000000..bf1b6f8e1 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodCalculator.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap.lodcalc; + +import com.jme3.export.Savable; +import com.jme3.math.Vector3f; +import com.jme3.terrain.geomipmap.TerrainPatch; +import com.jme3.terrain.geomipmap.UpdatedTerrainPatch; +import java.util.HashMap; +import java.util.List; + +/** + * Calculate the Level of Detail of a terrain patch based on the + * cameras, or other locations. + * + * @author Brent Owens + */ +public interface LodCalculator extends Savable, Cloneable { + + public boolean calculateLod(TerrainPatch terrainPatch, List locations, HashMap updates); + + public LodCalculator clone(); + + public void turnOffLod(); + public void turnOnLod(); + public boolean isLodOff(); + + /** + * If true, then this calculator can cause neighbouring terrain chunks to + * have LOD levels that are greater than 1 apart. + * Entropy algorithms will want to return true for this. Straight distance + * calculations will just want to return false. + */ + public boolean usesVariableLod(); +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodCalculatorFactory.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodCalculatorFactory.java new file mode 100644 index 000000000..bfbd24a11 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodCalculatorFactory.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap.lodcalc; + +import com.jme3.export.Savable; +import com.jme3.terrain.geomipmap.TerrainPatch; + +/** + * Creates LOD Calculator objects for the terrain patches. + * + * @author Brent Owens + * @deprecated phasing this out + */ +public interface LodCalculatorFactory extends Savable, Cloneable { + + public LodCalculator createCalculator(); + public LodCalculator createCalculator(TerrainPatch terrainPatch); + + public LodCalculatorFactory clone(); +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodDistanceCalculatorFactory.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodDistanceCalculatorFactory.java new file mode 100644 index 000000000..ab6e0037e --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodDistanceCalculatorFactory.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap.lodcalc; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.terrain.geomipmap.TerrainPatch; +import java.io.IOException; + +/** + * + * @author bowens + * @deprecated phasing out + */ +public class LodDistanceCalculatorFactory implements LodCalculatorFactory { + + private float lodThresholdSize = 2.7f; + private LodThreshold lodThreshold = null; + + + public LodDistanceCalculatorFactory() { + } + + public LodDistanceCalculatorFactory(LodThreshold lodThreshold) { + this.lodThreshold = lodThreshold; + } + + public LodCalculator createCalculator() { + return new DistanceLodCalculator(); + } + + public LodCalculator createCalculator(TerrainPatch terrainPatch) { + return new DistanceLodCalculator(); + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule c = ex.getCapsule(this); + c.write(lodThreshold, "lodThreshold", null); + c.write(lodThresholdSize, "lodThresholdSize", 2); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule c = im.getCapsule(this); + lodThresholdSize = c.readFloat("lodThresholdSize", 2); + lodThreshold = (LodThreshold) c.readSavable("lodThreshold", null); + } + + @Override + public LodDistanceCalculatorFactory clone() { + LodDistanceCalculatorFactory clone = new LodDistanceCalculatorFactory(); + clone.lodThreshold = lodThreshold.clone(); + clone.lodThresholdSize = lodThresholdSize; + return clone; + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodPerspectiveCalculatorFactory.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodPerspectiveCalculatorFactory.java new file mode 100644 index 000000000..cb4a7d41e --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodPerspectiveCalculatorFactory.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap.lodcalc; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.renderer.Camera; +import com.jme3.terrain.geomipmap.TerrainPatch; +import java.io.IOException; + +/** + * TODO: Make it work with multiple cameras + * TODO: Fix the cracks when the lod differences are greater than 1 + * for two adjacent blocks. + * @deprecated phasing out + */ +public class LodPerspectiveCalculatorFactory implements LodCalculatorFactory { + + private Camera cam; + private float pixelError; + + public LodPerspectiveCalculatorFactory(Camera cam, float pixelError){ + this.cam = cam; + this.pixelError = pixelError; + } + + public LodCalculator createCalculator() { + return new PerspectiveLodCalculator(cam, pixelError); + } + + public LodCalculator createCalculator(TerrainPatch terrainPatch) { + PerspectiveLodCalculator p = new PerspectiveLodCalculator(cam, pixelError); + return p; + } + + public void write(JmeExporter ex) throws IOException { + } + + public void read(JmeImporter im) throws IOException { + } + + @Override + public LodCalculatorFactory clone() { + try { + return (LodCalculatorFactory) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodThreshold.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodThreshold.java new file mode 100644 index 000000000..b085a07f5 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/LodThreshold.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap.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/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java new file mode 100644 index 000000000..b26699631 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap.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 Camera cam; + private float pixelError; + private boolean turnOffLod = false; + + public PerspectiveLodCalculator() {} + + public PerspectiveLodCalculator(Camera cam, float pixelError) { + this.cam = cam; + this.pixelError = pixelError; + } + + /** + * This computes the "C" value in the geomipmapping paper. + * See section "2.3.1.2 Pre-calculating d" + * + * @param cam + * @param pixelLimit + * @return + */ + private float getCameraConstant(Camera cam, float pixelLimit){ + float n = cam.getFrustumNear(); + float t = FastMath.abs(cam.getFrustumTop()); + float A = n / t; + float v_res = cam.getHeight(); + float T = (2f * pixelLimit) / v_res; + return A / T; + } + + public boolean calculateLod(TerrainPatch patch, List locations, HashMap updates) { + if (turnOffLod) { + // set to full detail + int prevLOD = patch.getLod(); + UpdatedTerrainPatch utp = updates.get(patch.getName()); + if (utp == null) { + utp = new UpdatedTerrainPatch(patch); + updates.put(utp.getName(), utp); + } + utp.setNewLod(0); + utp.setPreviousLod(prevLOD); + //utp.setReIndexNeeded(true); + return true; + } + + float[] lodEntropies = patch.getLodEntropies(); + float cameraConstant = getCameraConstant(cam, pixelError); + + Vector3f patchPos = getCenterLocation(patch); + + // vector from camera to patch + //Vector3f toPatchDir = locations.get(0).subtract(patchPos).normalizeLocal(); + //float facing = cam.getDirection().dot(toPatchDir); + float distance = patchPos.distance(locations.get(0)); + + // go through each lod level to find the one we are in + for (int i = 0; i <= patch.getMaxLod(); i++) { + if (distance < lodEntropies[i] * cameraConstant || 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(); + + + UpdatedTerrainPatch utp = updates.get(patch.getName()); + if (utp == null) { + utp = new UpdatedTerrainPatch(patch);//save in here, do not update actual variables + updates.put(utp.getName(), utp); + } + utp.setNewLod(i); + utp.setPreviousLod(prevLOD); + //utp.setReIndexNeeded(reIndexNeeded); + return reIndexNeeded; + } + } + + return false; + } + + public Vector3f getCenterLocation(TerrainPatch patch) { + Vector3f loc = patch.getWorldTranslation().clone(); + loc.x += patch.getSize() / 2; + loc.z += patch.getSize() / 2; + return loc; + } + + @Override + public LodCalculator clone() { + try { + return (LodCalculator) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + public void write(JmeExporter ex) throws IOException { + + } + + public void read(JmeImporter im) throws IOException { + } + + public boolean usesVariableLod() { + return true; + } + + public float getPixelError() { + return pixelError; + } + + public void setPixelError(float pixelError) { + this.pixelError = pixelError; + } + + public void setCam(Camera cam) { + this.cam = cam; + } + + public void turnOffLod() { + turnOffLod = true; + } + + public boolean isLodOff() { + return turnOffLod; + } + + public void turnOnLod() { + turnOffLod = false; + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/SimpleLodThreshold.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/SimpleLodThreshold.java new file mode 100644 index 000000000..6f7ea57dc --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/SimpleLodThreshold.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap.lodcalc; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.terrain.Terrain; +import com.jme3.terrain.geomipmap.TerrainQuad; +import java.io.IOException; + + +/** + * Just multiplies the terrain patch size by 2. So every two + * patches away the camera is, the LOD changes. + * + * Set it higher to have the LOD change less frequently. + * + * @author bowens + */ +public class SimpleLodThreshold implements LodThreshold { + + private int size; // size of a terrain patch + private float lodMultiplier = 2; + + + public SimpleLodThreshold() { + } + + public SimpleLodThreshold(Terrain terrain) { + if (terrain instanceof TerrainQuad) + this.size = ((TerrainQuad)terrain).getPatchSize(); + } + + public SimpleLodThreshold(int patchSize, float lodMultiplier) { + this.size = patchSize; + } + + public float getLodMultiplier() { + return lodMultiplier; + } + + public void setLodMultiplier(float lodMultiplier) { + this.lodMultiplier = lodMultiplier; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + + public float getLodDistanceThreshold() { + return size*lodMultiplier; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(size, "size", 16); + oc.write(lodMultiplier, "lodMultiplier", 2); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + size = ic.readInt("size", 16); + lodMultiplier = ic.readInt("lodMultiplier", 2); + } + + @Override + public LodThreshold clone() { + SimpleLodThreshold clone = new SimpleLodThreshold(); + clone.size = size; + clone.lodMultiplier = lodMultiplier; + + return clone; + } + + @Override + public String toString() { + return "SimpleLodThreshold "+size+", "+lodMultiplier; + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/util/EntropyComputeUtil.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/util/EntropyComputeUtil.java new file mode 100644 index 000000000..0f638830e --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/util/EntropyComputeUtil.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap.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.Buffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +/** + * 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, Buffer 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); + if (lodIndices instanceof IntBuffer) + terrainBlock.setBuffer(Type.Index, 3, (IntBuffer)lodIndices); + else if (lodIndices instanceof ShortBuffer) { + terrainBlock.setBuffer(Type.Index, 3, (ShortBuffer) lodIndices); + } + + // Recalculate collision mesh + terrainBlock.createCollisionData(); + + float entropy = 0; + for (int i = 0; i < positions.limit() / 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/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/BresenhamTerrainPicker.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/BresenhamTerrainPicker.java new file mode 100644 index 000000000..cf7efc085 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/BresenhamTerrainPicker.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap.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 patch + * the TerrainPatch 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 patch + * the patch 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/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/BresenhamYUpGridTracer.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/BresenhamYUpGridTracer.java new file mode 100644 index 000000000..28d968a8a --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/BresenhamYUpGridTracer.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap.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/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPickData.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPickData.java new file mode 100644 index 000000000..576dd15ca --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPickData.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap.picking; + +import com.jme3.collision.CollisionResult; +import com.jme3.terrain.geomipmap.TerrainPatch; + +/** + * Pick result on a terrain patch with the intersection on the bounding box + * of that terrain patch. + * + * @author Brent Owens + */ +public class TerrainPickData implements Comparable { + + protected TerrainPatch targetPatch; + protected CollisionResult cr; + + public TerrainPickData() { + } + + public TerrainPickData(TerrainPatch patch, CollisionResult cr) { + this.targetPatch = patch; + this.cr = cr; + } + + public int compareTo(Object o) { + if (o instanceof TerrainPickData) { + TerrainPickData tpd = (TerrainPickData) o; + if (this.cr.getDistance() < tpd.cr.getDistance()) + return -1; + else if (this.cr.getDistance() == tpd.cr.getDistance()) + return 0; + else + return 1; + } + + return 0; + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof TerrainPickData){ + return ((TerrainPickData)obj).compareTo(this) == 0; + } + return super.equals(obj); + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPicker.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPicker.java new file mode 100644 index 000000000..08308849f --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPicker.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.geomipmap.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/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/AbstractHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/AbstractHeightMap.java new file mode 100644 index 000000000..21c504528 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/AbstractHeightMap.java @@ -0,0 +1,495 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +import java.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.fine( 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. + FileOutputStream fos = null; + DataOutputStream dos = null; + try { + fos = new FileOutputStream(filename); + 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; + } finally { + if (fos != null) { + fos.close(); + } + if (dos != null) { + dos.close(); + } + } + + logger.log(Level.FINE, "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; + } + } + } + + /** + * Find the minimum and maximum height values. + * @return a float array with two value: min height, max height + */ + public float[] findMinMaxHeights() { + float[] minmax = new float[2]; + + float currentMin, currentMax; + currentMin = heightData[0]; + currentMax = heightData[0]; + + for (int i = 0; i < heightData.length; i++) { + if (heightData[i] > currentMax) { + currentMax = heightData[i]; + } else if (heightData[i] < currentMin) { + currentMin = heightData[i]; + } + } + minmax[0] = currentMin; + minmax[1] = currentMax; + return minmax; + } + + /** + * 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; + } + + float[] minmax = findMinMaxHeights(); + + normalizeTerrain(1f); + + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + float flat = 1.0f; + float original = heightData[x + y * size]; + + // Flatten as many times as desired; + for (int i = 0; i < flattening; i++) { + flat *= original; + } + heightData[x + y * size] = flat; + } + } + + // re-normalize back to its oraginal height range + float height = minmax[1] - minmax[0]; + normalizeTerrain(height); + } + + /** + * Smooth the terrain. For each node, its 8 neighbors heights + * are averaged and will participate in the node new height + * by a factor np between 0 and 1 + * + * You must first load() the heightmap data before this will have any effect. + * + * @param np + * The factor to what extend the neighbors average has an influence. + * Value of 0 will ignore neighbors (no smoothing) + * Value of 1 will ignore the node old height. + */ + public void smooth(float np) { + smooth(np, 1); + } + + /** + * Smooth the terrain. For each node, its X(determined by radius) neighbors heights + * are averaged and will participate in the node new height + * by a factor np between 0 and 1 + * + * You must first load() the heightmap data before this will have any effect. + * + * @param np + * The factor to what extend the neighbors average has an influence. + * Value of 0 will ignore neighbors (no smoothing) + * Value of 1 will ignore the node old height. + */ + public void smooth(float np, int radius) { + if (np < 0 || np > 1) { + return; + } + if (radius == 0) + radius = 1; + + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + int neighNumber = 0; + float neighAverage = 0; + for (int rx = -radius; rx <= radius; rx++) { + for (int ry = -radius; ry <= radius; ry++) { + if (x+rx < 0 || x+rx >= size) { + continue; + } + if (y+ry < 0 || y+ry >= size) { + continue; + } + neighNumber++; + neighAverage += heightData[(x+rx) + (y+ry) * size]; + } + } + + neighAverage /= neighNumber; + float cp = 1 - np; + heightData[x + y * size] = neighAverage * np + heightData[x + y * size] * cp; + } + } + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/CombinerHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/CombinerHeightMap.java new file mode 100644 index 000000000..283f14974 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/CombinerHeightMap.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +import java.util.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.fine("Created heightmap using Combiner"); + + + return true; + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FaultHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FaultHeightMap.java new file mode 100644 index 000000000..2518eb897 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FaultHeightMap.java @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +import com.jme3.math.FastMath; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Creates an heightmap based on the fault algorithm. Each iteration, a random line + * crossing the map is generated. On one side height values are raised, on the other side + * lowered. + * @author cghislai + */ +public class FaultHeightMap extends AbstractHeightMap { + + private static final Logger logger = Logger.getLogger(FaultHeightMap.class.getName()); + /** + * Values on one side are lowered, on the other side increased, + * creating a step at the fault line + */ + public static final int FAULTTYPE_STEP = 0; + /** + * Values on one side are lowered, then increase lineary while crossing + * the fault line to the other side. The fault line will be a inclined + * plane + */ + public static final int FAULTTYPE_LINEAR = 1; + /** + * Values are lowered on one side, increased on the other, creating a + * cosine curve on the fault line + */ + public static final int FAULTTYPE_COSINE = 2; + /** + * Value are lowered on both side, but increased on the fault line + * creating a smooth ridge on the fault line. + */ + public static final int FAULTTYPE_SINE = 3; + /** + * A linear fault is created + */ + public static final int FAULTSHAPE_LINE = 10; + /** + * A circular fault is created. + */ + public static final int FAULTSHAPE_CIRCLE = 11; + private long seed; // A seed to feed the random + private int iterations; // iterations to perform + private float minFaultHeight; // the height modification applied + private float maxFaultHeight; // the height modification applied + private float minRange; // The range for linear and trigo faults + private float maxRange; // The range for linear and trigo faults + private float minRadius; // radii for circular fault + private float maxRadius; + private int faultType; // The type of fault + private int faultShape; // The type of fault + + /** + * Constructor creates the fault. For faulttype other than STEP, a range can + * be provided. For faultshape circle, min and max radii can be provided. + * Don't forget to reload the map if you have set parameters after the constructor + * call. + * @param size The size of the heightmap + * @param iterations Iterations to perform + * @param faultType Type of fault + * @param faultShape Shape of the fault -line or circle + * @param minFaultHeight Height modified on each side + * @param maxFaultHeight Height modified on each side + * @param seed A seed to feed the Random generator + * @see setFaultRange, setMinRadius, setMaxRadius + */ + public FaultHeightMap(int size, int iterations, int faultType, int faultShape, float minFaultHeight, float maxFaultHeight, long seed) throws Exception { + if (size < 0 || iterations < 0) { + throw new Exception("Size and iterations must be greater than 0!"); + } + this.size = size; + this.iterations = iterations; + this.faultType = faultType; + this.faultShape = faultShape; + this.minFaultHeight = minFaultHeight; + this.maxFaultHeight = maxFaultHeight; + this.seed = seed; + this.minRange = minFaultHeight; + this.maxRange = maxFaultHeight; + this.minRadius = size / 10; + this.maxRadius = size / 4; + load(); + } + + /** + * Create an heightmap with linear step faults. + * @param size size of heightmap + * @param iterations number of iterations + * @param minFaultHeight Height modified on each side + * @param maxFaultHeight Height modified on each side + */ + public FaultHeightMap(int size, int iterations, float minFaultHeight, float maxFaultHeight) throws Exception { + this(size, iterations, FAULTTYPE_STEP, FAULTSHAPE_LINE, minFaultHeight, maxFaultHeight, new Random().nextLong()); + } + + @Override + public boolean load() { + // clean up data if needed. + if (null != heightData) { + unloadHeightMap(); + } + heightData = new float[size * size]; + float[][] tempBuffer = new float[size][size]; + Random random = new Random(seed); + + for (int i = 0; i < iterations; i++) { + addFault(tempBuffer, random); + } + + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + setHeightAtPoint(tempBuffer[i][j], i, j); + } + } + + normalizeTerrain(NORMALIZE_RANGE); + + logger.log(Level.FINE, "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/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FluidSimHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FluidSimHeightMap.java new file mode 100644 index 000000000..d5aff587d --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FluidSimHeightMap.java @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +import java.util.Random; +import java.util.logging.Logger; + +/** + * 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.fine("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/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMap.java new file mode 100644 index 000000000..65731b162 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMap.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +/** + * + * @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/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMapGrid.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMapGrid.java new file mode 100644 index 000000000..ca129236b --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMapGrid.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +import com.jme3.math.Vector3f; + +/** + * + * @author Anthyon + */ +@Deprecated +/** + * @Deprecated in favor of TerrainGridTileLoader + */ +public interface HeightMapGrid { + + public HeightMap getHeightMapAt(Vector3f location); + + public void setSize(int size); + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HillHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HillHeightMap.java new file mode 100644 index 000000000..5a284d857 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HillHeightMap.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +import java.util.Random; +import java.util.logging.Logger; + +/** + * 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 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.fine("Contructing hill heightmap using seed: " + seed); + this.size = size; + this.seed = seed; + this.iterations = iterations; + this.minRadius = minRadius; + this.maxRadius = maxRadius; + + load(); + } + + /** + * Constructor sets the attributes of the hill system and generates the + * height map by using a random seed. + * + * @param size + * size the size of the terrain to be generated + * @param iterations + * the number of hills to grow + * @param minRadius + * the minimum radius of a hill + * @param maxRadius + * the maximum radius of a hill + * @throws Exception + * @throws JmeException + * if size of the terrain is not greater that zero, or number of + * iterations is not greater that zero + */ + public HillHeightMap(int size, int iterations, float minRadius, + float maxRadius) throws Exception { + this(size, iterations, minRadius, maxRadius, new Random().nextLong()); + } + + /* + * Generates a heightmap using the Hill Algorithm and the attributes set by + * the constructor or the setters. + */ + public boolean load() { + // clean up data if needed. + if (null != heightData) { + unloadHeightMap(); + } + heightData = new float[size * size]; + float[][] tempBuffer = new float[size][size]; + Random random = new Random(seed); + + // Add the hills + for (int i = 0; i < iterations; i++) { + addHill(tempBuffer, random); + } + + // transfer temporary buffer to final heightmap + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + setHeightAtPoint((float) tempBuffer[i][j], j, i); + } + } + + normalizeTerrain(NORMALIZE_RANGE); + + logger.fine("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 + * @throws JmeException if the minimum radius is not greater than zero or not + * lower than the maximum radius + */ + public void setMinRadius(float minRadius) throws Exception { + if (minRadius <= 0 || minRadius >= maxRadius) { + throw new Exception("The minimum radius is not greater than 0, " + + "or not lower than the maximum radius"); + } + this.minRadius = minRadius; + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageBasedHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageBasedHeightMap.java new file mode 100644 index 000000000..d4e9b22c8 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageBasedHeightMap.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +import com.jme3.math.ColorRGBA; +import com.jme3.texture.Image; +import com.jme3.texture.image.ImageRaster; + +/** + * 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 implements ImageHeightmap { + + + protected Image colorImage; + private float backwardsCompScale = 255f; + + + public void setImage(Image image) { + this.colorImage = image; + } + + /** + * Creates a HeightMap from an Image. The image will be converted to + * grayscale, and the grayscale values will be used to generate the height + * map. White is highest point while black is lowest point. + * + * Currently, the Image used must be square (width == height), but future + * work could rescale the image. + * + * @param colorImage + * Image to map to the height map. + */ + public ImageBasedHeightMap(Image colorImage) { + this.colorImage = colorImage; + } + + public ImageBasedHeightMap(Image colorImage, float heightScale) { + this.colorImage = colorImage; + this.heightScale = heightScale; + } + + /** + * Loads the image data from top left to bottom right + */ + public boolean load() { + return load(false, false); + } + + /** + * Get the grayscale value, or override in your own sub-classes + */ + protected float calculateHeight(float red, float green, float blue) { + return (float) (0.299 * red + 0.587 * green + 0.114 * blue); + } + + protected float calculateHeight(ColorRGBA color) { + return (float) (0.299 * color.r + 0.587 * color.g + 0.114 * color.b); + } + + protected ImageRaster getImageRaster() { + return ImageRaster.create(colorImage); + } + + public boolean load(boolean flipX, boolean flipY) { + + int imageWidth = colorImage.getWidth(); + int imageHeight = colorImage.getHeight(); + + if (imageWidth != imageHeight) + throw new RuntimeException("imageWidth: " + imageWidth + + " != imageHeight: " + imageHeight); + + size = imageWidth; + ImageRaster raster = getImageRaster(); + + heightData = new float[(imageWidth * imageHeight)]; + + ColorRGBA colorStore = new ColorRGBA(); + + int index = 0; + if (flipY) { + for (int h = 0; h < imageHeight; ++h) { + if (flipX) { + for (int w = imageWidth - 1; w >= 0; --w) { + //int baseIndex = (h * imageWidth)+ w; + //heightData[index++] = getHeightAtPostion(raster, baseIndex, colorStore)*heightScale; + heightData[index++] = calculateHeight(raster.getPixel(w, h, colorStore))*heightScale*backwardsCompScale; + } + } else { + for (int w = 0; w < imageWidth; ++w) { + //int baseIndex = (h * imageWidth)+ w; + //heightData[index++] = getHeightAtPostion(raster, baseIndex, colorStore)*heightScale; + heightData[index++] = calculateHeight(raster.getPixel(w, h, colorStore))*heightScale*backwardsCompScale; + } + } + } + } else { + for (int h = imageHeight - 1; h >= 0; --h) { + if (flipX) { + for (int w = imageWidth - 1; w >= 0; --w) { + //int baseIndex = (h * imageWidth)+ w; + //heightData[index++] = getHeightAtPostion(raster, baseIndex, colorStore)*heightScale; + heightData[index++] = calculateHeight(raster.getPixel(w, h, colorStore))*heightScale*backwardsCompScale; + } + } else { + for (int w = 0; w < imageWidth; ++w) { + //int baseIndex = (h * imageWidth)+ w; + //heightData[index++] = getHeightAtPostion(raster, baseIndex, colorStore)*heightScale; + heightData[index++] = calculateHeight(raster.getPixel(w, h, colorStore))*heightScale*backwardsCompScale; + } + } + } + } + + return true; + } + + /*protected float getHeightAtPostion(ImageRaster image, int position, ColorRGBA store) { + switch (image.getFormat()){ + case RGBA8: + buf.position( position * 4 ); + store.set(byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get())); + return calculateHeight(store.r, store.g, store.b); + case ABGR8: + buf.position( position * 4 ); + float a = byte2float(buf.get()); + float b = byte2float(buf.get()); + float g = byte2float(buf.get()); + float r = byte2float(buf.get()); + store.set(r,g,b,a); + return calculateHeight(store.r, store.g, store.b); + case RGB8: + buf.position( position * 3 ); + store.set(byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()), 1); + return calculateHeight(store.r, store.g, store.b); + case Luminance8: + buf.position( position ); + return byte2float(buf.get())*255*heightScale; + case Luminance16: + ShortBuffer sbuf = buf.asShortBuffer(); + sbuf.position( position ); + return (sbuf.get() & 0xFFFF) / 65535f * 255f * heightScale; + default: + throw new UnsupportedOperationException("Image format: "+image.getFormat()); + } + } + + private float byte2float(byte b){ + return ((float)(b & 0xFF)) / 255f; + }*/ +} \ No newline at end of file diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageBasedHeightMapGrid.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageBasedHeightMapGrid.java new file mode 100644 index 000000000..f0d841c22 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageBasedHeightMapGrid.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.asset.TextureKey; +import com.jme3.math.Vector3f; +import com.jme3.texture.Texture; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Loads Terrain grid tiles with image heightmaps. + * By default it expects a 16-bit grayscale image as the heightmap, but + * you can also call setImageType(BufferedImage.TYPE_) to set it to be a different + * image type. If you do this, you must also set a custom ImageHeightmap that will + * understand and be able to parse the image. By default if you pass in an image of type + * BufferedImage.TYPE_3BYTE_BGR, it will use the ImageBasedHeightMap for you. + * + * @author Anthyon, Brent Owens + */ +@Deprecated +/** + * @Deprecated in favor of ImageTileLoader + */ +public class ImageBasedHeightMapGrid implements HeightMapGrid { + + private static final Logger logger = Logger.getLogger(ImageBasedHeightMapGrid.class.getName()); + private final AssetManager assetManager; + private final Namer namer; + private int size; + + + public ImageBasedHeightMapGrid(final String textureBase, final String textureExt, AssetManager assetManager) { + this(assetManager, new Namer() { + + public String getName(int x, int y) { + return textureBase + "_" + x + "_" + y + "." + textureExt; + } + }); + } + + public ImageBasedHeightMapGrid(AssetManager assetManager, Namer namer) { + this.assetManager = assetManager; + this.namer = namer; + } + + public HeightMap getHeightMapAt(Vector3f location) { + // HEIGHTMAP image (for the terrain heightmap) + int x = (int) location.x; + int z = (int) location.z; + + AbstractHeightMap heightmap = null; + //BufferedImage im = null; + + try { + String name = namer.getName(x, z); + logger.log(Level.FINE, "Loading heightmap from file: {0}", name); + final Texture texture = assetManager.loadTexture(new TextureKey(name)); + + // CREATE HEIGHTMAP + heightmap = new ImageBasedHeightMap(texture.getImage()); + + heightmap.setHeightScale(1); + heightmap.load(); + + } catch (AssetNotFoundException e) { + logger.log(Level.SEVERE, "Asset Not found! ", e); + } + return heightmap; + } + + public void setSize(int size) { + this.size = size - 1; + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageHeightmap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageHeightmap.java new file mode 100644 index 000000000..a61000e84 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageHeightmap.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +/** + * A heightmap that is built off an image. + * If you want to be able to supply different Image types to + * ImageBaseHeightMapGrid, you need to implement this interface, + * and have that class extend Abstract heightmap. + * + * @author bowens + * @deprecated + */ +public interface ImageHeightmap { + + /** + * Set the image to use for this heightmap + */ + //public void setImage(Image image); + + /** + * The BufferedImage.TYPE_ that is supported + * by this ImageHeightmap + */ + //public int getSupportedImageType(); +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/MidpointDisplacementHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/MidpointDisplacementHeightMap.java new file mode 100644 index 000000000..9998d64a2 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/MidpointDisplacementHeightMap.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +import com.jme3.math.FastMath; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; +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.FINE, "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/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/Namer.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/Namer.java new file mode 100644 index 000000000..cfd9d6755 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/Namer.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +/** + * + * @author Anthyon + */ +public interface Namer { + + /** + * Gets a name for a heightmap tile given it's cell id + * @param x + * @param y + * @return + */ + public String getName(int x, int y); + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java new file mode 100644 index 000000000..c684aee20 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ParticleDepositionHeightMap.java @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +import java.util.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.fine("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/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/RawHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/RawHeightMap.java new file mode 100644 index 000000000..6cba90654 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/RawHeightMap.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.terrain.heightmap; + +import com.jme3.math.FastMath; +import com.jme3.util.LittleEndien; +import java.io.*; +import java.net.URL; +import java.util.logging.Logger; + +/** + * 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/jme3-terrain/src/main/java/com/jme3/terrain/noise/Basis.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/Basis.java new file mode 100644 index 000000000..9d640cd98 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/Basis.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise; + +import com.jme3.terrain.noise.basis.ImprovedNoise; +import com.jme3.terrain.noise.modulator.Modulator; +import java.nio.FloatBuffer; + +/** + * Interface for - basically 3D - noise generation algorithms, based on the + * book: Texturing & Modeling - A Procedural Approach + * + * The main concept is to look at noise as a basis for generating fractals. + * Basis can be anything, like a simple: + * + * + * float value(float x, float y, float z) { + * return 0; // a flat noise with 0 value everywhere + * } + * + * + * or a more complex perlin noise ({@link ImprovedNoise} + * + * Fractals use these functions to generate a more complex result based on some + * frequency, roughness, etc values. + * + * Fractals themselves are implementing the Basis interface as well, opening + * an infinite range of results. + * + * @author Anthyon + * + * @since 2011 + * + */ +public interface Basis { + + public void init(); + + public Basis setScale(float scale); + + public float getScale(); + + public Basis addModulator(Modulator modulator); + + public float value(float x, float y, float z); + + public FloatBuffer getBuffer(float sx, float sy, float base, int size); + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/Color.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/Color.java new file mode 100644 index 000000000..719c0ffee --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/Color.java @@ -0,0 +1,134 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise; + +/** + * Helper class for working with colors and gradients + * + * @author Anthyon + * + */ +public class Color { + + private final float[] rgba = new float[4]; + + public Color() {} + + public Color(final int r, final int g, final int b) { + this(r, g, b, 255); + } + + public Color(final int r, final int g, final int b, final int a) { + this.rgba[0] = (r & 255) / 256f; + this.rgba[1] = (g & 255) / 256f; + this.rgba[2] = (b & 255) / 256f; + this.rgba[3] = (a & 255) / 256f; + } + + public Color(final float r, final float g, final float b) { + this(r, g, b, 1); + } + + public Color(final float r, final float g, final float b, final float a) { + this.rgba[0] = ShaderUtils.clamp(r, 0, 1); + this.rgba[1] = ShaderUtils.clamp(g, 0, 1); + this.rgba[2] = ShaderUtils.clamp(b, 0, 1); + this.rgba[3] = ShaderUtils.clamp(a, 0, 1); + } + + public Color(final int h, final float s, final float b) { + this(h, s, b, 1); + } + + public Color(final int h, final float s, final float b, final float a) { + this.rgba[3] = a; + if (s == 0) { + // achromatic ( grey ) + this.rgba[0] = b; + this.rgba[1] = b; + this.rgba[2] = b; + return; + } + + float hh = h / 60.0f; + int i = ShaderUtils.floor(hh); + float f = hh - i; + float p = b * (1 - s); + float q = b * (1 - s * f); + float t = b * (1 - s * (1 - f)); + + if (i == 0) { + this.rgba[0] = b; + this.rgba[1] = t; + this.rgba[2] = p; + } else if (i == 1) { + this.rgba[0] = q; + this.rgba[1] = b; + this.rgba[2] = p; + } else if (i == 2) { + this.rgba[0] = p; + this.rgba[1] = b; + this.rgba[2] = t; + } else if (i == 3) { + this.rgba[0] = p; + this.rgba[1] = q; + this.rgba[2] = b; + } else if (i == 4) { + this.rgba[0] = t; + this.rgba[1] = p; + this.rgba[2] = b; + } else { + this.rgba[0] = b; + this.rgba[1] = p; + this.rgba[2] = q; + } + } + + public int toInteger() { + return 0x00000000 | (int) (this.rgba[3] * 256) << 24 | (int) (this.rgba[0] * 256) << 16 | (int) (this.rgba[1] * 256) << 8 + | (int) (this.rgba[2] * 256); + } + + public String toWeb() { + return Integer.toHexString(this.toInteger()); + } + + public Color toGrayscale() { + float v = (this.rgba[0] + this.rgba[1] + this.rgba[2]) / 3f; + return new Color(v, v, v, this.rgba[3]); + } + + public Color toSepia() { + float r = ShaderUtils.clamp(this.rgba[0] * 0.393f + this.rgba[1] * 0.769f + this.rgba[2] * 0.189f, 0, 1); + float g = ShaderUtils.clamp(this.rgba[0] * 0.349f + this.rgba[1] * 0.686f + this.rgba[2] * 0.168f, 0, 1); + float b = ShaderUtils.clamp(this.rgba[0] * 0.272f + this.rgba[1] * 0.534f + this.rgba[2] * 0.131f, 0, 1); + return new Color(r, g, b, this.rgba[3]); + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/Filter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/Filter.java new file mode 100644 index 000000000..1dca8bfbf --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/Filter.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise; + +import java.nio.FloatBuffer; + +public interface Filter { + public Filter addPreFilter(Filter filter); + + public Filter addPostFilter(Filter filter); + + public FloatBuffer doFilter(float sx, float sy, float base, FloatBuffer data, int size); + + public int getMargin(int size, int margin); + + public boolean isEnabled(); +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/ShaderUtils.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/ShaderUtils.java new file mode 100644 index 000000000..d1c2f6e84 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/ShaderUtils.java @@ -0,0 +1,288 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferInt; +import java.awt.image.WritableRaster; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Helper class containing useful functions explained in the book: + * Texturing & Modeling - A Procedural Approach + * + * @author Anthyon + * + */ +public class ShaderUtils { + + public static final float[] i2c(final int color) { + return new float[] { (color & 0x00ff0000) / 256f, (color & 0x0000ff00) / 256f, (color & 0x000000ff) / 256f, + (color & 0xff000000) / 256f }; + } + + public static final int c2i(final float[] color) { + return (color.length == 4 ? (int) (color[3] * 256) : 0xff000000) | ((int) (color[0] * 256) << 16) | ((int) (color[1] * 256) << 8) + | (int) (color[2] * 256); + } + + public static final float mix(final float a, final float b, final float f) { + return (1 - f) * a + f * b; + } + + public static final Color mix(final Color a, final Color b, final float f) { + return new Color((int) ShaderUtils.clamp(ShaderUtils.mix(a.getRed(), b.getRed(), f), 0, 255), (int) ShaderUtils.clamp( + ShaderUtils.mix(a.getGreen(), b.getGreen(), f), 0, 255), (int) ShaderUtils.clamp( + ShaderUtils.mix(a.getBlue(), b.getBlue(), f), 0, 255)); + } + + public static final int mix(final int a, final int b, final float f) { + return (int) ((1 - f) * a + f * b); + } + + public static final float[] mix(final float[] c1, final float[] c2, final float f) { + return new float[] { ShaderUtils.mix(c1[0], c2[0], f), ShaderUtils.mix(c1[1], c2[1], f), ShaderUtils.mix(c1[2], c2[2], f) }; + } + + public static final float step(final float a, final float x) { + return x < a ? 0 : 1; + } + + public static final float boxstep(final float a, final float b, final float x) { + return ShaderUtils.clamp((x - a) / (b - a), 0, 1); + } + + public static final float pulse(final float a, final float b, final float x) { + return ShaderUtils.step(a, x) - ShaderUtils.step(b, x); + } + + public static final float clamp(final float x, final float a, final float b) { + return x < a ? a : x > b ? b : x; + } + + public static final float min(final float a, final float b) { + return a < b ? a : b; + } + + public static final float max(final float a, final float b) { + return a > b ? a : b; + } + + public static final float abs(final float x) { + return x < 0 ? -x : x; + } + + public static final float smoothstep(final float a, final float b, final float x) { + if (x < a) { + return 0; + } else if (x > b) { + return 1; + } + float xx = (x - a) / (b - a); + return xx * xx * (3 - 2 * xx); + } + + public static final float mod(final float a, final float b) { + int n = (int) (a / b); + float aa = a - n * b; + if (aa < 0) { + aa += b; + } + return aa; + } + + public static final int floor(final float x) { + return x > 0 ? (int) x : (int) x - 1; + } + + public static final float ceil(final float x) { + return (int) x + (x > 0 && x != (int) x ? 1 : 0); + } + + public static final float spline(float x, final float[] knot) { + float CR00 = -0.5f; + float CR01 = 1.5f; + float CR02 = -1.5f; + float CR03 = 0.5f; + float CR10 = 1.0f; + float CR11 = -2.5f; + float CR12 = 2.0f; + float CR13 = -0.5f; + float CR20 = -0.5f; + float CR21 = 0.0f; + float CR22 = 0.5f; + float CR23 = 0.0f; + float CR30 = 0.0f; + float CR31 = 1.0f; + float CR32 = 0.0f; + float CR33 = 0.0f; + + int span; + int nspans = knot.length - 3; + float c0, c1, c2, c3; /* coefficients of the cubic. */ + if (nspans < 1) {/* illegal */ + throw new RuntimeException("Spline has too few knots."); + } + /* Find the appropriate 4-point span of the spline. */ + x = ShaderUtils.clamp(x, 0, 1) * nspans; + span = (int) x; + if (span >= knot.length - 3) { + span = knot.length - 3; + } + x -= span; + /* Evaluate the span cubic at x using Horner’s rule. */ + c3 = CR00 * knot[span + 0] + CR01 * knot[span + 1] + CR02 * knot[span + 2] + CR03 * knot[span + 3]; + c2 = CR10 * knot[span + 0] + CR11 * knot[span + 1] + CR12 * knot[span + 2] + CR13 * knot[span + 3]; + c1 = CR20 * knot[span + 0] + CR21 * knot[span + 1] + CR22 * knot[span + 2] + CR23 * knot[span + 3]; + c0 = CR30 * knot[span + 0] + CR31 * knot[span + 1] + CR32 * knot[span + 2] + CR33 * knot[span + 3]; + return ((c3 * x + c2) * x + c1) * x + c0; + } + + public static final float[] spline(final float x, final float[][] knots) { + float[] retval = new float[knots.length]; + for (int i = 0; i < knots.length; i++) { + retval[i] = ShaderUtils.spline(x, knots[i]); + } + return retval; + } + + public static final float gammaCorrection(final float gamma, final float x) { + return (float) Math.pow(x, 1 / gamma); + } + + public static final float bias(final float b, final float x) { + return (float) Math.pow(x, Math.log(b) / Math.log(0.5)); + } + + public static final float gain(final float g, final float x) { + return x < 0.5 ? ShaderUtils.bias(1 - g, 2 * x) / 2 : 1 - ShaderUtils.bias(1 - g, 2 - 2 * x) / 2; + } + + public static final float sinValue(final float s, final float minFreq, final float maxFreq, final float swidth) { + float value = 0; + float cutoff = ShaderUtils.clamp(0.5f / swidth, 0, maxFreq); + float f; + for (f = minFreq; f < 0.5 * cutoff; f *= 2) { + value += Math.sin(2 * Math.PI * f * s) / f; + } + float fade = ShaderUtils.clamp(2 * (cutoff - f) / cutoff, 0, 1); + value += fade * Math.sin(2 * Math.PI * f * s) / f; + return value; + } + + public static final float length(final float x, final float y, final float z) { + return (float) Math.sqrt(x * x + y * y + z * z); + } + + public static final float[] rotate(final float[] v, final float[][] m) { + float x = v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2]; + float y = v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2]; + float z = v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2]; + return new float[] { x, y, z }; + } + + public static final float[][] calcRotationMatrix(final float ax, final float ay, final float az) { + float[][] retval = new float[3][3]; + float cax = (float) Math.cos(ax); + float sax = (float) Math.sin(ax); + float cay = (float) Math.cos(ay); + float say = (float) Math.sin(ay); + float caz = (float) Math.cos(az); + float saz = (float) Math.sin(az); + + retval[0][0] = cay * caz; + retval[0][1] = -cay * saz; + retval[0][2] = say; + retval[1][0] = sax * say * caz + cax * saz; + retval[1][1] = -sax * say * saz + cax * caz; + retval[1][2] = -sax * cay; + retval[2][0] = -cax * say * caz + sax * saz; + retval[2][1] = cax * say * saz + sax * caz; + retval[2][2] = cax * cay; + + return retval; + } + + public static final float[] normalize(final float[] v) { + float l = ShaderUtils.length(v); + float[] r = new float[v.length]; + int i = 0; + for (float vv : v) { + r[i++] = vv / l; + } + return r; + } + + public static final float length(final float[] v) { + float s = 0; + for (float vv : v) { + s += vv * vv; + } + return (float) Math.sqrt(s); + } + + public static final ByteBuffer getImageDataFromImage(BufferedImage bufferedImage) { + WritableRaster wr; + DataBuffer db; + + BufferedImage bi = new BufferedImage(128, 64, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bi.createGraphics(); + g.drawImage(bufferedImage, null, null); + bufferedImage = bi; + wr = bi.getRaster(); + db = wr.getDataBuffer(); + + DataBufferInt dbi = (DataBufferInt) db; + int[] data = dbi.getData(); + + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.asIntBuffer().put(data); + byteBuffer.flip(); + + return byteBuffer; + } + + public static float frac(float f) { + return f - ShaderUtils.floor(f); + } + + public static float[] floor(float[] fs) { + float[] retval = new float[fs.length]; + for (int i = 0; i < fs.length; i++) { + retval[i] = ShaderUtils.floor(fs[i]); + } + return retval; + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/FilteredBasis.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/FilteredBasis.java new file mode 100644 index 000000000..8fec02ef5 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/FilteredBasis.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.basis; + +import com.jme3.terrain.noise.Basis; +import com.jme3.terrain.noise.filter.AbstractFilter; +import com.jme3.terrain.noise.modulator.Modulator; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +public class FilteredBasis extends AbstractFilter implements Basis { + + private Basis basis; + private List modulators = new ArrayList(); + private float scale; + + public FilteredBasis() {} + + public FilteredBasis(Basis basis) { + this.basis = basis; + } + + public Basis getBasis() { + return this.basis; + } + + public void setBasis(Basis basis) { + this.basis = basis; + } + + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int size) { + return data; + } + + @Override + public void init() { + this.basis.init(); + } + + @Override + public Basis setScale(float scale) { + this.scale = scale; + return this; + } + + @Override + public float getScale() { + return this.scale; + } + + @Override + public Basis addModulator(Modulator modulator) { + this.modulators.add(modulator); + return this; + } + + @Override + public float value(float x, float y, float z) { + throw new UnsupportedOperationException( + "Method value cannot be called on FilteredBasis and its descendants. Use getBuffer instead!"); + } + + @Override + public FloatBuffer getBuffer(float sx, float sy, float base, int size) { + int margin = this.getMargin(size, 0); + int workSize = size + 2 * margin; + FloatBuffer retval = this.basis.getBuffer(sx - margin, sy - margin, base, workSize); + return this.clip(this.doFilter(sx, sy, base, retval, workSize), workSize, size, margin); + } + + public FloatBuffer clip(FloatBuffer buf, int origSize, int newSize, int offset) { + FloatBuffer result = FloatBuffer.allocate(newSize * newSize); + + float[] orig = buf.array(); + for (int i = offset; i < offset + newSize; i++) { + result.put(orig, i * origSize + offset, newSize); + } + + return result; + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/ImprovedNoise.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/ImprovedNoise.java new file mode 100644 index 000000000..9233ef87a --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/ImprovedNoise.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.basis; + +import com.jme3.terrain.noise.ShaderUtils; + +// JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN. +/** + * Perlin's default implementation of Improved Perlin Noise + * designed to work with Noise base + */ +public final class ImprovedNoise extends Noise { + + @Override + public void init() { + + } + + static public float noise(float x, float y, float z) { + int X = ShaderUtils.floor(x), // FIND UNIT CUBE THAT + Y = ShaderUtils.floor(y), // CONTAINS POINT. + Z = ShaderUtils.floor(z); + x -= X; // FIND RELATIVE X,Y,Z + y -= Y; // OF POINT IN CUBE. + z -= Z; + X = X & 255; + Y = Y & 255; + Z = Z & 255; + float u = ImprovedNoise.fade(x), // COMPUTE FADE CURVES + v = ImprovedNoise.fade(y), // FOR EACH OF X,Y,Z. + w = ImprovedNoise.fade(z); + int A = ImprovedNoise.p[X] + Y; + int AA = ImprovedNoise.p[A] + Z; + int AB = ImprovedNoise.p[A + 1] + Z; + int B = ImprovedNoise.p[X + 1] + Y; + int BA = ImprovedNoise.p[B] + Z; + int BB = ImprovedNoise.p[B + 1] + Z; + + return ImprovedNoise.lerp( + w, + ImprovedNoise.lerp( + v, + ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AA], x, y, z), + ImprovedNoise.grad3(ImprovedNoise.p[BA], x - 1, y, z)), // BLENDED + ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AB], x, y - 1, z), // RESULTS + ImprovedNoise.grad3(ImprovedNoise.p[BB], x - 1, y - 1, z))),// FROM + ImprovedNoise.lerp(v, + ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AA + 1], x, y, z - 1), // CORNERS + ImprovedNoise.grad3(ImprovedNoise.p[BA + 1], x - 1, y, z - 1)), // OF + ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AB + 1], x, y - 1, z - 1), + ImprovedNoise.grad3(ImprovedNoise.p[BB + 1], x - 1, y - 1, z - 1)))); + } + + static final float fade(final float t) { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + static final float lerp(final float t, final float a, final float b) { + return a + t * (b - a); + } + + static float grad(final int hash, final float x, final float y, final float z) { + int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE + float u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS. + v = h < 4 ? y : h == 12 || h == 14 ? x : z; + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } + + static final float grad3(final int hash, final float x, final float y, final float z) { + int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE + return x * ImprovedNoise.GRAD3[h][0] + y * ImprovedNoise.GRAD3[h][1] + z * ImprovedNoise.GRAD3[h][2]; + } + + static final int p[] = new int[512], permutation[] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, + 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, + 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, + 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, + 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, + 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, + 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, + 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, + 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, + 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 }; + + private static float[][] GRAD3 = new float[][] { { 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 }, { 1, 0, 1 }, { -1, 0, 1 }, + { 1, 0, -1 }, { -1, 0, -1 }, { 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 }, { 1, 0, -1 }, { -1, 0, -1 }, { 0, -1, 1 }, + { 0, 1, 1 } }; + + static { + for (int i = 0; i < 256; i++) { + ImprovedNoise.p[256 + i] = ImprovedNoise.p[i] = ImprovedNoise.permutation[i]; + } + } + + @Override + public float value(final float x, final float y, final float z) { + return ImprovedNoise.noise(this.scale * x, this.scale * y, this.scale * z); + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/Noise.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/Noise.java new file mode 100644 index 000000000..0ceaa24ac --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/Noise.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.basis; + +import com.jme3.terrain.noise.Basis; +import com.jme3.terrain.noise.modulator.Modulator; +import com.jme3.terrain.noise.modulator.NoiseModulator; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * Utility base class for Noise implementations + * + * @author Anthyon + * + */ +public abstract class Noise implements Basis { + + protected List modulators = new ArrayList(); + + protected float scale = 1.0f; + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + @Override + public FloatBuffer getBuffer(float sx, float sy, float base, int size) { + FloatBuffer retval = FloatBuffer.allocate(size * size); + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + retval.put(this.modulate((sx + x) / size, (sy + y) / size, base)); + } + } + return retval; + } + + public float modulate(float x, float y, float z) { + float retval = this.value(x, y, z); + for (Modulator m : this.modulators) { + if (m instanceof NoiseModulator) { + retval = m.value(retval); + } + } + return retval; + } + + @Override + public Basis addModulator(Modulator modulator) { + this.modulators.add(modulator); + return this; + } + + @Override + public Basis setScale(float scale) { + this.scale = scale; + return this; + } + + @Override + public float getScale() { + return this.scale; + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/NoiseAggregator.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/NoiseAggregator.java new file mode 100644 index 000000000..8a547a3b9 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/NoiseAggregator.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.basis; + +import com.jme3.terrain.noise.Basis; + +/** + * A simple aggregator basis. Takes two basis functions and a rate and return + * some mixed values + * + * @author Anthyon + * + */ +public class NoiseAggregator extends Noise { + + private final float rate; + private final Basis a; + private final Basis b; + + public NoiseAggregator(final Basis a, final Basis b, final float rate) { + this.a = a; + this.b = b; + this.rate = rate; + } + + @Override + public void init() { + this.a.init(); + this.b.init(); + } + + @Override + public float value(final float x, final float y, final float z) { + return this.a.value(x, y, z) * (1 - this.rate) + this.rate * this.b.value(x, y, z); + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/AbstractFilter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/AbstractFilter.java new file mode 100644 index 000000000..02715d5b0 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/AbstractFilter.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.filter; + +import com.jme3.terrain.noise.Filter; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractFilter implements Filter { + + protected List preFilters = new ArrayList(); + protected List postFilters = new ArrayList(); + + private boolean enabled = true; + + @Override + public Filter addPreFilter(Filter filter) { + this.preFilters.add(filter); + return this; + } + + @Override + public Filter addPostFilter(Filter filter) { + this.postFilters.add(filter); + return this; + } + + @Override + public FloatBuffer doFilter(float sx, float sy, float base, FloatBuffer data, int size) { + if (!this.isEnabled()) { + return data; + } + FloatBuffer retval = data; + for (Filter f : this.preFilters) { + retval = f.doFilter(sx, sy, base, retval, size); + } + retval = this.filter(sx, sy, base, retval, size); + for (Filter f : this.postFilters) { + retval = f.doFilter(sx, sy, base, retval, size); + } + return retval; + } + + public abstract FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size); + + @Override + public int getMargin(int size, int margin) { + // TODO sums up all the margins from filters... maybe there's a more + // efficient algorithm + if (!this.isEnabled()) { + return margin; + } + for (Filter f : this.preFilters) { + margin = f.getMargin(size, margin); + } + for (Filter f : this.postFilters) { + margin = f.getMargin(size, margin); + } + return margin; + } + + @Override + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/HydraulicErodeFilter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/HydraulicErodeFilter.java new file mode 100644 index 000000000..db0580f63 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/HydraulicErodeFilter.java @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.filter; + +import com.jme3.terrain.noise.Basis; +import java.nio.FloatBuffer; + +public class HydraulicErodeFilter extends AbstractFilter { + + private Basis waterMap; + private Basis sedimentMap; + private float Kr; + private float Ks; + private float Ke; + private float Kc; + private float T; + + public void setKc(float kc) { + this.Kc = kc; + } + + public void setKe(float ke) { + this.Ke = ke; + } + + public void setKr(float kr) { + this.Kr = kr; + } + + public void setKs(float ks) { + this.Ks = ks; + } + + public void setSedimentMap(Basis sedimentMap) { + this.sedimentMap = sedimentMap; + } + + public void setT(float t) { + this.T = t; + } + + public void setWaterMap(Basis waterMap) { + this.waterMap = waterMap; + } + + @Override + public int getMargin(int size, int margin) { + return super.getMargin(size, margin) + 1; + } + + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int workSize) { + float[] ga = buffer.array(); + // float[] wa = this.waterMap.getBuffer(sx, sy, base, workSize).array(); + // float[] sa = this.sedimentMap.getBuffer(sx, sy, base, + // workSize).array(); + float[] wt = new float[workSize * workSize]; + float[] st = new float[workSize * workSize]; + + int[] idxrel = { -workSize - 1, -workSize + 1, workSize - 1, workSize + 1 }; + + // step 1. water arrives and step 2. captures material + for (int y = 0; y < workSize; y++) { + for (int x = 0; x < workSize; x++) { + int idx = y * workSize + x; + float wtemp = this.Kr; // * wa[idx]; + float stemp = this.Ks; // * sa[idx]; + if (wtemp > 0) { + wt[idx] += wtemp; + if (stemp > 0) { + ga[idx] -= stemp * wt[idx]; + st[idx] += stemp * wt[idx]; + } + } + + // step 3. water is transported to it's neighbours + float a = ga[idx] + wt[idx]; + // float[] aj = new float[idxrel.length]; + float amax = 0; + int amaxidx = -1; + float ac = 0; + float dtotal = 0; + + for (int j = 0; j < idxrel.length; j++) { + if (idx + idxrel[j] > 0 && idx + idxrel[j] < workSize) { + float at = ga[idx + idxrel[j]] + wt[idx + idxrel[j]]; + if (a - at > a - amax) { + dtotal += at; + amax = at; + amaxidx = j; + ac++; + } + } + } + + float aa = (dtotal + a) / (ac + 1); + // for (int j = 0; j < idxrel.length; j++) { + // if (idx + idxrel[j] > 0 && idx + idxrel[j] < workSize && a - + // aj[j] > 0) { + if (amaxidx > -1) { + float dwj = Math.min(wt[idx], a - aa) * (a - amax) / dtotal; + float dsj = st[idx] * dwj / wt[idx]; + wt[idx] -= dwj; + st[idx] -= dsj; + wt[idx + idxrel[amaxidx]] += dwj; + st[idx + idxrel[amaxidx]] += dsj; + } + // } + + // step 4. water evaporates and deposits material + wt[idx] = wt[idx] * (1 - this.Ke); + if (wt[idx] < this.T) { + wt[idx] = 0; + } + float smax = this.Kc * wt[idx]; + if (st[idx] > smax) { + ga[idx] += st[idx] - smax; + st[idx] -= st[idx] - smax; + } + } + } + + return buffer; + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/IterativeFilter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/IterativeFilter.java new file mode 100644 index 000000000..60bc51249 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/IterativeFilter.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.filter; + +import com.jme3.terrain.noise.Filter; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +public class IterativeFilter extends AbstractFilter { + + private int iterations; + + private List preIterateFilters = new ArrayList(); + private List postIterateFilters = new ArrayList(); + private Filter filter; + + @Override + public int getMargin(int size, int margin) { + if (!this.isEnabled()) { + return margin; + } + for (Filter f : this.preIterateFilters) { + margin = f.getMargin(size, margin); + } + margin = this.filter.getMargin(size, margin); + for (Filter f : this.postIterateFilters) { + margin = f.getMargin(size, margin); + } + return this.iterations * margin + super.getMargin(size, margin); + } + + public void setIterations(int iterations) { + this.iterations = iterations; + } + + public int getIterations() { + return this.iterations; + } + + public IterativeFilter addPostIterateFilter(Filter filter) { + this.postIterateFilters.add(filter); + return this; + } + + public IterativeFilter addPreIterateFilter(Filter filter) { + this.preIterateFilters.add(filter); + return this; + } + + public void setFilter(Filter filter) { + this.filter = filter; + } + + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int size) { + if (!this.isEnabled()) { + return data; + } + FloatBuffer retval = data; + + for (int i = 0; i < this.iterations; i++) { + for (Filter f : this.preIterateFilters) { + retval = f.doFilter(sx, sy, base, retval, size); + } + retval = this.filter.doFilter(sx, sy, base, retval, size); + for (Filter f : this.postIterateFilters) { + retval = f.doFilter(sx, sy, base, retval, size); + } + } + + return retval; + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/OptimizedErode.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/OptimizedErode.java new file mode 100644 index 000000000..fc20da601 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/OptimizedErode.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.filter; + +import java.nio.FloatBuffer; + +public class OptimizedErode extends AbstractFilter { + + private float talus; + private int radius; + + public OptimizedErode setRadius(int radius) { + this.radius = radius; + return this; + } + + public int getRadius() { + return this.radius; + } + + public OptimizedErode setTalus(float talus) { + this.talus = talus; + return this; + } + + public float getTalus() { + return this.talus; + } + + @Override + public int getMargin(int size, int margin) { + return super.getMargin(size, margin) + this.radius; + } + + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size) { + float[] tmp = buffer.array(); + float[] retval = new float[tmp.length]; + + for (int y = this.radius + 1; y < size - this.radius; y++) { + for (int x = this.radius + 1; x < size - this.radius; x++) { + int idx = y * size + x; + float h = tmp[idx]; + + float horizAvg = 0; + int horizCount = 0; + float vertAvg = 0; + int vertCount = 0; + + boolean horizT = false; + boolean vertT = false; + + for (int i = 0; i >= -this.radius; i--) { + int idxV = (y + i) * size + x; + int idxVL = (y + i - 1) * size + x; + int idxH = y * size + x + i; + int idxHL = y * size + x + i - 1; + float hV = tmp[idxV]; + float hH = tmp[idxH]; + + if (Math.abs(h - hV) > this.talus && Math.abs(h - tmp[idxVL]) > this.talus || vertT) { + vertT = true; + } else { + if (Math.abs(h - hV) <= this.talus) { + vertAvg += hV; + vertCount++; + } + } + + if (Math.abs(h - hH) > this.talus && Math.abs(h - tmp[idxHL]) > this.talus || horizT) { + horizT = true; + } else { + if (Math.abs(h - hH) <= this.talus) { + horizAvg += hH; + horizCount++; + } + } + } + + retval[idx] = 0.5f * (vertAvg / (vertCount > 0 ? vertCount : 1) + horizAvg / (horizCount > 0 ? horizCount : 1)); + } + } + return FloatBuffer.wrap(retval); + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/PerturbFilter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/PerturbFilter.java new file mode 100644 index 000000000..782f384aa --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/PerturbFilter.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.filter; + +import com.jme3.terrain.noise.ShaderUtils; +import com.jme3.terrain.noise.fractal.FractalSum; +import java.nio.FloatBuffer; +import java.util.logging.Logger; + +public class PerturbFilter extends AbstractFilter { + + private float magnitude; + + @Override + public int getMargin(int size, int margin) { + margin = super.getMargin(size, margin); + return (int) Math.floor(this.magnitude * (margin + size) + margin); + } + + public void setMagnitude(float magnitude) { + this.magnitude = magnitude; + } + + public float getMagnitude() { + return this.magnitude; + } + + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int workSize) { + float[] arr = data.array(); + int origSize = (int) Math.ceil(workSize / (2 * this.magnitude + 1)); + int offset = (workSize - origSize) / 2; + Logger.getLogger(PerturbFilter.class.getCanonicalName()).info( + "Found origSize : " + origSize + " and offset: " + offset + " for workSize : " + workSize + " and magnitude : " + + this.magnitude); + float[] retval = new float[workSize * workSize]; + float[] perturbx = new FractalSum().setOctaves(8).setScale(5f).getBuffer(sx, sy, base, workSize).array(); + float[] perturby = new FractalSum().setOctaves(8).setScale(5f).getBuffer(sx, sy, base + 1, workSize).array(); + for (int y = 0; y < workSize; y++) { + for (int x = 0; x < workSize; x++) { + // Perturb our coordinates + float noisex = perturbx[y * workSize + x]; + float noisey = perturby[y * workSize + x]; + + int px = (int) (origSize * noisex * this.magnitude); + int py = (int) (origSize * noisey * this.magnitude); + + float c00 = arr[this.wrap(y - py, workSize) * workSize + this.wrap(x - px, workSize)]; + float c01 = arr[this.wrap(y - py, workSize) * workSize + this.wrap(x + px, workSize)]; + float c10 = arr[this.wrap(y + py, workSize) * workSize + this.wrap(x - px, workSize)]; + float c11 = arr[this.wrap(y + py, workSize) * workSize + this.wrap(x + px, workSize)]; + + float c0 = ShaderUtils.mix(c00, c01, noisex); + float c1 = ShaderUtils.mix(c10, c11, noisex); + retval[y * workSize + x] = ShaderUtils.mix(c0, c1, noisey); + } + } + return FloatBuffer.wrap(retval); + } + + private int wrap(int v, int size) { + if (v < 0) { + return v + size - 1; + } else if (v >= size) { + return v - size; + } else { + return v; + } + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/SmoothFilter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/SmoothFilter.java new file mode 100644 index 000000000..1c20c2451 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/SmoothFilter.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.filter; + +import java.nio.FloatBuffer; + +public class SmoothFilter extends AbstractFilter { + + private int radius; + private float effect; + + public void setRadius(int radius) { + this.radius = radius; + } + + public int getRadius() { + return this.radius; + } + + public void setEffect(float effect) { + this.effect = effect; + } + + public float getEffect() { + return this.effect; + } + + @Override + public int getMargin(int size, int margin) { + return super.getMargin(size, margin) + this.radius; + } + + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size) { + float[] data = buffer.array(); + float[] retval = new float[data.length]; + + for (int y = this.radius; y < size - this.radius; y++) { + for (int x = this.radius; x < size - this.radius; x++) { + int idx = y * size + x; + float n = 0; + for (int i = -this.radius; i < this.radius + 1; i++) { + for (int j = -this.radius; j < this.radius + 1; j++) { + n += data[(y + i) * size + x + j]; + } + } + retval[idx] = this.effect * n / (4 * this.radius * (this.radius + 1) + 1) + (1 - this.effect) * data[idx]; + } + } + + return FloatBuffer.wrap(retval); + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/ThermalErodeFilter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/ThermalErodeFilter.java new file mode 100644 index 000000000..2e496799e --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/ThermalErodeFilter.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.filter; + +import java.nio.FloatBuffer; + +public class ThermalErodeFilter extends AbstractFilter { + + private float talus; + private float c; + + public ThermalErodeFilter setC(float c) { + this.c = c; + return this; + } + + public ThermalErodeFilter setTalus(float talus) { + this.talus = talus; + return this; + } + + @Override + public int getMargin(int size, int margin) { + return super.getMargin(size, margin) + 1; + } + + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int workSize) { + float[] ga = buffer.array(); + float[] sa = new float[workSize * workSize]; + + int[] idxrel = { -workSize - 1, -workSize + 1, workSize - 1, workSize + 1 }; + + for (int y = 0; y < workSize; y++) { + for (int x = 0; x < workSize; x++) { + int idx = y * workSize + x; + ga[idx] += sa[idx]; + sa[idx] = 0; + + float[] deltas = new float[idxrel.length]; + float deltaMax = this.talus; + float deltaTotal = 0; + + for (int j = 0; j < idxrel.length; j++) { + if (idx + idxrel[j] > 0 && idx + idxrel[j] < ga.length) { + float dj = ga[idx] - ga[idx + idxrel[j]]; + if (dj > this.talus) { + deltas[j] = dj; + deltaTotal += dj; + if (dj > deltaMax) { + deltaMax = dj; + } + } + } + } + + for (int j = 0; j < idxrel.length; j++) { + if (deltas[j] != 0) { + float d = this.c * (deltaMax - this.talus) * deltas[j] / deltaTotal; + if (d > ga[idx] + sa[idx]) { + d = ga[idx] + sa[idx]; + } + sa[idx] -= d; + sa[idx + idxrel[j]] += d; + } + deltas[j] = 0; + } + } + } + + return buffer; + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/Fractal.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/Fractal.java new file mode 100644 index 000000000..9b5344717 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/Fractal.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.fractal; + +import com.jme3.terrain.noise.Basis; + +/** + * Interface for a general fractal basis. + * + * Takes any number of basis funcions to work with and a few common parameters + * for noise fractals + * + * @author Anthyon + * + */ +public interface Fractal extends Basis { + + public Fractal setOctaves(final float octaves); + + public Fractal setFrequency(final float frequency); + + public Fractal setRoughness(final float roughness); + + public Fractal setAmplitude(final float amplitude); + + public Fractal setLacunarity(final float lacunarity); + + public Fractal addBasis(Basis basis); + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/FractalSum.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/FractalSum.java new file mode 100644 index 000000000..5fe5dcb4a --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/FractalSum.java @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.fractal; + +import com.jme3.terrain.noise.Basis; +import com.jme3.terrain.noise.ShaderUtils; +import com.jme3.terrain.noise.basis.ImprovedNoise; +import com.jme3.terrain.noise.basis.Noise; + +/** + * FractalSum is the simplest form of fractal functions summing up a few octaves + * of the noise value with an ever decreasing (0 < roughness < 1) amplitude + * + * lacunarity = 2.0f is the classical octave distance + * + * Note: though noise basis functions are generally designed to return value + * between -1..1, there sum can easily be made to extend out of this range. To + * handle this is up to the user. + * + * @author Anthyon + * + */ +public class FractalSum extends Noise implements Fractal { + + private Basis basis; + private float lacunarity; + private float amplitude; + private float roughness; + private float frequency; + private float octaves; + private int maxFreq; + + public FractalSum() { + this.basis = new ImprovedNoise(); + this.lacunarity = 2.124367f; + this.amplitude = 1.0f; + this.roughness = 0.6f; + this.frequency = 1f; + this.setOctaves(1); + } + + @Override + public float value(final float x, final float y, final float z) { + float total = 0; + + for (float f = this.frequency, a = this.amplitude; f < this.maxFreq; f *= this.lacunarity, a *= this.roughness) { + total += this.basis.value(this.scale * x * f, this.scale * y * f, this.scale * z * f) * a; + } + + return ShaderUtils.clamp(total, -1, 1); + } + + @Override + public Fractal addBasis(final Basis basis) { + this.basis = basis; + return this; + } + + public float getOctaves() { + return this.octaves; + } + + @Override + public Fractal setOctaves(final float octaves) { + this.octaves = octaves; + this.maxFreq = 1 << (int) octaves; + return this; + } + + public float getFrequency() { + return this.frequency; + } + + @Override + public Fractal setFrequency(final float frequency) { + this.frequency = frequency; + return this; + } + + public float getRoughness() { + return this.roughness; + } + + @Override + public Fractal setRoughness(final float roughness) { + this.roughness = roughness; + return this; + } + + public float getAmplitude() { + return this.amplitude; + } + + @Override + public Fractal setAmplitude(final float amplitude) { + this.amplitude = amplitude; + return this; + } + + public float getLacunarity() { + return this.lacunarity; + } + + @Override + public Fractal setLacunarity(final float lacunarity) { + this.lacunarity = lacunarity; + return this; + } + + @Override + public void init() { + + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/CatRom2.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/CatRom2.java new file mode 100644 index 000000000..ce65cd6f9 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/CatRom2.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.modulator; + +import com.jme3.terrain.noise.ShaderUtils; +import java.util.HashMap; +import java.util.Map; + +public class CatRom2 implements Modulator { + + private int sampleRate = 100; + + private final float[] table; + + private static Map instances = new HashMap(); + + public CatRom2(final int sampleRate) { + this.sampleRate = sampleRate; + this.table = new float[4 * sampleRate + 1]; + for (int i = 0; i < 4 * sampleRate + 1; i++) { + float x = i / (float) sampleRate; + x = (float) Math.sqrt(x); + if (x < 1) { + this.table[i] = 0.5f * (2 + x * x * (-5 + x * 3)); + } else { + this.table[i] = 0.5f * (4 + x * (-8 + x * (5 - x))); + } + } + } + + public static CatRom2 getInstance(final int sampleRate) { + if (!CatRom2.instances.containsKey(sampleRate)) { + CatRom2.instances.put(sampleRate, new CatRom2(sampleRate)); + } + return CatRom2.instances.get(sampleRate); + } + + @Override + public float value(final float... in) { + if (in[0] >= 4) { + return 0; + } + in[0] = in[0] * this.sampleRate + 0.5f; + int i = ShaderUtils.floor(in[0]); + if (i >= 4 * this.sampleRate + 1) { + return 0; + } + return this.table[i]; + } +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/Modulator.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/Modulator.java new file mode 100644 index 000000000..28bfd1c92 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/Modulator.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.modulator; + +public interface Modulator { + + public float value(float... in); + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/NoiseModulator.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/NoiseModulator.java new file mode 100644 index 000000000..38a3bd674 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/NoiseModulator.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2011, Novyon Events + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Anthyon + */ +package com.jme3.terrain.noise.modulator; + +public interface NoiseModulator extends Modulator { + +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.frag new file mode 100644 index 000000000..b0b594c62 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.frag @@ -0,0 +1,76 @@ +uniform vec3 m_region1; +uniform vec3 m_region2; +uniform vec3 m_region3; +uniform vec3 m_region4; + +uniform sampler2D m_region1ColorMap; +uniform sampler2D m_region2ColorMap; +uniform sampler2D m_region3ColorMap; +uniform sampler2D m_region4ColorMap; +uniform sampler2D m_slopeColorMap; + +uniform float m_slopeTileFactor; +uniform float m_terrainSize; + +varying vec3 normal; +varying vec4 position; + +vec4 GenerateTerrainColor() { + float height = position.y; + vec4 p = position / m_terrainSize; + + vec3 blend = abs( normal ); + blend = (blend -0.2) * 0.7; + blend = normalize(max(blend, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blend.x + blend.y + blend.z); + blend /= vec3(b, b, b); + + vec4 terrainColor = vec4(0.0, 0.0, 0.0, 1.0); + + float m_regionMin = 0.0; + float m_regionMax = 0.0; + float m_regionRange = 0.0; + float m_regionWeight = 0.0; + + vec4 slopeCol1 = texture2D(m_slopeColorMap, p.yz * m_slopeTileFactor); + vec4 slopeCol2 = texture2D(m_slopeColorMap, p.xy * m_slopeTileFactor); + + // Terrain m_region 1. + m_regionMin = m_region1.x; + m_regionMax = m_region1.y; + m_regionRange = m_regionMax - m_regionMin; + m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange; + m_regionWeight = max(0.0, m_regionWeight); + terrainColor += m_regionWeight * texture2D(m_region1ColorMap, p.xz * m_region1.z); + + // Terrain m_region 2. + m_regionMin = m_region2.x; + m_regionMax = m_region2.y; + m_regionRange = m_regionMax - m_regionMin; + m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange; + m_regionWeight = max(0.0, m_regionWeight); + terrainColor += m_regionWeight * (texture2D(m_region2ColorMap, p.xz * m_region2.z)); + + // Terrain m_region 3. + m_regionMin = m_region3.x; + m_regionMax = m_region3.y; + m_regionRange = m_regionMax - m_regionMin; + m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange; + m_regionWeight = max(0.0, m_regionWeight); + terrainColor += m_regionWeight * texture2D(m_region3ColorMap, p.xz * m_region3.z); + + // Terrain m_region 4. + m_regionMin = m_region4.x; + m_regionMax = m_region4.y; + m_regionRange = m_regionMax - m_regionMin; + m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange; + m_regionWeight = max(0.0, m_regionWeight); + terrainColor += m_regionWeight * texture2D(m_region4ColorMap, p.xz * m_region4.z); + + return (blend.y * terrainColor + blend.x * slopeCol1 + blend.z * slopeCol2); +} + +void main() { + vec4 color = GenerateTerrainColor(); + gl_FragColor = color; +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md new file mode 100644 index 000000000..b57aaa4ca --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md @@ -0,0 +1,40 @@ +MaterialDef Terrain { + + // Parameters to material: + // regionXColorMap: X = 1..4 the texture that should be appliad to state X + // regionX: a Vector3f containing the following information: + // regionX.x: the start height of the region + // regionX.y: the end height of the region + // regionX.z: the texture scale for the region + // it might not be the most elegant way for storing these 3 values, but it packs the data nicely :) + // slopeColorMap: the texture to be used for cliffs, and steep mountain sites + // slopeTileFactor: the texture scale for slopes + // terrainSize: the total size of the terrain (used for scaling the texture) + MaterialParameters { + Texture2D region1ColorMap + Texture2D region2ColorMap + Texture2D region3ColorMap + Texture2D region4ColorMap + Texture2D slopeColorMap + Float slopeTileFactor + Float terrainSize + Vector3 region1 + Vector3 region2 + Vector3 region3 + Vector3 region4 + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Terrain/HeightBasedTerrain.vert + FragmentShader GLSL100: Common/MatDefs/Terrain/HeightBasedTerrain.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + NormalMatrix + } + } + + Technique { + } +} \ No newline at end of file diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.vert b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.vert new file mode 100644 index 000000000..8260d8f72 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.vert @@ -0,0 +1,22 @@ +uniform float m_tilingFactor; +uniform mat4 g_WorldViewProjectionMatrix; +uniform mat4 g_WorldMatrix; +uniform mat3 g_NormalMatrix; + +uniform float m_terrainSize; + +attribute vec4 inTexCoord; +attribute vec3 inNormal; +attribute vec3 inPosition; + +varying vec3 normal; +varying vec4 position; + +void main() +{ + normal = normalize(inNormal); + position = g_WorldMatrix * vec4(inPosition, 0.0); + gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1); +} + + diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.frag new file mode 100644 index 000000000..7ae56cb4f --- /dev/null +++ b/jme3-terrain/src/main/resources/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/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md new file mode 100644 index 000000000..9702d6594 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md @@ -0,0 +1,32 @@ +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 { + } +} \ No newline at end of file diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.vert b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.vert new file mode 100644 index 000000000..ddb40a9f4 --- /dev/null +++ b/jme3-terrain/src/main/resources/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/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag new file mode 100644 index 000000000..8484b454d --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag @@ -0,0 +1,660 @@ + +uniform float m_Shininess; +uniform vec4 g_LightDirection; + +varying vec4 AmbientSum; +varying vec4 DiffuseSum; +varying vec4 SpecularSum; + +varying vec3 vNormal; +varying vec2 texCoord; +varying vec3 vPosition; +varying vec3 vnPosition; +varying vec3 vViewDir; +varying vec4 vLightDir; +varying vec4 vnLightDir; +varying vec3 lightVec; + + +#ifdef DIFFUSEMAP + uniform sampler2D m_DiffuseMap; +#endif +#ifdef DIFFUSEMAP_1 + uniform sampler2D m_DiffuseMap_1; +#endif +#ifdef DIFFUSEMAP_2 + uniform sampler2D m_DiffuseMap_2; +#endif +#ifdef DIFFUSEMAP_3 + uniform sampler2D m_DiffuseMap_3; +#endif +#ifdef DIFFUSEMAP_4 + uniform sampler2D m_DiffuseMap_4; +#endif +#ifdef DIFFUSEMAP_5 + uniform sampler2D m_DiffuseMap_5; +#endif +#ifdef DIFFUSEMAP_6 + uniform sampler2D m_DiffuseMap_6; +#endif +#ifdef DIFFUSEMAP_7 + uniform sampler2D m_DiffuseMap_7; +#endif +#ifdef DIFFUSEMAP_8 + uniform sampler2D m_DiffuseMap_8; +#endif +#ifdef DIFFUSEMAP_9 + uniform sampler2D m_DiffuseMap_9; +#endif +#ifdef DIFFUSEMAP_10 + uniform sampler2D m_DiffuseMap_10; +#endif +#ifdef DIFFUSEMAP_11 + uniform sampler2D m_DiffuseMap_11; +#endif + + +#ifdef DIFFUSEMAP_0_SCALE + uniform float m_DiffuseMap_0_scale; +#endif +#ifdef DIFFUSEMAP_1_SCALE + uniform float m_DiffuseMap_1_scale; +#endif +#ifdef DIFFUSEMAP_2_SCALE + uniform float m_DiffuseMap_2_scale; +#endif +#ifdef DIFFUSEMAP_3_SCALE + uniform float m_DiffuseMap_3_scale; +#endif +#ifdef DIFFUSEMAP_4_SCALE + uniform float m_DiffuseMap_4_scale; +#endif +#ifdef DIFFUSEMAP_5_SCALE + uniform float m_DiffuseMap_5_scale; +#endif +#ifdef DIFFUSEMAP_6_SCALE + uniform float m_DiffuseMap_6_scale; +#endif +#ifdef DIFFUSEMAP_7_SCALE + uniform float m_DiffuseMap_7_scale; +#endif +#ifdef DIFFUSEMAP_8_SCALE + uniform float m_DiffuseMap_8_scale; +#endif +#ifdef DIFFUSEMAP_9_SCALE + uniform float m_DiffuseMap_9_scale; +#endif +#ifdef DIFFUSEMAP_10_SCALE + uniform float m_DiffuseMap_10_scale; +#endif +#ifdef DIFFUSEMAP_11_SCALE + uniform float m_DiffuseMap_11_scale; +#endif + + +#ifdef ALPHAMAP + uniform sampler2D m_AlphaMap; +#endif +#ifdef ALPHAMAP_1 + uniform sampler2D m_AlphaMap_1; +#endif +#ifdef ALPHAMAP_2 + uniform sampler2D m_AlphaMap_2; +#endif + +#ifdef NORMALMAP + uniform sampler2D m_NormalMap; +#endif +#ifdef NORMALMAP_1 + uniform sampler2D m_NormalMap_1; +#endif +#ifdef NORMALMAP_2 + uniform sampler2D m_NormalMap_2; +#endif +#ifdef NORMALMAP_3 + uniform sampler2D m_NormalMap_3; +#endif +#ifdef NORMALMAP_4 + uniform sampler2D m_NormalMap_4; +#endif +#ifdef NORMALMAP_5 + uniform sampler2D m_NormalMap_5; +#endif +#ifdef NORMALMAP_6 + uniform sampler2D m_NormalMap_6; +#endif +#ifdef NORMALMAP_7 + uniform sampler2D m_NormalMap_7; +#endif +#ifdef NORMALMAP_8 + uniform sampler2D m_NormalMap_8; +#endif +#ifdef NORMALMAP_9 + uniform sampler2D m_NormalMap_9; +#endif +#ifdef NORMALMAP_10 + uniform sampler2D m_NormalMap_10; +#endif +#ifdef NORMALMAP_11 + uniform sampler2D m_NormalMap_11; +#endif + + +#ifdef TRI_PLANAR_MAPPING + varying vec4 wVertex; + varying vec3 wNormal; +#endif + + + +float tangDot(in vec3 v1, in vec3 v2){ + float d = dot(v1,v2); + #ifdef V_TANGENT + d = 1.0 - d*d; + return step(0.0, d) * sqrt(d); + #else + return d; + #endif +} + + +float lightComputeDiffuse(in vec3 norm, in vec3 lightdir, in vec3 viewdir){ + return max(0.0, dot(norm, lightdir)); +} + +float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){ + #ifdef WARDISO + // Isotropic Ward + vec3 halfVec = normalize(viewdir + lightdir); + float NdotH = max(0.001, tangDot(norm, halfVec)); + float NdotV = max(0.001, tangDot(norm, viewdir)); + float NdotL = max(0.001, tangDot(norm, lightdir)); + float a = tan(acos(NdotH)); + float p = max(shiny/128.0, 0.001); + return NdotL * (1.0 / (4.0*3.14159265*p*p)) * (exp(-(a*a)/(p*p)) / (sqrt(NdotV * NdotL))); + #else + // Standard Phong + vec3 R = reflect(-lightdir, norm); + return pow(max(tangDot(R, viewdir), 0.0), shiny); + #endif +} + +vec2 computeLighting(in vec3 wvPos, in vec3 wvNorm, in vec3 wvViewDir, in vec3 wvLightDir){ + float diffuseFactor = lightComputeDiffuse(wvNorm, wvLightDir, wvViewDir); + float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, wvLightDir, m_Shininess); + + if (m_Shininess <= 1.0) { + specularFactor = 0.0; // should be one instruction on most cards .. + } + + float att = vLightDir.w; + + return vec2(diffuseFactor, specularFactor) * vec2(att); +} + + +#ifdef ALPHAMAP + + vec4 calculateDiffuseBlend(in vec2 texCoord) { + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + vec4 diffuseColor = texture2D(m_DiffuseMap, texCoord * m_DiffuseMap_0_scale); + diffuseColor *= alphaBlend.r; + #ifdef DIFFUSEMAP_1 + vec4 diffuseColor1 = texture2D(m_DiffuseMap_1, texCoord * m_DiffuseMap_1_scale); + diffuseColor = mix( diffuseColor, diffuseColor1, alphaBlend.g ); + #ifdef DIFFUSEMAP_2 + vec4 diffuseColor2 = texture2D(m_DiffuseMap_2, texCoord * m_DiffuseMap_2_scale); + diffuseColor = mix( diffuseColor, diffuseColor2, alphaBlend.b ); + #ifdef DIFFUSEMAP_3 + vec4 diffuseColor3 = texture2D(m_DiffuseMap_3, texCoord * m_DiffuseMap_3_scale); + diffuseColor = mix( diffuseColor, diffuseColor3, alphaBlend.a ); + #ifdef ALPHAMAP_1 + #ifdef DIFFUSEMAP_4 + vec4 diffuseColor4 = texture2D(m_DiffuseMap_4, texCoord * m_DiffuseMap_4_scale); + diffuseColor = mix( diffuseColor, diffuseColor4, alphaBlend1.r ); + #ifdef DIFFUSEMAP_5 + vec4 diffuseColor5 = texture2D(m_DiffuseMap_5, texCoord * m_DiffuseMap_5_scale); + diffuseColor = mix( diffuseColor, diffuseColor5, alphaBlend1.g ); + #ifdef DIFFUSEMAP_6 + vec4 diffuseColor6 = texture2D(m_DiffuseMap_6, texCoord * m_DiffuseMap_6_scale); + diffuseColor = mix( diffuseColor, diffuseColor6, alphaBlend1.b ); + #ifdef DIFFUSEMAP_7 + vec4 diffuseColor7 = texture2D(m_DiffuseMap_7, texCoord * m_DiffuseMap_7_scale); + diffuseColor = mix( diffuseColor, diffuseColor7, alphaBlend1.a ); + #ifdef ALPHAMAP_2 + #ifdef DIFFUSEMAP_8 + vec4 diffuseColor8 = texture2D(m_DiffuseMap_8, texCoord * m_DiffuseMap_8_scale); + diffuseColor = mix( diffuseColor, diffuseColor8, alphaBlend2.r ); + #ifdef DIFFUSEMAP_9 + vec4 diffuseColor9 = texture2D(m_DiffuseMap_9, texCoord * m_DiffuseMap_9_scale); + diffuseColor = mix( diffuseColor, diffuseColor9, alphaBlend2.g ); + #ifdef DIFFUSEMAP_10 + vec4 diffuseColor10 = texture2D(m_DiffuseMap_10, texCoord * m_DiffuseMap_10_scale); + diffuseColor = mix( diffuseColor, diffuseColor10, alphaBlend2.b ); + #ifdef DIFFUSEMAP_11 + vec4 diffuseColor11 = texture2D(m_DiffuseMap_11, texCoord * m_DiffuseMap_11_scale); + diffuseColor = mix( diffuseColor, diffuseColor11, alphaBlend2.a ); + #endif + #endif + #endif + #endif + #endif + #endif + #endif + #endif + #endif + #endif + #endif + #endif + #endif + return diffuseColor; + } + + vec3 calculateNormal(in vec2 texCoord) { + vec3 normal = vec3(0,0,1); + vec3 n = vec3(0,0,0); + + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + #ifdef NORMALMAP + n = texture2D(m_NormalMap, texCoord * m_DiffuseMap_0_scale).xyz; + normal += n * alphaBlend.r; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.r; + #endif + + #ifdef NORMALMAP_1 + n = texture2D(m_NormalMap_1, texCoord * m_DiffuseMap_1_scale).xyz; + normal += n * alphaBlend.g; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.g; + #endif + + #ifdef NORMALMAP_2 + n = texture2D(m_NormalMap_2, texCoord * m_DiffuseMap_2_scale).xyz; + normal += n * alphaBlend.b; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.b; + #endif + + #ifdef NORMALMAP_3 + n = texture2D(m_NormalMap_3, texCoord * m_DiffuseMap_3_scale).xyz; + normal += n * alphaBlend.a; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.a; + #endif + + #ifdef ALPHAMAP_1 + #ifdef NORMALMAP_4 + n = texture2D(m_NormalMap_4, texCoord * m_DiffuseMap_4_scale).xyz; + normal += n * alphaBlend1.r; + #endif + + #ifdef NORMALMAP_5 + n = texture2D(m_NormalMap_5, texCoord * m_DiffuseMap_5_scale).xyz; + normal += n * alphaBlend1.g; + #endif + + #ifdef NORMALMAP_6 + n = texture2D(m_NormalMap_6, texCoord * m_DiffuseMap_6_scale).xyz; + normal += n * alphaBlend1.b; + #endif + + #ifdef NORMALMAP_7 + n = texture2D(m_NormalMap_7, texCoord * m_DiffuseMap_7_scale).xyz; + normal += n * alphaBlend1.a; + #endif + #endif + + #ifdef ALPHAMAP_2 + #ifdef NORMALMAP_8 + n = texture2D(m_NormalMap_8, texCoord * m_DiffuseMap_8_scale).xyz; + normal += n * alphaBlend2.r; + #endif + + #ifdef NORMALMAP_9 + n = texture2D(m_NormalMap_9, texCoord * m_DiffuseMap_9_scale); + normal += n * alphaBlend2.g; + #endif + + #ifdef NORMALMAP_10 + n = texture2D(m_NormalMap_10, texCoord * m_DiffuseMap_10_scale); + normal += n * alphaBlend2.b; + #endif + + #ifdef NORMALMAP_11 + n = texture2D(m_NormalMap_11, texCoord * m_DiffuseMap_11_scale); + normal += n * alphaBlend2.a; + #endif + #endif + + normal = (normal.xyz * vec3(2.0) - vec3(1.0)); + return normalize(normal); + } + + #ifdef TRI_PLANAR_MAPPING + + vec4 getTriPlanarBlend(in vec4 coords, in vec3 blending, in sampler2D map, in float scale) { + vec4 col1 = texture2D( map, coords.yz * scale); + vec4 col2 = texture2D( map, coords.xz * scale); + vec4 col3 = texture2D( map, coords.xy * scale); + // blend the results of the 3 planar projections. + vec4 tex = col1 * blending.x + col2 * blending.y + col3 * blending.z; + return tex; + } + + vec4 calculateTriPlanarDiffuseBlend(in vec3 wNorm, in vec4 wVert, in vec2 texCoord) { + // tri-planar texture bending factor for this fragment's normal + vec3 blending = abs( wNorm ); + blending = (blending -0.2) * 0.7; + blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blending.x + blending.y + blending.z); + blending /= vec3(b, b, b); + + // texture coords + vec4 coords = wVert; + + // blend the results of the 3 planar projections. + vec4 tex0 = getTriPlanarBlend(coords, blending, m_DiffuseMap, m_DiffuseMap_0_scale); + + #ifdef DIFFUSEMAP_1 + // blend the results of the 3 planar projections. + vec4 tex1 = getTriPlanarBlend(coords, blending, m_DiffuseMap_1, m_DiffuseMap_1_scale); + #endif + #ifdef DIFFUSEMAP_2 + // blend the results of the 3 planar projections. + vec4 tex2 = getTriPlanarBlend(coords, blending, m_DiffuseMap_2, m_DiffuseMap_2_scale); + #endif + #ifdef DIFFUSEMAP_3 + // blend the results of the 3 planar projections. + vec4 tex3 = getTriPlanarBlend(coords, blending, m_DiffuseMap_3, m_DiffuseMap_3_scale); + #endif + #ifdef DIFFUSEMAP_4 + // blend the results of the 3 planar projections. + vec4 tex4 = getTriPlanarBlend(coords, blending, m_DiffuseMap_4, m_DiffuseMap_4_scale); + #endif + #ifdef DIFFUSEMAP_5 + // blend the results of the 3 planar projections. + vec4 tex5 = getTriPlanarBlend(coords, blending, m_DiffuseMap_5, m_DiffuseMap_5_scale); + #endif + #ifdef DIFFUSEMAP_6 + // blend the results of the 3 planar projections. + vec4 tex6 = getTriPlanarBlend(coords, blending, m_DiffuseMap_6, m_DiffuseMap_6_scale); + #endif + #ifdef DIFFUSEMAP_7 + // blend the results of the 3 planar projections. + vec4 tex7 = getTriPlanarBlend(coords, blending, m_DiffuseMap_7, m_DiffuseMap_7_scale); + #endif + #ifdef DIFFUSEMAP_8 + // blend the results of the 3 planar projections. + vec4 tex8 = getTriPlanarBlend(coords, blending, m_DiffuseMap_8, m_DiffuseMap_8_scale); + #endif + #ifdef DIFFUSEMAP_9 + // blend the results of the 3 planar projections. + vec4 tex9 = getTriPlanarBlend(coords, blending, m_DiffuseMap_9, m_DiffuseMap_9_scale); + #endif + #ifdef DIFFUSEMAP_10 + // blend the results of the 3 planar projections. + vec4 tex10 = getTriPlanarBlend(coords, blending, m_DiffuseMap_10, m_DiffuseMap_10_scale); + #endif + #ifdef DIFFUSEMAP_11 + // blend the results of the 3 planar projections. + vec4 tex11 = getTriPlanarBlend(coords, blending, m_DiffuseMap_11, m_DiffuseMap_11_scale); + #endif + + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + vec4 diffuseColor = tex0 * alphaBlend.r; + #ifdef DIFFUSEMAP_1 + diffuseColor = mix( diffuseColor, tex1, alphaBlend.g ); + #ifdef DIFFUSEMAP_2 + diffuseColor = mix( diffuseColor, tex2, alphaBlend.b ); + #ifdef DIFFUSEMAP_3 + diffuseColor = mix( diffuseColor, tex3, alphaBlend.a ); + #ifdef ALPHAMAP_1 + #ifdef DIFFUSEMAP_4 + diffuseColor = mix( diffuseColor, tex4, alphaBlend1.r ); + #ifdef DIFFUSEMAP_5 + diffuseColor = mix( diffuseColor, tex5, alphaBlend1.g ); + #ifdef DIFFUSEMAP_6 + diffuseColor = mix( diffuseColor, tex6, alphaBlend1.b ); + #ifdef DIFFUSEMAP_7 + diffuseColor = mix( diffuseColor, tex7, alphaBlend1.a ); + #ifdef ALPHAMAP_2 + #ifdef DIFFUSEMAP_8 + diffuseColor = mix( diffuseColor, tex8, alphaBlend2.r ); + #ifdef DIFFUSEMAP_9 + diffuseColor = mix( diffuseColor, tex9, alphaBlend2.g ); + #ifdef DIFFUSEMAP_10 + diffuseColor = mix( diffuseColor, tex10, alphaBlend2.b ); + #ifdef DIFFUSEMAP_11 + diffuseColor = mix( diffuseColor, tex11, alphaBlend2.a ); + #endif + #endif + #endif + #endif + #endif + #endif + #endif + #endif + #endif + #endif + #endif + #endif + #endif + + return diffuseColor; + } + + vec3 calculateNormalTriPlanar(in vec3 wNorm, in vec4 wVert,in vec2 texCoord) { + // tri-planar texture bending factor for this fragment's world-space normal + vec3 blending = abs( wNorm ); + blending = (blending -0.2) * 0.7; + blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blending.x + blending.y + blending.z); + blending /= vec3(b, b, b); + + // texture coords + vec4 coords = wVert; + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + vec3 normal = vec3(0,0,1); + vec3 n = vec3(0,0,0); + + #ifdef NORMALMAP + n = getTriPlanarBlend(coords, blending, m_NormalMap, m_DiffuseMap_0_scale).xyz; + normal += n * alphaBlend.r; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.r; + #endif + + #ifdef NORMALMAP_1 + n = getTriPlanarBlend(coords, blending, m_NormalMap_1, m_DiffuseMap_1_scale).xyz; + normal += n * alphaBlend.g; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.g; + #endif + + #ifdef NORMALMAP_2 + n = getTriPlanarBlend(coords, blending, m_NormalMap_2, m_DiffuseMap_2_scale).xyz; + normal += n * alphaBlend.b; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.b; + #endif + + #ifdef NORMALMAP_3 + n = getTriPlanarBlend(coords, blending, m_NormalMap_3, m_DiffuseMap_3_scale).xyz; + normal += n * alphaBlend.a; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.a; + #endif + + #ifdef ALPHAMAP_1 + #ifdef NORMALMAP_4 + n = getTriPlanarBlend(coords, blending, m_NormalMap_4, m_DiffuseMap_4_scale).xyz; + normal += n * alphaBlend1.r; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.r; + #endif + + #ifdef NORMALMAP_5 + n = getTriPlanarBlend(coords, blending, m_NormalMap_5, m_DiffuseMap_5_scale).xyz; + normal += n * alphaBlend1.g; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.g; + #endif + + #ifdef NORMALMAP_6 + n = getTriPlanarBlend(coords, blending, m_NormalMap_6, m_DiffuseMap_6_scale).xyz; + normal += n * alphaBlend1.b; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.b; + #endif + + #ifdef NORMALMAP_7 + n = getTriPlanarBlend(coords, blending, m_NormalMap_7, m_DiffuseMap_7_scale).xyz; + normal += n * alphaBlend1.a; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.a; + #endif + #endif + + #ifdef ALPHAMAP_2 + #ifdef NORMALMAP_8 + n = getTriPlanarBlend(coords, blending, m_NormalMap_8, m_DiffuseMap_8_scale).xyz; + normal += n * alphaBlend2.r; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.r; + #endif + + #ifdef NORMALMAP_9 + n = getTriPlanarBlend(coords, blending, m_NormalMap_9, m_DiffuseMap_9_scale).xyz; + normal += n * alphaBlend2.g; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.g; + #endif + + #ifdef NORMALMAP_10 + n = getTriPlanarBlend(coords, blending, m_NormalMap_10, m_DiffuseMap_10_scale).xyz; + normal += n * alphaBlend2.b; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.b; + #endif + + #ifdef NORMALMAP_11 + n = getTriPlanarBlend(coords, blending, m_NormalMap_11, m_DiffuseMap_11_scale).xyz; + normal += n * alphaBlend2.a; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.a; + #endif + #endif + + normal = (normal.xyz * vec3(2.0) - vec3(1.0)); + return normalize(normal); + } + #endif + +#endif + + + +void main(){ + + //---------------------- + // diffuse calculations + //---------------------- + #ifdef DIFFUSEMAP + #ifdef ALPHAMAP + #ifdef TRI_PLANAR_MAPPING + vec4 diffuseColor = calculateTriPlanarDiffuseBlend(wNormal, wVertex, texCoord); + #else + vec4 diffuseColor = calculateDiffuseBlend(texCoord); + #endif + #else + vec4 diffuseColor = texture2D(m_DiffuseMap, texCoord); + #endif + #else + vec4 diffuseColor = vec4(1.0); + #endif + + float spotFallOff = 1.0; + if(g_LightDirection.w!=0.0){ + vec3 L=normalize(lightVec.xyz); + vec3 spotdir = normalize(g_LightDirection.xyz); + float curAngleCos = dot(-L, spotdir); + float innerAngleCos = floor(g_LightDirection.w) * 0.001; + float outerAngleCos = fract(g_LightDirection.w); + float innerMinusOuter = innerAngleCos - outerAngleCos; + + spotFallOff = (curAngleCos - outerAngleCos) / innerMinusOuter; + + if(spotFallOff <= 0.0){ + gl_FragColor = AmbientSum * diffuseColor; + return; + }else{ + spotFallOff = clamp(spotFallOff, 0.0, 1.0); + } + } + + //--------------------- + // normal calculations + //--------------------- + #if defined(NORMALMAP) || defined(NORMALMAP_1) || defined(NORMALMAP_2) || defined(NORMALMAP_3) || defined(NORMALMAP_4) || defined(NORMALMAP_5) || defined(NORMALMAP_6) || defined(NORMALMAP_7) || defined(NORMALMAP_8) || defined(NORMALMAP_9) || defined(NORMALMAP_10) || defined(NORMALMAP_11) + #ifdef TRI_PLANAR_MAPPING + vec3 normal = calculateNormalTriPlanar(wNormal, wVertex, texCoord); + #else + vec3 normal = calculateNormal(texCoord); + #endif + #else + vec3 normal = vNormal; + #endif + + + //----------------------- + // lighting calculations + //----------------------- + vec4 lightDir = vLightDir; + lightDir.xyz = normalize(lightDir.xyz); + + vec2 light = computeLighting(vPosition, normal, vViewDir.xyz, lightDir.xyz)*spotFallOff; + + vec4 specularColor = vec4(1.0); + + //-------------------------- + // final color calculations + //-------------------------- + gl_FragColor = AmbientSum * diffuseColor + + DiffuseSum * diffuseColor * light.x + + SpecularSum * specularColor * light.y; + + //gl_FragColor.a = alpha; +} \ No newline at end of file diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md new file mode 100644 index 000000000..bfdbf5b42 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md @@ -0,0 +1,254 @@ +// NOTE: Doesn't support OpenGL1 +MaterialDef Terrain Lighting { + + MaterialParameters { + + // use tri-planar mapping + Boolean useTriPlanarMapping + + // Use ward specular instead of phong + Boolean WardIso + + // Are we rendering TerrainGrid + Boolean isTerrainGrid + + // Ambient color + Color Ambient + + // Diffuse color + Color Diffuse + + // Specular color + Color Specular + + // Specular power/shininess + Float Shininess : 0 + + // Texture map #0 + Texture2D DiffuseMap + Float DiffuseMap_0_scale + Texture2D NormalMap + + // Texture map #1 + Texture2D DiffuseMap_1 + Float DiffuseMap_1_scale + Texture2D NormalMap_1 + + // Texture map #2 + Texture2D DiffuseMap_2 + Float DiffuseMap_2_scale + Texture2D NormalMap_2 + + // Texture map #3 + Texture2D DiffuseMap_3 + Float DiffuseMap_3_scale + Texture2D NormalMap_3 + + // Texture map #4 + Texture2D DiffuseMap_4 + Float DiffuseMap_4_scale + Texture2D NormalMap_4 + + // Texture map #5 + Texture2D DiffuseMap_5 + Float DiffuseMap_5_scale + Texture2D NormalMap_5 + + // Texture map #6 + Texture2D DiffuseMap_6 + Float DiffuseMap_6_scale + Texture2D NormalMap_6 + + // Texture map #7 + Texture2D DiffuseMap_7 + Float DiffuseMap_7_scale + Texture2D NormalMap_7 + + // Texture map #8 + Texture2D DiffuseMap_8 + Float DiffuseMap_8_scale + Texture2D NormalMap_8 + + // Texture map #9 + Texture2D DiffuseMap_9 + Float DiffuseMap_9_scale + Texture2D NormalMap_9 + + // Texture map #10 + Texture2D DiffuseMap_10 + Float DiffuseMap_10_scale + Texture2D NormalMap_10 + + // Texture map #11 + Texture2D DiffuseMap_11 + Float DiffuseMap_11_scale + Texture2D NormalMap_11 + + + // Specular/gloss map + Texture2D SpecularMap + + + // Texture that specifies alpha values + Texture2D AlphaMap + Texture2D AlphaMap_1 + Texture2D AlphaMap_2 + + // Texture of the glowing parts of the material + Texture2D GlowMap + + // The glow color of the object + Color GlowColor + } + + Technique { + + LightMode MultiPass + + VertexShader GLSL100: Common/MatDefs/Terrain/TerrainLighting.vert + FragmentShader GLSL100: Common/MatDefs/Terrain/TerrainLighting.frag + + WorldParameters { + WorldViewProjectionMatrix + NormalMatrix + WorldViewMatrix + ViewMatrix + } + + Defines { + TRI_PLANAR_MAPPING : useTriPlanarMapping + TERRAIN_GRID : isTerrainGrid + WARDISO : WardIso + + DIFFUSEMAP : DiffuseMap + DIFFUSEMAP_1 : DiffuseMap_1 + DIFFUSEMAP_2 : DiffuseMap_2 + DIFFUSEMAP_3 : DiffuseMap_3 + DIFFUSEMAP_4 : DiffuseMap_4 + DIFFUSEMAP_5 : DiffuseMap_5 + DIFFUSEMAP_6 : DiffuseMap_6 + DIFFUSEMAP_7 : DiffuseMap_7 + DIFFUSEMAP_8 : DiffuseMap_8 + DIFFUSEMAP_9 : DiffuseMap_9 + DIFFUSEMAP_10 : DiffuseMap_10 + DIFFUSEMAP_11 : DiffuseMap_11 + NORMALMAP : NormalMap + NORMALMAP_1 : NormalMap_1 + NORMALMAP_2 : NormalMap_2 + NORMALMAP_3 : NormalMap_3 + NORMALMAP_4 : NormalMap_4 + NORMALMAP_5 : NormalMap_5 + NORMALMAP_6 : NormalMap_6 + NORMALMAP_7 : NormalMap_7 + NORMALMAP_8 : NormalMap_8 + NORMALMAP_9 : NormalMap_9 + NORMALMAP_10 : NormalMap_10 + NORMALMAP_11 : NormalMap_11 + SPECULARMAP : SpecularMap + ALPHAMAP : AlphaMap + ALPHAMAP_1 : AlphaMap_1 + ALPHAMAP_2 : AlphaMap_2 + DIFFUSEMAP_0_SCALE : DiffuseMap_0_scale + DIFFUSEMAP_1_SCALE : DiffuseMap_1_scale + DIFFUSEMAP_2_SCALE : DiffuseMap_2_scale + DIFFUSEMAP_3_SCALE : DiffuseMap_3_scale + DIFFUSEMAP_4_SCALE : DiffuseMap_4_scale + DIFFUSEMAP_5_SCALE : DiffuseMap_5_scale + DIFFUSEMAP_6_SCALE : DiffuseMap_6_scale + DIFFUSEMAP_7_SCALE : DiffuseMap_7_scale + DIFFUSEMAP_8_SCALE : DiffuseMap_8_scale + DIFFUSEMAP_9_SCALE : DiffuseMap_9_scale + DIFFUSEMAP_10_SCALE : DiffuseMap_10_scale + DIFFUSEMAP_11_SCALE : DiffuseMap_11_scale + } + } + + Technique PreShadow { + + VertexShader GLSL100 : Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL100 : Common/MatDefs/Shadow/PreShadow.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + } + + Defines { + DIFFUSEMAP_ALPHA : DiffuseMap + } + + RenderState { + FaceCull Off + DepthTest On + DepthWrite On + PolyOffset 5 0 + ColorWrite Off + } + + } + + Technique PreNormalPass { + + VertexShader GLSL100 : Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + NormalMatrix + } + + Defines { + DIFFUSEMAP_ALPHA : DiffuseMap + } + + RenderState { + + } + + } + + Technique GBuf { + + VertexShader GLSL100: Common/MatDefs/Light/GBuf.vert + FragmentShader GLSL100: Common/MatDefs/Light/GBuf.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + } + + Defines { + VERTEX_COLOR : UseVertexColor + MATERIAL_COLORS : UseMaterialColors + V_TANGENT : VTangent + MINNAERT : Minnaert + WARDISO : WardIso + + DIFFUSEMAP : DiffuseMap + NORMALMAP : NormalMap + SPECULARMAP : SpecularMap + PARALLAXMAP : ParallaxMap + } + } + + Technique { + LightMode FixedPipeline + } + + 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/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.vert b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.vert new file mode 100644 index 000000000..a3a1cc24e --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.vert @@ -0,0 +1,107 @@ +uniform mat4 g_WorldViewProjectionMatrix; +uniform mat4 g_WorldViewMatrix; +uniform mat3 g_NormalMatrix; +uniform mat4 g_ViewMatrix; + +uniform vec4 g_LightColor; +uniform vec4 g_LightPosition; +uniform vec4 g_AmbientLightColor; + +uniform float m_Shininess; + +attribute vec3 inPosition; +attribute vec3 inNormal; +attribute vec2 inTexCoord; +attribute vec4 inTangent; + +varying vec3 vNormal; +varying vec2 texCoord; +varying vec3 vPosition; +varying vec3 vnPosition; +varying vec3 vViewDir; +varying vec3 vnViewDir; +varying vec4 vLightDir; +varying vec4 vnLightDir; + +varying vec3 lightVec; + +varying vec4 AmbientSum; +varying vec4 DiffuseSum; +varying vec4 SpecularSum; + +#ifdef TRI_PLANAR_MAPPING + varying vec4 wVertex; + varying vec3 wNormal; +#endif + +// JME3 lights in world space +void lightComputeDir(in vec3 worldPos, in vec4 color, in vec4 position, out vec4 lightDir){ + float posLight = step(0.5, color.w); + vec3 tempVec = position.xyz * sign(posLight - 0.5) - (worldPos * posLight); + lightVec.xyz = tempVec; + float dist = length(tempVec); + lightDir.w = clamp(1.0 - position.w * dist * posLight, 0.0, 1.0); + lightDir.xyz = tempVec / vec3(dist); +} + + +void main(){ + vec4 pos = vec4(inPosition, 1.0); + gl_Position = g_WorldViewProjectionMatrix * pos; + #ifdef TERRAIN_GRID + texCoord = inTexCoord * 2.0; + #else + texCoord = inTexCoord; + #endif + + vec3 wvPosition = (g_WorldViewMatrix * pos).xyz; + vec3 wvNormal = normalize(g_NormalMatrix * inNormal); + vec3 viewDir = normalize(-wvPosition); + + vec4 wvLightPos = (g_ViewMatrix * vec4(g_LightPosition.xyz,clamp(g_LightColor.w,0.0,1.0))); + wvLightPos.w = g_LightPosition.w; + vec4 lightColor = g_LightColor; + + //-------------------------- + // specific to normal maps: + //-------------------------- + #if defined(NORMALMAP) || defined(NORMALMAP_1) || defined(NORMALMAP_2) || defined(NORMALMAP_3) || defined(NORMALMAP_4) || defined(NORMALMAP_5) || defined(NORMALMAP_6) || defined(NORMALMAP_7) || defined(NORMALMAP_8) || defined(NORMALMAP_9) || defined(NORMALMAP_10) || defined(NORMALMAP_11) + vec3 wvTangent = normalize(g_NormalMatrix * inTangent.xyz); + vec3 wvBinormal = cross(wvNormal, wvTangent); + + mat3 tbnMat = mat3(wvTangent, wvBinormal * -inTangent.w,wvNormal); + + vPosition = wvPosition * tbnMat; + vViewDir = viewDir * tbnMat; + lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir); + vLightDir.xyz = (vLightDir.xyz * tbnMat).xyz; + #else + + //------------------------- + // general to all lighting + //------------------------- + vNormal = wvNormal; + + vPosition = wvPosition; + vViewDir = viewDir; + + lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir); + + #endif + + //computing spot direction in view space and unpacking spotlight cos + // spotVec=(g_ViewMatrix *vec4(g_LightDirection.xyz,0.0) ); + // spotVec.w=floor(g_LightDirection.w)*0.001; + // lightVec.w = fract(g_LightDirection.w); + + AmbientSum = vec4(0.2, 0.2, 0.2, 1.0) * g_AmbientLightColor; // Default: ambient color is dark gray + DiffuseSum = lightColor; + SpecularSum = lightColor; + + +#ifdef TRI_PLANAR_MAPPING + wVertex = vec4(inPosition,0.0); + wNormal = inNormal; +#endif + +} \ No newline at end of file diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/BaseMesh_249.blend b/jme3-testdata/src/main/resources/Blender/2.4x/BaseMesh_249.blend new file mode 100644 index 000000000..617711c51 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/BaseMesh_249.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/BaseScene.blend b/jme3-testdata/src/main/resources/Blender/2.4x/BaseScene.blend new file mode 100644 index 000000000..f4149af04 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/BaseScene.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/MountainValley_Track.blend b/jme3-testdata/src/main/resources/Blender/2.4x/MountainValley_Track.blend new file mode 100644 index 000000000..f2f794f14 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/MountainValley_Track.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/ObjectAnimation.blend b/jme3-testdata/src/main/resources/Blender/2.4x/ObjectAnimation.blend new file mode 100644 index 000000000..ea0404c87 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/ObjectAnimation.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/SimpleAnimation.blend b/jme3-testdata/src/main/resources/Blender/2.4x/SimpleAnimation.blend new file mode 100644 index 000000000..8ca887d09 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/SimpleAnimation.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/Sinbad.blend b/jme3-testdata/src/main/resources/Blender/2.4x/Sinbad.blend new file mode 100644 index 000000000..3737d0085 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/Sinbad.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter - (Inverted Normal Map).png b/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter - (Inverted Normal Map).png new file mode 100644 index 000000000..cfe461db2 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter - (Inverted Normal Map).png differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter - Height Map.png b/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter - Height Map.png new file mode 100644 index 000000000..20caf5007 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter - Height Map.png differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter.png b/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter.png new file mode 100644 index 000000000..29d6867ab Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter.png differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/animtest.blend b/jme3-testdata/src/main/resources/Blender/2.4x/animtest.blend new file mode 100644 index 000000000..eabd593dc Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/animtest.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/constraints.blend b/jme3-testdata/src/main/resources/Blender/2.4x/constraints.blend new file mode 100644 index 000000000..53976ad01 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/constraints.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/curves.blend b/jme3-testdata/src/main/resources/Blender/2.4x/curves.blend new file mode 100644 index 000000000..9e9b73b1a Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/curves.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/kerrigan.blend b/jme3-testdata/src/main/resources/Blender/2.4x/kerrigan.blend new file mode 100644 index 000000000..51b4266eb Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/kerrigan.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/kerrigan_diffuse.png b/jme3-testdata/src/main/resources/Blender/2.4x/kerrigan_diffuse.png new file mode 100644 index 000000000..3776ea017 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/kerrigan_diffuse.png differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/materials.blend b/jme3-testdata/src/main/resources/Blender/2.4x/materials.blend new file mode 100644 index 000000000..e38be7355 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/materials.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/modifiers.blend b/jme3-testdata/src/main/resources/Blender/2.4x/modifiers.blend new file mode 100644 index 000000000..d722d851f Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/modifiers.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/nurbs.blend b/jme3-testdata/src/main/resources/Blender/2.4x/nurbs.blend new file mode 100644 index 000000000..84b291b73 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/nurbs.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/particles.blend b/jme3-testdata/src/main/resources/Blender/2.4x/particles.blend new file mode 100644 index 000000000..ba7ee53ee Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/particles.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/positions.blend b/jme3-testdata/src/main/resources/Blender/2.4x/positions.blend new file mode 100644 index 000000000..d22e89796 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/positions.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_body.tga b/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_body.tga new file mode 100644 index 000000000..0074ecd4c Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_body.tga differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_clothes.tga b/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_clothes.tga new file mode 100644 index 000000000..51bf211bd Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_clothes.tga differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_sword.tga b/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_sword.tga new file mode 100644 index 000000000..2cabb79e8 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_sword.tga differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/texturedPlaneTest.blend b/jme3-testdata/src/main/resources/Blender/2.4x/texturedPlaneTest.blend new file mode 100644 index 000000000..da8128d6b Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/texturedPlaneTest.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures.blend b/jme3-testdata/src/main/resources/Blender/2.4x/textures.blend new file mode 100644 index 000000000..183fc88d1 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/textures.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures/Concrete_Wall.PNG b/jme3-testdata/src/main/resources/Blender/2.4x/textures/Concrete_Wall.PNG new file mode 100644 index 000000000..b6713622e Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/textures/Concrete_Wall.PNG differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures/Grass_256.png b/jme3-testdata/src/main/resources/Blender/2.4x/textures/Grass_256.png new file mode 100644 index 000000000..4b6cd8466 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/textures/Grass_256.png differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures/SandDesert_StartTower.png b/jme3-testdata/src/main/resources/Blender/2.4x/textures/SandDesert_StartTower.png new file mode 100644 index 000000000..7b2e6ddd2 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/textures/SandDesert_StartTower.png differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures/SkyBox-Mountain.png b/jme3-testdata/src/main/resources/Blender/2.4x/textures/SkyBox-Mountain.png new file mode 100644 index 000000000..ac0821d56 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/textures/SkyBox-Mountain.png differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures/Tar_Cracked.png b/jme3-testdata/src/main/resources/Blender/2.4x/textures/Tar_Cracked.png new file mode 100644 index 000000000..27ba25f32 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/textures/Tar_Cracked.png differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures/WarningStrip.png b/jme3-testdata/src/main/resources/Blender/2.4x/textures/WarningStrip.png new file mode 100644 index 000000000..05366a7af Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.4x/textures/WarningStrip.png differ diff --git a/jme3-testdata/src/main/resources/Blender/2.5x/BaseMesh_256.blend b/jme3-testdata/src/main/resources/Blender/2.5x/BaseMesh_256.blend new file mode 100644 index 000000000..2d058d196 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.5x/BaseMesh_256.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/2.5x/textures.blend b/jme3-testdata/src/main/resources/Blender/2.5x/textures.blend new file mode 100644 index 000000000..a4f743c17 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/2.5x/textures.blend differ diff --git a/jme3-testdata/src/main/resources/Blender/test.conf b/jme3-testdata/src/main/resources/Blender/test.conf new file mode 100644 index 000000000..40b630423 Binary files /dev/null and b/jme3-testdata/src/main/resources/Blender/test.conf differ diff --git a/jme3-testdata/src/main/resources/Effects/Explosion/Debris.png b/jme3-testdata/src/main/resources/Effects/Explosion/Debris.png new file mode 100644 index 000000000..c6eab7bac Binary files /dev/null and b/jme3-testdata/src/main/resources/Effects/Explosion/Debris.png differ diff --git a/jme3-testdata/src/main/resources/Effects/Explosion/flame.png b/jme3-testdata/src/main/resources/Effects/Explosion/flame.png new file mode 100644 index 000000000..d3a31f792 Binary files /dev/null and b/jme3-testdata/src/main/resources/Effects/Explosion/flame.png differ diff --git a/jme3-testdata/src/main/resources/Effects/Explosion/flash.png b/jme3-testdata/src/main/resources/Effects/Explosion/flash.png new file mode 100644 index 000000000..d24091751 Binary files /dev/null and b/jme3-testdata/src/main/resources/Effects/Explosion/flash.png differ diff --git a/jme3-testdata/src/main/resources/Effects/Explosion/roundspark.png b/jme3-testdata/src/main/resources/Effects/Explosion/roundspark.png new file mode 100644 index 000000000..bb2b5d84f Binary files /dev/null and b/jme3-testdata/src/main/resources/Effects/Explosion/roundspark.png differ diff --git a/jme3-testdata/src/main/resources/Effects/Explosion/shockwave.png b/jme3-testdata/src/main/resources/Effects/Explosion/shockwave.png new file mode 100644 index 000000000..62856bd1c Binary files /dev/null and b/jme3-testdata/src/main/resources/Effects/Explosion/shockwave.png differ diff --git a/jme3-testdata/src/main/resources/Effects/Explosion/smoketrail.png b/jme3-testdata/src/main/resources/Effects/Explosion/smoketrail.png new file mode 100644 index 000000000..5b965182a Binary files /dev/null and b/jme3-testdata/src/main/resources/Effects/Explosion/smoketrail.png differ diff --git a/jme3-testdata/src/main/resources/Effects/Explosion/spark.png b/jme3-testdata/src/main/resources/Effects/Explosion/spark.png new file mode 100644 index 000000000..fa2d25a04 Binary files /dev/null and b/jme3-testdata/src/main/resources/Effects/Explosion/spark.png differ diff --git a/jme3-testdata/src/main/resources/Effects/Smoke/Smoke.png b/jme3-testdata/src/main/resources/Effects/Smoke/Smoke.png new file mode 100644 index 000000000..ec46564ba Binary files /dev/null and b/jme3-testdata/src/main/resources/Effects/Smoke/Smoke.png differ diff --git a/jme3-testdata/src/main/resources/Interface/Joystick/gamepad-buttons.png b/jme3-testdata/src/main/resources/Interface/Joystick/gamepad-buttons.png new file mode 100644 index 000000000..ea8f50c69 Binary files /dev/null and b/jme3-testdata/src/main/resources/Interface/Joystick/gamepad-buttons.png differ diff --git a/jme3-testdata/src/main/resources/Interface/Joystick/gamepad-frame.png b/jme3-testdata/src/main/resources/Interface/Joystick/gamepad-frame.png new file mode 100644 index 000000000..0db36a115 Binary files /dev/null and b/jme3-testdata/src/main/resources/Interface/Joystick/gamepad-frame.png differ diff --git a/jme3-testdata/src/main/resources/Interface/Joystick/gamepad-stick.png b/jme3-testdata/src/main/resources/Interface/Joystick/gamepad-stick.png new file mode 100644 index 000000000..75b83d5e5 Binary files /dev/null and b/jme3-testdata/src/main/resources/Interface/Joystick/gamepad-stick.png differ diff --git a/jme3-testdata/src/main/resources/Interface/Logo/Cursor.png b/jme3-testdata/src/main/resources/Interface/Logo/Cursor.png new file mode 100644 index 000000000..ce5f4229d Binary files /dev/null and b/jme3-testdata/src/main/resources/Interface/Logo/Cursor.png differ diff --git a/jme3-testdata/src/main/resources/Interface/Logo/Logo.j3m b/jme3-testdata/src/main/resources/Interface/Logo/Logo.j3m new file mode 100644 index 000000000..7c086b6d8 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Interface/Logo/Monkey.jpg b/jme3-testdata/src/main/resources/Interface/Logo/Monkey.jpg new file mode 100644 index 000000000..cfe465de1 Binary files /dev/null and b/jme3-testdata/src/main/resources/Interface/Logo/Monkey.jpg differ diff --git a/jme3-testdata/src/main/resources/Interface/Logo/Monkey.png b/jme3-testdata/src/main/resources/Interface/Logo/Monkey.png new file mode 100644 index 000000000..e1c8c3d8b Binary files /dev/null and b/jme3-testdata/src/main/resources/Interface/Logo/Monkey.png differ diff --git a/jme3-testdata/src/main/resources/Interface/Nifty/CinematicTest.xml b/jme3-testdata/src/main/resources/Interface/Nifty/CinematicTest.xml new file mode 100644 index 000000000..5d8648207 --- /dev/null +++ b/jme3-testdata/src/main/resources/Interface/Nifty/CinematicTest.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Interface/Nifty/HelloJme.xml b/jme3-testdata/src/main/resources/Interface/Nifty/HelloJme.xml new file mode 100644 index 000000000..c945de6e2 --- /dev/null +++ b/jme3-testdata/src/main/resources/Interface/Nifty/HelloJme.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Interface/icons/SmartMonkey128.png b/jme3-testdata/src/main/resources/Interface/icons/SmartMonkey128.png new file mode 100644 index 000000000..555c72276 Binary files /dev/null and b/jme3-testdata/src/main/resources/Interface/icons/SmartMonkey128.png differ diff --git a/jme3-testdata/src/main/resources/Interface/icons/SmartMonkey16.png b/jme3-testdata/src/main/resources/Interface/icons/SmartMonkey16.png new file mode 100644 index 000000000..34a91c428 Binary files /dev/null and b/jme3-testdata/src/main/resources/Interface/icons/SmartMonkey16.png differ diff --git a/jme3-testdata/src/main/resources/Interface/icons/SmartMonkey256.png b/jme3-testdata/src/main/resources/Interface/icons/SmartMonkey256.png new file mode 100644 index 000000000..02417f986 Binary files /dev/null and b/jme3-testdata/src/main/resources/Interface/icons/SmartMonkey256.png differ diff --git a/jme3-testdata/src/main/resources/Interface/icons/SmartMonkey32.png b/jme3-testdata/src/main/resources/Interface/icons/SmartMonkey32.png new file mode 100644 index 000000000..ee5e2093c Binary files /dev/null and b/jme3-testdata/src/main/resources/Interface/icons/SmartMonkey32.png differ diff --git a/jme3-testdata/src/main/resources/Models/Boat/boat.j3m b/jme3-testdata/src/main/resources/Models/Boat/boat.j3m new file mode 100644 index 000000000..d33459c7d --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Boat/boat.j3m @@ -0,0 +1,6 @@ +Material My Material : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + DiffuseMap : Models/Boat/boat.png + NormalMap : Models/Boat/boat_normal.png + } +} diff --git a/jme3-testdata/src/main/resources/Models/Boat/boat.j3o b/jme3-testdata/src/main/resources/Models/Boat/boat.j3o new file mode 100644 index 000000000..c93d3b03f Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Boat/boat.j3o differ diff --git a/jme3-testdata/src/main/resources/Models/Boat/boat.mesh.xml b/jme3-testdata/src/main/resources/Models/Boat/boat.mesh.xml new file mode 100644 index 000000000..069bfe6c8 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Boat/boat.mesh.xml @@ -0,0 +1,3731 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Boat/boat.png b/jme3-testdata/src/main/resources/Models/Boat/boat.png new file mode 100644 index 000000000..d6ffea96b Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Boat/boat.png differ diff --git a/jme3-testdata/src/main/resources/Models/Boat/boat_normal.png b/jme3-testdata/src/main/resources/Models/Boat/boat_normal.png new file mode 100644 index 000000000..4fc03b15c Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Boat/boat_normal.png differ diff --git a/jme3-testdata/src/main/resources/Models/Buggy/Buggy.j3m b/jme3-testdata/src/main/resources/Models/Buggy/Buggy.j3m new file mode 100644 index 000000000..1cde6ed2b --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Buggy/Buggy.j3m @@ -0,0 +1,14 @@ +Material My Material : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + DiffuseMap : Models/Buggy/buggy_diffuse.jpg + GlowMap : Models/Buggy/buggy_glow.jpg + SpecularMap : Models/Buggy/buggy_specular.jpg + NormalMap : Models/Buggy/buggy_normals.png + Shininess : 10 + + UseMaterialColors : true + Ambient : 0.5 0.5 0.5 1 + Diffuse : 1 1 1 1 + Specular : 1 1 1 1 + } +} diff --git a/jme3-testdata/src/main/resources/Models/Buggy/Buggy.j3o b/jme3-testdata/src/main/resources/Models/Buggy/Buggy.j3o new file mode 100644 index 000000000..3dd776443 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Buggy/Buggy.j3o differ diff --git a/jme3-testdata/src/main/resources/Models/Buggy/buggy_diffuse.jpg b/jme3-testdata/src/main/resources/Models/Buggy/buggy_diffuse.jpg new file mode 100644 index 000000000..45a8fa843 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Buggy/buggy_diffuse.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Buggy/buggy_glow.jpg b/jme3-testdata/src/main/resources/Models/Buggy/buggy_glow.jpg new file mode 100644 index 000000000..952c66a15 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Buggy/buggy_glow.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Buggy/buggy_normals.png b/jme3-testdata/src/main/resources/Models/Buggy/buggy_normals.png new file mode 100644 index 000000000..dd9feefd7 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Buggy/buggy_normals.png differ diff --git a/jme3-testdata/src/main/resources/Models/Buggy/buggy_specular.jpg b/jme3-testdata/src/main/resources/Models/Buggy/buggy_specular.jpg new file mode 100644 index 000000000..a2745594b Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Buggy/buggy_specular.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Elephant/Elephant.j3m b/jme3-testdata/src/main/resources/Models/Elephant/Elephant.j3m new file mode 100644 index 000000000..4a58ec394 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Elephant/Elephant.jpg b/jme3-testdata/src/main/resources/Models/Elephant/Elephant.jpg new file mode 100644 index 000000000..b4b4014d1 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Elephant/Elephant.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Elephant/Elephant.material b/jme3-testdata/src/main/resources/Models/Elephant/Elephant.material new file mode 100644 index 000000000..268d6cc4c --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Elephant/Elephant.mesh.xml b/jme3-testdata/src/main/resources/Models/Elephant/Elephant.mesh.xml new file mode 100644 index 000000000..8bffa1a5e --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Elephant/Elephant.mesh.xml @@ -0,0 +1,62928 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Elephant/Elephant.skeleton.xml b/jme3-testdata/src/main/resources/Models/Elephant/Elephant.skeleton.xml new file mode 100644 index 000000000..79da840d4 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Elephant/Elephant.skeleton.xml @@ -0,0 +1,1422 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Elephant/ElephantEye.jpg b/jme3-testdata/src/main/resources/Models/Elephant/ElephantEye.jpg new file mode 100644 index 000000000..6eb7b18bc Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Elephant/ElephantEye.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Elephant/ElephantTusk.jpg b/jme3-testdata/src/main/resources/Models/Elephant/ElephantTusk.jpg new file mode 100644 index 000000000..e9d534d34 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Elephant/ElephantTusk.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Elephant/Elephant_normal.jpg b/jme3-testdata/src/main/resources/Models/Elephant/Elephant_normal.jpg new file mode 100644 index 000000000..7df4c4355 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Elephant/Elephant_normal.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Ferrari/Car.jpg b/jme3-testdata/src/main/resources/Models/Ferrari/Car.jpg new file mode 100644 index 000000000..56d3f63e5 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Ferrari/Car.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Ferrari/Car.material b/jme3-testdata/src/main/resources/Models/Ferrari/Car.material new file mode 100644 index 000000000..f6c9b99be --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Ferrari/Car.mesh.xml b/jme3-testdata/src/main/resources/Models/Ferrari/Car.mesh.xml new file mode 100644 index 000000000..8668bda73 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Ferrari/Car.mesh.xml @@ -0,0 +1,5083 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Ferrari/Car.scene b/jme3-testdata/src/main/resources/Models/Ferrari/Car.scene new file mode 100644 index 000000000..5ff38600d --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Ferrari/Car.scene @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Ferrari/WheelBackLeft.mesh.xml b/jme3-testdata/src/main/resources/Models/Ferrari/WheelBackLeft.mesh.xml new file mode 100644 index 000000000..75999b2cc --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Ferrari/WheelBackLeft.mesh.xml @@ -0,0 +1,1126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Ferrari/WheelBackRight.mesh.xml b/jme3-testdata/src/main/resources/Models/Ferrari/WheelBackRight.mesh.xml new file mode 100644 index 000000000..d0d7f1f4f --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Ferrari/WheelBackRight.mesh.xml @@ -0,0 +1,1126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Ferrari/WheelFrontLeft.mesh.xml b/jme3-testdata/src/main/resources/Models/Ferrari/WheelFrontLeft.mesh.xml new file mode 100644 index 000000000..57e308c4f --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Ferrari/WheelFrontLeft.mesh.xml @@ -0,0 +1,1126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Ferrari/WheelFrontRight.mesh.xml b/jme3-testdata/src/main/resources/Models/Ferrari/WheelFrontRight.mesh.xml new file mode 100644 index 000000000..6dc7ee46f --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Ferrari/WheelFrontRight.mesh.xml @@ -0,0 +1,1126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/HoverTank/Tank2.mesh.xml b/jme3-testdata/src/main/resources/Models/HoverTank/Tank2.mesh.xml new file mode 100644 index 000000000..514061cb9 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/HoverTank/Tank2.mesh.xml @@ -0,0 +1,80192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/HoverTank/tank.j3m b/jme3-testdata/src/main/resources/Models/HoverTank/tank.j3m new file mode 100644 index 000000000..5621dbeee --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/HoverTank/tank.j3m @@ -0,0 +1,13 @@ +Material My Material : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + SpecularMap : Models/HoverTank/tank_specular.jpg + Shininess : 8 + NormalMap : Models/HoverTank/tank_normals.png + DiffuseMap : Models/HoverTank/tank_diffuse.jpg + GlowMap : Models/HoverTank/tank_glow_map.jpg + 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/jme3-testdata/src/main/resources/Models/HoverTank/tankFinalExport.blend b/jme3-testdata/src/main/resources/Models/HoverTank/tankFinalExport.blend new file mode 100644 index 000000000..ee44ffb9c Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/HoverTank/tankFinalExport.blend differ diff --git a/jme3-testdata/src/main/resources/Models/HoverTank/tank_diffuse.jpg b/jme3-testdata/src/main/resources/Models/HoverTank/tank_diffuse.jpg new file mode 100644 index 000000000..0820ac593 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/HoverTank/tank_diffuse.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/HoverTank/tank_glow_map.jpg b/jme3-testdata/src/main/resources/Models/HoverTank/tank_glow_map.jpg new file mode 100644 index 000000000..a0d15fd0f Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/HoverTank/tank_glow_map.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/HoverTank/tank_normals.png b/jme3-testdata/src/main/resources/Models/HoverTank/tank_normals.png new file mode 100644 index 000000000..794218793 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/HoverTank/tank_normals.png differ diff --git a/jme3-testdata/src/main/resources/Models/HoverTank/tank_specular.jpg b/jme3-testdata/src/main/resources/Models/HoverTank/tank_specular.jpg new file mode 100644 index 000000000..66aad2bc6 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/HoverTank/tank_specular.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Jaime/Jaime.j3m b/jme3-testdata/src/main/resources/Models/Jaime/Jaime.j3m new file mode 100644 index 000000000..92944e59e --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Jaime/Jaime.j3m @@ -0,0 +1,22 @@ +Material MyMaterial : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + DiffuseMap : Models/Jaime/diffuseMap.jpg + NormalMap : Models/Jaime/NormalMap.png + UseMaterialColors : true + Diffuse : 1.0 1.0 1.0 1.0 + Ambient : 0.7058824 0.6509804 0.6 1.0 + Specular : 0.87058824 0.8117647 0.7921569 1.0 + Shininess : 1.0 + } + AdditionalRenderState { + FaceCull Back + Wireframe Off + DepthWrite On + PolyOffset 0.0 0.0 + AlphaTestFalloff 0.0 + Blend Off + PointSprite Off + ColorWrite On + DepthTest On + } +} diff --git a/jme3-testdata/src/main/resources/Models/Jaime/Jaime.j3o b/jme3-testdata/src/main/resources/Models/Jaime/Jaime.j3o new file mode 100644 index 000000000..723553a9d Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Jaime/Jaime.j3o differ diff --git a/jme3-testdata/src/main/resources/Models/Jaime/NormalMap.png b/jme3-testdata/src/main/resources/Models/Jaime/NormalMap.png new file mode 100644 index 000000000..a1a9f6a36 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Jaime/NormalMap.png differ diff --git a/jme3-testdata/src/main/resources/Models/Jaime/diffuseMap.jpg b/jme3-testdata/src/main/resources/Models/Jaime/diffuseMap.jpg new file mode 100644 index 000000000..6fb195696 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Jaime/diffuseMap.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead.j3m b/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead.j3m new file mode 100644 index 000000000..62568d094 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead.mesh.xml b/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead.mesh.xml new file mode 100644 index 000000000..de1f88b54 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead.mesh.xml @@ -0,0 +1,28388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead_diffuse.jpg b/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead_diffuse.jpg new file mode 100644 index 000000000..cfd825430 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead_diffuse.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead_normal.jpg b/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead_normal.jpg new file mode 100644 index 000000000..a36b2f986 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead_normal.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead_spec.jpg b/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead_spec.jpg new file mode 100644 index 000000000..0e1748887 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/MonkeyHead/MonkeyHead_spec.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Ninja/Ninja.jpg b/jme3-testdata/src/main/resources/Models/Ninja/Ninja.jpg new file mode 100644 index 000000000..9397f5d91 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Ninja/Ninja.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Ninja/Ninja.material b/jme3-testdata/src/main/resources/Models/Ninja/Ninja.material new file mode 100644 index 000000000..71df902ef --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Ninja/Ninja.mesh.xml b/jme3-testdata/src/main/resources/Models/Ninja/Ninja.mesh.xml new file mode 100644 index 000000000..e51900827 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Ninja/Ninja.mesh.xml @@ -0,0 +1,7773 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Ninja/Ninja.skeleton.xml b/jme3-testdata/src/main/resources/Models/Ninja/Ninja.skeleton.xml new file mode 100644 index 000000000..632539e31 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Ninja/Ninja.skeleton.xml @@ -0,0 +1,13299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Oto/Oto.j3m b/jme3-testdata/src/main/resources/Models/Oto/Oto.j3m new file mode 100644 index 000000000..7ffd8660e --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Oto/Oto.jpg b/jme3-testdata/src/main/resources/Models/Oto/Oto.jpg new file mode 100644 index 000000000..2178a5e1e Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Oto/Oto.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Oto/Oto.material b/jme3-testdata/src/main/resources/Models/Oto/Oto.material new file mode 100644 index 000000000..b73faf413 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Oto/Oto.mesh.xml b/jme3-testdata/src/main/resources/Models/Oto/Oto.mesh.xml new file mode 100644 index 000000000..a1c7f95b5 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Oto/Oto.mesh.xml @@ -0,0 +1,29285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Oto/Oto.skeleton.xml b/jme3-testdata/src/main/resources/Models/Oto/Oto.skeleton.xml new file mode 100644 index 000000000..9e256af1a --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Oto/Oto.skeleton.xml @@ -0,0 +1,8801 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m new file mode 100644 index 000000000..91967d5c4 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.jpg b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.jpg new file mode 100644 index 000000000..193a0e64b Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.material b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.material new file mode 100644 index 000000000..b4d3436df --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.mesh.xml b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.mesh.xml new file mode 100644 index 000000000..8fcd4334b --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.mesh.xml @@ -0,0 +1,1547 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post_normal.jpg b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post_normal.jpg new file mode 100644 index 000000000..bb8a0c893 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post_normal.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post_specular.jpg b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post_specular.jpg new file mode 100644 index 000000000..67468106a Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post_specular.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Sinbad/README-LICENSE.txt b/jme3-testdata/src/main/resources/Models/Sinbad/README-LICENSE.txt new file mode 100644 index 000000000..7a0f73383 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Sinbad/Sinbad.material b/jme3-testdata/src/main/resources/Models/Sinbad/Sinbad.material new file mode 100644 index 000000000..9ee737cdf --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Sinbad/Sinbad.mesh.xml b/jme3-testdata/src/main/resources/Models/Sinbad/Sinbad.mesh.xml new file mode 100644 index 000000000..1909ec0cd --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Sinbad/Sinbad.mesh.xml @@ -0,0 +1,44303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Sinbad/Sinbad.skeleton.xml b/jme3-testdata/src/main/resources/Models/Sinbad/Sinbad.skeleton.xml new file mode 100644 index 000000000..798212dbb --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Sinbad/Sinbad.skeleton.xml @@ -0,0 +1,126454 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Sinbad/Sword.material b/jme3-testdata/src/main/resources/Models/Sinbad/Sword.material new file mode 100644 index 000000000..2baa89c18 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Sinbad/Sword.mesh.xml b/jme3-testdata/src/main/resources/Models/Sinbad/Sword.mesh.xml new file mode 100644 index 000000000..eafa077b2 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Sinbad/Sword.mesh.xml @@ -0,0 +1,2193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Sinbad/sinbad_body.jpg b/jme3-testdata/src/main/resources/Models/Sinbad/sinbad_body.jpg new file mode 100644 index 000000000..e9679d971 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Sinbad/sinbad_body.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Sinbad/sinbad_clothes.jpg b/jme3-testdata/src/main/resources/Models/Sinbad/sinbad_clothes.jpg new file mode 100644 index 000000000..f52e014ca Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Sinbad/sinbad_clothes.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Sinbad/sinbad_sword.jpg b/jme3-testdata/src/main/resources/Models/Sinbad/sinbad_sword.jpg new file mode 100644 index 000000000..f555eabdc Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Sinbad/sinbad_sword.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/SpaceCraft/Rocket.material b/jme3-testdata/src/main/resources/Models/SpaceCraft/Rocket.material new file mode 100644 index 000000000..9939857f2 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/SpaceCraft/Rocket.material @@ -0,0 +1,13 @@ +material SOLID/TEX/Rocket.tga +{ + technique + { + pass + { + texture_unit + { + texture Rocket.png + } + } + } +} diff --git a/jme3-testdata/src/main/resources/Models/SpaceCraft/Rocket.mesh.xml b/jme3-testdata/src/main/resources/Models/SpaceCraft/Rocket.mesh.xml new file mode 100644 index 000000000..14164245c --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/SpaceCraft/Rocket.mesh.xml @@ -0,0 +1,1150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/SpaceCraft/Rocket.png b/jme3-testdata/src/main/resources/Models/SpaceCraft/Rocket.png new file mode 100644 index 000000000..ffb94aa4f Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/SpaceCraft/Rocket.png differ diff --git a/jme3-testdata/src/main/resources/Models/Sponza/Sponza.j3o b/jme3-testdata/src/main/resources/Models/Sponza/Sponza.j3o new file mode 100644 index 000000000..ac71efa10 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Sponza/Sponza.j3o differ diff --git a/jme3-testdata/src/main/resources/Models/TangentBugs/test.blend b/jme3-testdata/src/main/resources/Models/TangentBugs/test.blend new file mode 100644 index 000000000..51890a238 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/TangentBugs/test.blend differ diff --git a/jme3-testdata/src/main/resources/Models/TangentBugs/test_normal.png b/jme3-testdata/src/main/resources/Models/TangentBugs/test_normal.png new file mode 100644 index 000000000..f586e8dc8 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/TangentBugs/test_normal.png differ diff --git a/jme3-testdata/src/main/resources/Models/Teapot/Teapot.mesh.xml b/jme3-testdata/src/main/resources/Models/Teapot/Teapot.mesh.xml new file mode 100644 index 000000000..b0aed3c42 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Teapot/Teapot.mesh.xml @@ -0,0 +1,34297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Teapot/Teapot.mtl b/jme3-testdata/src/main/resources/Models/Teapot/Teapot.mtl new file mode 100644 index 000000000..11afe1167 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Teapot/Teapot.obj b/jme3-testdata/src/main/resources/Models/Teapot/Teapot.obj new file mode 100644 index 000000000..779b3b877 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Terrain/Terrain.mesh.xml b/jme3-testdata/src/main/resources/Models/Terrain/Terrain.mesh.xml new file mode 100644 index 000000000..611fd9ef4 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Terrain/Terrain.mesh.xml @@ -0,0 +1,115985 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o b/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o new file mode 100644 index 000000000..1d56eefa9 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o differ diff --git a/jme3-testdata/src/main/resources/Models/Tree/BarkColor.jpg b/jme3-testdata/src/main/resources/Models/Tree/BarkColor.jpg new file mode 100644 index 000000000..cd40c7d2c Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Tree/BarkColor.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Tree/BarkNormal.jpg b/jme3-testdata/src/main/resources/Models/Tree/BarkNormal.jpg new file mode 100644 index 000000000..cc0ecc02b Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Tree/BarkNormal.jpg differ diff --git a/jme3-testdata/src/main/resources/Models/Tree/Leaves.j3m b/jme3-testdata/src/main/resources/Models/Tree/Leaves.j3m new file mode 100644 index 000000000..fddc2f75c --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Models/Tree/Leaves.png b/jme3-testdata/src/main/resources/Models/Tree/Leaves.png new file mode 100644 index 000000000..a3a9f8e33 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Tree/Leaves.png differ diff --git a/jme3-testdata/src/main/resources/Models/Tree/Tree.mesh.j3o b/jme3-testdata/src/main/resources/Models/Tree/Tree.mesh.j3o new file mode 100644 index 000000000..f77a37bbe Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Tree/Tree.mesh.j3o differ diff --git a/jme3-testdata/src/main/resources/Models/Tree/Tree.mesh.j3odata b/jme3-testdata/src/main/resources/Models/Tree/Tree.mesh.j3odata new file mode 100644 index 000000000..96938a36b --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Tree/Tree.mesh.j3odata @@ -0,0 +1,3 @@ +# +#Thu Aug 25 22:31:31 CEST 2011 +ORIGINAL_PATH=Models/Tree/Tree.mesh.j3o diff --git a/jme3-testdata/src/main/resources/Models/Tree/Tree.mesh.xml b/jme3-testdata/src/main/resources/Models/Tree/Tree.mesh.xml new file mode 100644 index 000000000..3c197b86c --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Tree/Tree.mesh.xml @@ -0,0 +1,20727 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Models/Tree/Trunk.j3m b/jme3-testdata/src/main/resources/Models/Tree/Trunk.j3m new file mode 100644 index 000000000..061dc2331 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Tree/Trunk.j3m @@ -0,0 +1,11 @@ +Material Trunk : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + DiffuseMap : Repeat Models/Tree/BarkColor.jpg + NormalMap : Repeat Models/Tree/BarkNormal.jpg + 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/jme3-testdata/src/main/resources/Models/WaterTest/WaterTest.mesh.xml b/jme3-testdata/src/main/resources/Models/WaterTest/WaterTest.mesh.xml new file mode 100644 index 000000000..6908e62aa --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/WaterTest/WaterTest.mesh.xml @@ -0,0 +1,7505 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Scenes/Beach/FullskiesSunset0068.dds b/jme3-testdata/src/main/resources/Scenes/Beach/FullskiesSunset0068.dds new file mode 100644 index 000000000..6d5e0d8e8 Binary files /dev/null and b/jme3-testdata/src/main/resources/Scenes/Beach/FullskiesSunset0068.dds differ diff --git a/jme3-testdata/src/main/resources/Scenes/DotScene/DotScene.scene b/jme3-testdata/src/main/resources/Scenes/DotScene/DotScene.scene new file mode 100644 index 000000000..dea6c15b3 --- /dev/null +++ b/jme3-testdata/src/main/resources/Scenes/DotScene/DotScene.scene @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Scenes/ManyLights/AO.jpg b/jme3-testdata/src/main/resources/Scenes/ManyLights/AO.jpg new file mode 100644 index 000000000..00990785d Binary files /dev/null and b/jme3-testdata/src/main/resources/Scenes/ManyLights/AO.jpg differ diff --git a/jme3-testdata/src/main/resources/Scenes/ManyLights/Grid.mesh.xml b/jme3-testdata/src/main/resources/Scenes/ManyLights/Grid.mesh.xml new file mode 100644 index 000000000..f5d75e44e --- /dev/null +++ b/jme3-testdata/src/main/resources/Scenes/ManyLights/Grid.mesh.xml @@ -0,0 +1,7162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Scenes/ManyLights/Main.material b/jme3-testdata/src/main/resources/Scenes/ManyLights/Main.material new file mode 100644 index 000000000..6b90874f3 --- /dev/null +++ b/jme3-testdata/src/main/resources/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.jpg + tex_address_mode wrap + filtering trilinear + } + } + } +} diff --git a/jme3-testdata/src/main/resources/Scenes/ManyLights/Main.scene b/jme3-testdata/src/main/resources/Scenes/ManyLights/Main.scene new file mode 100644 index 000000000..eb260fbc3 --- /dev/null +++ b/jme3-testdata/src/main/resources/Scenes/ManyLights/Main.scene @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-testdata/src/main/resources/Sound/Effects/Bang.wav b/jme3-testdata/src/main/resources/Sound/Effects/Bang.wav new file mode 100644 index 000000000..4bfb435e7 Binary files /dev/null and b/jme3-testdata/src/main/resources/Sound/Effects/Bang.wav differ diff --git a/jme3-testdata/src/main/resources/Sound/Effects/Beep.ogg b/jme3-testdata/src/main/resources/Sound/Effects/Beep.ogg new file mode 100644 index 000000000..91eb7191f Binary files /dev/null and b/jme3-testdata/src/main/resources/Sound/Effects/Beep.ogg differ diff --git a/jme3-testdata/src/main/resources/Sound/Effects/Foot steps.ogg b/jme3-testdata/src/main/resources/Sound/Effects/Foot steps.ogg new file mode 100644 index 000000000..d10d3c793 Binary files /dev/null and b/jme3-testdata/src/main/resources/Sound/Effects/Foot steps.ogg differ diff --git a/jme3-testdata/src/main/resources/Sound/Effects/Gun.wav b/jme3-testdata/src/main/resources/Sound/Effects/Gun.wav new file mode 100644 index 000000000..eecc4d107 Binary files /dev/null and b/jme3-testdata/src/main/resources/Sound/Effects/Gun.wav differ diff --git a/jme3-testdata/src/main/resources/Sound/Effects/kick.wav b/jme3-testdata/src/main/resources/Sound/Effects/kick.wav new file mode 100644 index 000000000..6a3f26f1f Binary files /dev/null and b/jme3-testdata/src/main/resources/Sound/Effects/kick.wav differ diff --git a/jme3-testdata/src/main/resources/Sound/Environment/Nature.ogg b/jme3-testdata/src/main/resources/Sound/Environment/Nature.ogg new file mode 100644 index 000000000..e98d73e45 Binary files /dev/null and b/jme3-testdata/src/main/resources/Sound/Environment/Nature.ogg differ diff --git a/jme3-testdata/src/main/resources/Sound/Environment/Ocean Waves.ogg b/jme3-testdata/src/main/resources/Sound/Environment/Ocean Waves.ogg new file mode 100644 index 000000000..a52c96c3f Binary files /dev/null and b/jme3-testdata/src/main/resources/Sound/Environment/Ocean Waves.ogg differ diff --git a/jme3-testdata/src/main/resources/Sound/Environment/River.ogg b/jme3-testdata/src/main/resources/Sound/Environment/River.ogg new file mode 100644 index 000000000..e213fe394 Binary files /dev/null and b/jme3-testdata/src/main/resources/Sound/Environment/River.ogg differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/TerrainGrid.j3o b/jme3-testdata/src/main/resources/TerrainGrid/TerrainGrid.j3o new file mode 100644 index 000000000..a9134f0d6 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/TerrainGrid.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_-1.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_-1.j3o new file mode 100644 index 000000000..1e9467d91 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_-1.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_-2.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_-2.j3o new file mode 100644 index 000000000..c7ee9345e Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_-2.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_-3.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_-3.j3o new file mode 100644 index 000000000..c13985efc Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_-3.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_0.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_0.j3o new file mode 100644 index 000000000..73c5a009c Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_0.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_1.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_1.j3o new file mode 100644 index 000000000..bd2157fa2 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_0_0_1.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_-1.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_-1.j3o new file mode 100644 index 000000000..2a8b59d82 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_-1.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_-2.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_-2.j3o new file mode 100644 index 000000000..0a69a0d87 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_-2.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_-3.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_-3.j3o new file mode 100644 index 000000000..2a044106e Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_-3.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_0.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_0.j3o new file mode 100644 index 000000000..140dcf850 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_0.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_1.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_1.j3o new file mode 100644 index 000000000..7d20ace1f Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_1_0_1.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_-1.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_-1.j3o new file mode 100644 index 000000000..1c4d1fbc0 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_-1.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_-2.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_-2.j3o new file mode 100644 index 000000000..cd87ea5ef Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_-2.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_-3.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_-3.j3o new file mode 100644 index 000000000..08251160d Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_-3.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_-4.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_-4.j3o new file mode 100644 index 000000000..b45d2c39b Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_-4.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_0.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_0.j3o new file mode 100644 index 000000000..7764d0ccc Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_2_0_0.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_-1.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_-1.j3o new file mode 100644 index 000000000..c8cbb413a Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_-1.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_-2.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_-2.j3o new file mode 100644 index 000000000..edada9624 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_-2.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_-3.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_-3.j3o new file mode 100644 index 000000000..6d61636b5 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_-3.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_-4.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_-4.j3o new file mode 100644 index 000000000..a14d6cf64 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_-4.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_0.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_0.j3o new file mode 100644 index 000000000..7137ee9a9 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_3_0_0.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_4_0_-1.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_4_0_-1.j3o new file mode 100644 index 000000000..f0f6471c0 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_4_0_-1.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_4_0_-2.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_4_0_-2.j3o new file mode 100644 index 000000000..95699bfa2 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_4_0_-2.j3o differ diff --git a/jme3-testdata/src/main/resources/TerrainGrid/testgrid_4_0_-3.j3o b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_4_0_-3.j3o new file mode 100644 index 000000000..b44143149 Binary files /dev/null and b/jme3-testdata/src/main/resources/TerrainGrid/testgrid_4_0_-3.j3o differ diff --git a/jme3-testdata/src/main/resources/Textures/3D/flame.dds b/jme3-testdata/src/main/resources/Textures/3D/flame.dds new file mode 100644 index 000000000..2eb00f746 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/3D/flame.dds differ diff --git a/jme3-testdata/src/main/resources/Textures/BumpMapTest/SimpleBump.j3m b/jme3-testdata/src/main/resources/Textures/BumpMapTest/SimpleBump.j3m new file mode 100644 index 000000000..9ed9187da --- /dev/null +++ b/jme3-testdata/src/main/resources/Textures/BumpMapTest/SimpleBump.j3m @@ -0,0 +1,11 @@ +Material Pong Rock : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess: 8.0 + NormalMap: Textures/BumpMapTest/Simple_normal.png + ParallaxMap : Textures/BumpMapTest/Simple_height.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/jme3-testdata/src/main/resources/Textures/BumpMapTest/Simple_height.png b/jme3-testdata/src/main/resources/Textures/BumpMapTest/Simple_height.png new file mode 100644 index 000000000..f396670d8 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/BumpMapTest/Simple_height.png differ diff --git a/jme3-testdata/src/main/resources/Textures/BumpMapTest/Simple_normal.png b/jme3-testdata/src/main/resources/Textures/BumpMapTest/Simple_normal.png new file mode 100644 index 000000000..e03ef00d8 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/BumpMapTest/Simple_normal.png differ diff --git a/jme3-testdata/src/main/resources/Textures/BumpMapTest/Tangent.j3m b/jme3-testdata/src/main/resources/Textures/BumpMapTest/Tangent.j3m new file mode 100644 index 000000000..851a0e9e9 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Textures/BumpMapTest/Tangent.png b/jme3-testdata/src/main/resources/Textures/BumpMapTest/Tangent.png new file mode 100644 index 000000000..0c9a3ce2f Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/BumpMapTest/Tangent.png differ diff --git a/jme3-testdata/src/main/resources/Textures/ColorRamp/cloudy.png b/jme3-testdata/src/main/resources/Textures/ColorRamp/cloudy.png new file mode 100644 index 000000000..169e98118 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/ColorRamp/cloudy.png differ diff --git a/jme3-testdata/src/main/resources/Textures/ColorRamp/toon.png b/jme3-testdata/src/main/resources/Textures/ColorRamp/toon.png new file mode 100644 index 000000000..70591884f Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/ColorRamp/toon.png differ diff --git a/jme3-testdata/src/main/resources/Textures/ColoredTex/Monkey.png b/jme3-testdata/src/main/resources/Textures/ColoredTex/Monkey.png new file mode 100644 index 000000000..eafa03fde Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/ColoredTex/Monkey.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Cursors/meme.cur b/jme3-testdata/src/main/resources/Textures/Cursors/meme.cur new file mode 100644 index 000000000..89c8a68e7 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Cursors/meme.cur differ diff --git a/jme3-testdata/src/main/resources/Textures/Cursors/monkey.ani b/jme3-testdata/src/main/resources/Textures/Cursors/monkey.ani new file mode 100644 index 000000000..68b125ce2 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Cursors/monkey.ani differ diff --git a/jme3-testdata/src/main/resources/Textures/Cursors/nyancat.ico b/jme3-testdata/src/main/resources/Textures/Cursors/nyancat.ico new file mode 100644 index 000000000..ab26e914c Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Cursors/nyancat.ico differ diff --git a/jme3-testdata/src/main/resources/Textures/HdrTest/Memorial.hdr b/jme3-testdata/src/main/resources/Textures/HdrTest/Memorial.hdr new file mode 100644 index 000000000..97919bf81 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/HdrTest/Memorial.hdr differ diff --git a/jme3-testdata/src/main/resources/Textures/HdrTest/Memorial.j3m b/jme3-testdata/src/main/resources/Textures/HdrTest/Memorial.j3m new file mode 100644 index 000000000..81c5c8a54 --- /dev/null +++ b/jme3-testdata/src/main/resources/Textures/HdrTest/Memorial.j3m @@ -0,0 +1,5 @@ +Material HDR Picture : Common/MatDefs/Misc/Unshaded.j3md { + MaterialParameters { + ColorMap : Flip Textures/HdrTest/Memorial.hdr + } +} \ No newline at end of file diff --git a/jme3-testdata/src/main/resources/Textures/Sky/Bright/BrightSky.dds b/jme3-testdata/src/main/resources/Textures/Sky/Bright/BrightSky.dds new file mode 100644 index 000000000..3379320ab Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Sky/Bright/BrightSky.dds differ diff --git a/jme3-testdata/src/main/resources/Textures/Sky/Bright/FullskiesBlueClear03.dds b/jme3-testdata/src/main/resources/Textures/Sky/Bright/FullskiesBlueClear03.dds new file mode 100644 index 000000000..1a86a1fda Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Sky/Bright/FullskiesBlueClear03.dds differ diff --git a/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/LICENSE.txt b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/LICENSE.txt new file mode 100644 index 000000000..9685769b1 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_down.jpg b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_down.jpg new file mode 100644 index 000000000..c9f5e16e3 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_down.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_east.jpg b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_east.jpg new file mode 100644 index 000000000..1eb30a715 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_east.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_north.jpg b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_north.jpg new file mode 100644 index 000000000..be769e595 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_north.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_south.jpg b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_south.jpg new file mode 100644 index 000000000..ec28db88f Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_south.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_up.jpg b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_up.jpg new file mode 100644 index 000000000..3128f76d1 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_up.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_west.jpg b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_west.jpg new file mode 100644 index 000000000..5528fd8c0 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Sky/Lagoon/lagoon_west.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Sky/St Peters/StPeters.hdr b/jme3-testdata/src/main/resources/Textures/Sky/St Peters/StPeters.hdr new file mode 100644 index 000000000..5283a6dad Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Sky/St Peters/StPeters.hdr differ diff --git a/jme3-testdata/src/main/resources/Textures/Sky/St Peters/StPeters.jpg b/jme3-testdata/src/main/resources/Textures/Sky/St Peters/StPeters.jpg new file mode 100644 index 000000000..4d46cef7a Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Sky/St Peters/StPeters.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall.j3m b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall.j3m new file mode 100644 index 000000000..8b54f9e39 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall.jpg new file mode 100644 index 000000000..c386a0d6c Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall2.j3m b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall2.j3m new file mode 100644 index 000000000..7db3ab893 --- /dev/null +++ b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall2.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_parallax.dds + PackedNormalParallax: true + } +} \ No newline at end of file diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall_height.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall_height.jpg new file mode 100644 index 000000000..3486674aa Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall_height.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall_normal.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall_normal.jpg new file mode 100644 index 000000000..249d553c5 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall_normal.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds new file mode 100644 index 000000000..fd93003dd Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/Pond/Pond.j3m b/jme3-testdata/src/main/resources/Textures/Terrain/Pond/Pond.j3m new file mode 100644 index 000000000..2e493db92 --- /dev/null +++ b/jme3-testdata/src/main/resources/Textures/Terrain/Pond/Pond.j3m @@ -0,0 +1,12 @@ +Material Pong Rock : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess: 8.0 + DiffuseMap: Repeat Textures/Terrain/Pond/Pond.jpg + NormalMap: Repeat Textures/Terrain/Pond/Pond_normal.png + Diffuse : 0.8 0.8 0.8 1.0 + UseMaterialColors : true + Specular : 1.0 1.0 1.0 1.0 + } + AdditionalRenderState { + } +} diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/Pond/Pond.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/Pond/Pond.jpg new file mode 100644 index 000000000..177881cfc Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/Pond/Pond.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/Pond/Pond_normal.png b/jme3-testdata/src/main/resources/Textures/Terrain/Pond/Pond_normal.png new file mode 100644 index 000000000..8e566ed4d Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/Pond/Pond_normal.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/Rock/Rock.PNG b/jme3-testdata/src/main/resources/Textures/Terrain/Rock/Rock.PNG new file mode 100644 index 000000000..4b5b1908b Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/Rock/Rock.PNG differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/Rock/Rock.j3m b/jme3-testdata/src/main/resources/Textures/Terrain/Rock/Rock.j3m new file mode 100644 index 000000000..643653b48 --- /dev/null +++ b/jme3-testdata/src/main/resources/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/jme3-testdata/src/main/resources/Textures/Terrain/Rock/Rock_normal.png b/jme3-testdata/src/main/resources/Textures/Terrain/Rock/Rock_normal.png new file mode 100644 index 000000000..48221c0c5 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/Rock/Rock_normal.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/Rock2/rock.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/Rock2/rock.jpg new file mode 100644 index 000000000..343dc1cb9 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/Rock2/rock.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/Rocky/Rocky.j3m b/jme3-testdata/src/main/resources/Textures/Terrain/Rocky/Rocky.j3m new file mode 100644 index 000000000..0d228f9c7 --- /dev/null +++ b/jme3-testdata/src/main/resources/Textures/Terrain/Rocky/Rocky.j3m @@ -0,0 +1,7 @@ +Material Rock : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess: 32.0 + DiffuseMap : Textures/Terrain/Rocky/RockyTexture.jpg + NormalMap : Textures/Terrain/Rocky/RockyNormals.jpg + } +} \ No newline at end of file diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/Rocky/RockyNormals.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/Rocky/RockyNormals.jpg new file mode 100644 index 000000000..cae306024 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/Rocky/RockyNormals.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/Rocky/RockyTexture.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/Rocky/RockyTexture.jpg new file mode 100644 index 000000000..ed60a4cc8 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/Rocky/RockyTexture.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/alpha1.png b/jme3-testdata/src/main/resources/Textures/Terrain/splat/alpha1.png new file mode 100644 index 000000000..4179ba517 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/alpha1.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/alpha2.png b/jme3-testdata/src/main/resources/Textures/Terrain/splat/alpha2.png new file mode 100644 index 000000000..49eacecf0 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/alpha2.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/alphamap.png b/jme3-testdata/src/main/resources/Textures/Terrain/splat/alphamap.png new file mode 100644 index 000000000..14f0a9b55 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/alphamap.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/alphamap2.png b/jme3-testdata/src/main/resources/Textures/Terrain/splat/alphamap2.png new file mode 100644 index 000000000..9367ef6b8 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/alphamap2.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/dirt.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/splat/dirt.jpg new file mode 100644 index 000000000..474206828 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/dirt.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/dirt_normal.png b/jme3-testdata/src/main/resources/Textures/Terrain/splat/dirt_normal.png new file mode 100644 index 000000000..2eb57f4af Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/dirt_normal.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/fortress512.png b/jme3-testdata/src/main/resources/Textures/Terrain/splat/fortress512.png new file mode 100644 index 000000000..b6b4ed165 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/fortress512.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/grass.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/splat/grass.jpg new file mode 100644 index 000000000..8d5b97d3c Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/grass.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/grass_normal.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/splat/grass_normal.jpg new file mode 100644 index 000000000..12de38085 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/grass_normal.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/mountains1024.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/splat/mountains1024.jpg new file mode 100644 index 000000000..37d647591 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/mountains1024.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/mountains128.png b/jme3-testdata/src/main/resources/Textures/Terrain/splat/mountains128.png new file mode 100644 index 000000000..4a4dc7cf8 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/mountains128.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/mountains512.png b/jme3-testdata/src/main/resources/Textures/Terrain/splat/mountains512.png new file mode 100644 index 000000000..13cb4d11c Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/mountains512.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/pools.png b/jme3-testdata/src/main/resources/Textures/Terrain/splat/pools.png new file mode 100644 index 000000000..22e8b8380 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/pools.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/road.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/splat/road.jpg new file mode 100644 index 000000000..21195e6eb Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/road.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/splat/road_normal.png b/jme3-testdata/src/main/resources/Textures/Terrain/splat/road_normal.png new file mode 100644 index 000000000..ae56480a3 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/splat/road_normal.png differ diff --git a/jme3-testdata/src/main/resources/profiling points b/jme3-testdata/src/main/resources/profiling points new file mode 100644 index 000000000..168346ce1 --- /dev/null +++ b/jme3-testdata/src/main/resources/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